Skip to main content
Rails Concepts:

Querying Named Scopes Across Models with ActiveRecord::Relation#merge

37

Episode 6 · July 3, 2014

Use the merge method to prevent duplication of ActiveRecord scopes when you're querying across different models

ActiveRecord


After years of working with ActiveRecord and watching it change so much, it is exciting to find new features you didn't know about. The one I discovered this week is ActiveRecord#merge. It is one of the most underused methods in ActiveRecord, due in part, to the name. It isn't necessarily clear what they mean by "merge" but it's simply a way of using a named scope on a joined model.

Say we have two models that are associated and one of them has a scope:

class Author < ActiveRecord::Base
  has_many :books
end
class Book < ActiveRecord::Base
  belongs_to :author

  scope :available, ->{ where(available: true) }
end

Let's say we want to join the tables to find all Authors who have books that are available. Without ActiveRecord#merge, we have to make this query:

Author.joins(:books).where("books.available = ?", true)
SELECT "authors".* FROM "authors" INNER JOIN "books" ON "books"."author_id" = "authors"."id" WHERE "books"."available" = 't'

But with ActiveRecord#merge, this becomes a whole lot cleaner and we don't duplicate the available scope:

Author.joins(:books).merge(Book.available)
SELECT "authors".* FROM "authors" INNER JOIN "books" ON "books"."author_id" = "authors"."id" WHERE "books"."available" = 't'

As you can see, the resulting SQL queries are exactly the same. ActiveRecord#merge is a great way to reduce the duplication in your code to continue relying on the named scopes you define in your models. I really want to see more people using this so please share this around!

Transcripts

ActiveRecord merge is one of the coolest features that I've found in ActiveRecord recently, and we're going to go through an example that show you how to do this.

With ActiveRecord we set up two models here in our example. One is an Autor model and it has_many :books, and a Book belongs_to :author, now the book has two scopes that we're going to use in this example that allow us to filter out books. And if we come into our terminal, we can say Book.available, and this will select books where the available column is set to true, and if we go to Book.unavailable, we can see that it sees only books that are false and null for availability. This pulls out all the different books from the database but we can't use this scope when we do joins in the author model, so imagine that we want to get all the authors that have an available book.

Normally, we would go with Author.joins(:books), and this is the first step, it starts to join the books, and we get all the results in this relationship. So it does an inner join, but now if we want to do this, we have to say Author.joins(:books).where("books.available = ?", true), we're basically creating the scope ourselves manually because we can't say Author.joins(:books).available, we get a big, long error, because this available is looking for a scope on the author model. Now it would be really useful for us to be able to essentially merge these two queries that we're trying to do. Now that we've cleared out that error, we can take a look at a better approach to merging those two queries. We have the scope on the books, and we have authors.

If we say Author.joins(:books).merge(Book.available), and this allows us to query using both of the scopes because these ActiveRecord queries are not evaluated until you start using the objects, so right now this will merge the Book.available query with the Author.joins(:books) query and take that result and actually execute it. So when we run this, it actually does exactly what you expect. So it inner joins the books together on the author id, and it adds the where books.availabe is true. So the ActiveRecord merge allows you to use two tables and connect their scopes into a single query. This is really cool because it will save you a lot of time, because otherwise you're going to duplicate these scopes in your different models because you will have to go into the model and create a scope, and let's say:

scope :available_books, -> {joins(:books).where("books.availabe = ?", true)}

This is how you normally set it up without ActiveRecord merge, but using the merge, it allows you to get rid of the code that we just wrote and not duplicate it in two places because we already have it down here. And that is the beauty of ActiveRecord merge.

Transcript written by Miguel

Loading...

Subscribe to the newsletter

Join 18,000+ developers who get early access to new screencasts, articles, guides, updates, and more.

By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

More of a social being? We're also on Twitter and YouTube.