Niels B.
Niels B.

Reputation: 6310

Scoping through association

Consider the following

class Engineer < ActiveRecord::Base
  has_many :ideas
  scope :masters, -> { where(educational_level: ["master", "candidate"]) }
end

class Idea < ActiveRecord::Base
  belongs_to :engineer
  scope :popular, -> { where("rating > ?", 4) }
end

Now, let's say I want to retrieve all popular ideas conceived by master engineers ordered by the engineers' age. For simplicity, assume that age is just an integer column in the engineers table.

One way to do this is to first fetch the engineers.

popular_ideas_by_educated_engineers = []
Engineer.masters.order("age").each do |engineer|
  popular_ideas_by_educated_engineers += engineer.ideas.popular
end

This code has an n+1 issue, because ideas are not eager loaded and it overall feels clunky.

I would like to write the code in a way that allows to get the desired results by writing something like

Idea.popular.by_masters.order_by_engineer_age

Is this possible - and if so, how?

I know I can hardcode SQL into the Idea class which joins on engineers and imposes some limitations, but what I am looking for is utilizing the scopes already defined on the Engineer class.

Upvotes: 0

Views: 35

Answers (2)

midu
midu

Reputation: 1707

You can use joins and merges here:

class Engineer < ActiveRecord::Base
  scope :masters, -> { where(educational_level: ["master", "candidate"]) }
  scope :ordered_by_age, -> { order(:age) } 
end

class Idea < ActiveRecord::Base
  belongs_to :engineer

  scope :popular, -> { where("rating > ?", 4) }
  scope :by_master, -> { joins(:engineer).merge(Engineer.masters) }
  scope :order_by_engineer_age, -> { joins(:engineer).merge(Engineer.order_by_age) }
end

Upvotes: 0

tadman
tadman

Reputation: 211740

You might want to try and flip this around, like you suggest:

Idea.popular.includes(:engineer).by_masters.order('engineers.age')

Where your by_masters scope looks something like:

scope :by_masters, -> { where: { engineers: { educational_level: 'master' } } }

It's the includes call that forces a JOIN that makes this possible.

Upvotes: 1

Related Questions