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!