Adding and Merging ActiveRecord Relations

The expressive possibilities of assembling ActiveRecord queries out of disparate parts make using Rails so fun.

Jared White by Jared White on October 24, 2020

An intriguing scenario arose in a project I was working on. In this application, creators can create plugin presets. Presets themselves are associated with “banks” — aka a bank has_many presets. There are also actions that can be taken against either banks or presets: users generally will bookmark them or download them. Thus these actions have a polymorphic association with both presets and banks.

What I wanted to do was package up all the actions users had performed for banks and related presets created by a particular creator. The resulting ActiveRecord query I arrived at expresses that well. Could this be refactored in a more performant way? (For example, using only one SQL query instead of three?) Very likely. But as this code is run only infrequently for reporting reasons, it’s a reasonable tradeoff between performance and readability.

Here’s the code for the ItemAction.for_creator(creator_profile) method:

class ItemAction < ApplicationRecord
  belongs_to :actionable, polymorphic: true
  enum action_type: [ :bookmark, :download, :publish ]

  def self.for_creator(creator_profile)
    banks = creator_profile.banks.published.select(:id)
    presets = Preset.where(bank: banks).select(:id)

    where(actionable: banks + presets).merge(
      ItemAction.bookmark.or(
        ItemAction.download
      )
    )
  end

  # other code, etc.
end

First, we get the list of banks. We use a published scope to limit the list to only banks which have been made available to the public. Also, we only need the id field back, not all the fields in the database table.

Second, we get the list of presets for those banks, again pulling in only the id field.

The last line of the method is where things get interesting. We want actions where the actionable association brings in both the banks and the presets we’ve loaded, so we use the + operator to concatenate both result sets together. Once we have that relation, we can use ActiveRecord’s merge functionality to pull in additional scopes that specify we only want either bookmark or download actions. We don’t want any other actions (say, publish), because those are actions taken by creators, not end users.

And that does the trick! Yay! A brief but useful example of the expressive power of adding and merging ActiveRecord relations together to get a final result.

“Ruby is simple in appearance, but is very complex inside, just like our human body.”

matz

Join 300 fullstack Ruby developers and subscribe to receive a timely tip you can apply directly to your Ruby site or application each week:

Banner image by Michael Dziedzic on Unsplash


Other Recent Articles

Episode 9: Preact Signals and the Signalize Gem

What are signals? What is find-grained reactivity? Why is everyone talking about them on the frontend these days? And what, if anything, can we apply from our newfound knowledge of signals to backend programming?

Continue Reading

Episode 8: Hotwiring Multi-Platform Rails Apps with Ayush Newatia

I’m very excited to have Ayush on the show today to talk about all things fullstack web dev, his new book The Rails & Hotwire Codex, and why “vanilla” is awesome!

Continue Reading

More This Way