Chaining ActiveRecord Scopes from Different Models
You probably know you can chain ActiveRecord scopes together and it combines it all together into one sql statement, but did you know that you can also use scopes from associated models in your chain?
Let me show you with an example. Let’s say we have a discussion site where users can post comments with two simple models:
We could display a list of all users with User.all
or even display them alphabetically with User.order(:name)
(yes I’d create a scope if I were doing this for real).
What if I wanted to display the users sorted so that the ones with the most recent comment was first?
There’s a lot going on there, let’s break it down.
User.includes(:comments)
- tells active record to query both the users and comments tablesComment.order('comments.created_at desc')
- sorts the results by the date of the comment (we need to specify the table and column name since created_at is also a column on the users table)merge(Comment.XXX)
- lets us use a scope from the Comment model even though we’re dealing with Users
When ActiveRecord and ActiveRelation take all this and convert it into a sql statement it will join the users and comments tables and order by the comments.created_at column. Here’s the sql I actually get in irb (boy am I glad I didn’t have to type that sql myself!).
Adding Scopes to make it usable
It works to type all that but in a real application you’d add scopes to make it easier to work with. Let’s do that!
Now we can write some nice simple scopes like
User.by_most_recent_comment
to get all users sorted so the ones with recent comments are at the topUser.with_recent_comments
to get all users who have commented in the past monthUser.with_recent_comments.by_most_recent_comment
to get users who have commented in the past month sorted by the date of their most recent comment.
Happy scoping!