[Proposal] Association methods on `CollectionProxy`

TL;DR: I would like to be able to do user.posts.comments to select comments from all posts of a given user.


Imagine a simple set of models. Users can create posts, and the same users can create comments on posts.

bin/rails generate model User email
bin/rails generate model Post title body user:references
bin/rails generate model Comment body post:references user:references
class User < ApplicationRecord
  has_many :posts
end

class Post < ApplicationRecord
  belongs_to :user
  has_many :comments
end

class Comment < ApplicationRecord
  belongs_to :post
  belongs_to :user
end

User 1 creates two posts. User 2 creates a comment on the first post and User 3 creates a comment on the second post.

user_1 = User.create!(email: 'one@missiveapp.com')
user_2 = User.create!(email: 'two@missiveapp.com')
user_3 = User.create!(email: 'tre@missiveapp.com')

post_1 = user_1.posts.create!(title: 'One’s First Post')
post_1.comments.create!(user: user_2, body: 'Two’s Take on First Post')

post_2 = user_1.posts.create!(title: 'One’s Second Post')
post_2.comments.create!(user: user_3, body: 'Tre’s Take on Second Post')

With that kind of setup, I often wish to do:

user_1.posts.comments
# => undefined method `comments' for #<Post::ActiveRecord_Associations_CollectionProxy:...> (NoMethodError)

Why can’t we? I don’t think there is any ambiguity to what this should translate to. It would be equivalent to:

Comment.where(post_id: user_1.posts.select(:id))
# => [#<Comment:... id: 1, ... post_id: 1, user_id: 2, ...>,
#     #<Comment:... id: 2, ... post_id: 2, user_id: 3, ...>]
SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (
  SELECT "posts"."id" FROM "posts" WHERE "posts"."user_id" = ?
)  [["user_id", 2]]

Given the Post#comments association, there would be a corresponding Post::ActiveRecord_Associations_CollectionProxy#comments method.

It would be even more valuable with more chained associations:

user_1.posts.comments.users

This would translate to:

User.where(id: Comment.where(post_id: user_1.posts.select(:id)).select(:user_id))
# => [#<User:... id: 2, email: "two@missiveapp.com", ...>,
#     #<User:... id: 3, email: "tre@missiveapp.com", ...>]
SELECT "users".* FROM "users" WHERE "users"."id" IN (
  SELECT "comments"."user_id" FROM "comments" WHERE "comments"."post_id" IN (
    SELECT "posts"."id" FROM "posts" WHERE "posts"."user_id" = ?
  )
)  [["user_id", 2]]

Note the usage of plural #users on the Comment collection proxy although the original association is singular Comment#user.

This seems like an intuitive addition that some people may have already suggested? I have no idea how complex the implementation would be, if there are particular concerns with polymorphic, :through associations, etc.

I’m just curious to see if there is any interest out there, or maybe I’m missing something that makes this a non-starter? Cheers! :v: