Reputation: 6310
Consider the following:
class User < ActiveRecord::Base
has_many :posts
end
Now I want to get posts for some banned users.
User.where(is_banned: true).posts
This produces a NoMethodError
as posts is not defined on ActiveRecord::Relation
.
What is the slickest way of making the code above work?
I can think of
User.where(is_banned: true).map(&:posts).flatten.uniq
But this is inefficient.
I can also think of
user_scope = User.where(is_banned: true)
Post.where(user: user_scope)
This requires the user association to be set up in the Post
model and it appears to generate a nested select. I don't know about the efficiency.
Ideally, I would like a technique that allows traversing multiple relations, so I can write something like:
User.where(is_banned: true).posts.comments.votes.voters
which should give me every voter (user) who has voted for a comment on a post written by a banned user.
Upvotes: 0
Views: 107
Reputation: 7561
Here's a start of a solution for your ideal technique. It probably doesn't work as written with extended chaining, and performance would probably be pretty bad. It would also require that you define the inverse_of
for each association —
module LocalRelationExtensions
def method_missing(meth, *args, &blk)
if (assoc = self.klass.reflect_on_association(meth)) && (inverse = assoc.inverse_of)
assoc.klass.joins(inverse.name).merge(self)
else
super
end
end
end
ActiveRecord::Relation.include(LocalRelationExtensions)
But really you should use the comment of @engineersmnky.
Upvotes: 0
Reputation: 1985
In your code:
User.where(is_banned: true)
will be and ActiveRecord::Relation and you need one record. So doing if from the User model would be more complicated. Depending on how the relationship is set up you could add a scope in your Post model.
scope :banned_users, -> { joins(:users).where('is_banned = ?', true) }
Then you would just call Post.banned_users
to get all the post created by banned users.
Upvotes: 1
Reputation: 29318
Why not just use joins
?
Post.joins(:user).where(users: {is_banned: true})
This will generate SQL to the effect of
SELECT *
FROM posts
INNER JOIN users ON posts.user_id = users.id
WHERE users.is_banned = true
This seems to be exactly what you are looking for. As far as your long chain goes you can do the same thing just with a much deeper join.
Upvotes: 2