Reputation: 6310
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
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
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