Reputation: 13388
I wonder if we could eager load in model level:
# country.rb
class Country < ActiveRecord::Base
has_many :country_days
def country_highlights
country_days.map { |country_day| country_day.shops }.flatten.uniq.map { |shop| shop.name }.join(", ")
end
end
# country_day.rb
class CountryDay < ActiveRecord::Base
belongs_to :country
has_many :country_day_shops
has_many :shops, :through => :country_day_shops
end
# shop.rb
class Shop < ActiveRecord::Base
end
Most of the times it's difficult to use .includes
in controller because of some polymorphic association. Is there anyway for me to eager load the method country_highlights
at the model level, so that I don't have to add .includes
in the controller?
Upvotes: 1
Views: 1471
Reputation: 15056
You can't "eager load" country_days from a model instance, but you can certainly skip loading them all together by using a has_many through:
. You can also skip the extra map, too.
# country.rb
class Country < ActiveRecord::Base
has_many :country_days
has_many :country_day_shops, through: :country_days #EDIT: You may have to add this relationship
has_many :shops, through: :country_day_shops #And change this one to use the new relationship above.
def country_highlights
shops.distinct_names.join(", ")
end
end
# country_day.rb
class CountryDay < ActiveRecord::Base
belongs_to :country
has_many :country_day_shops
has_many :shops, :through => :country_day_shops
end
# shop.rb
class Shop < ActiveRecord::Base
def self.distinct_names
pluck("DISTINCT shops.name") #Edit 2: You may need this instead of 'DISTINCT name' if you get an ambiguous column name error.
end
end
The has_many through:
will use a JOIN
to load the associate shop records, in effect eager loading them, rather than loading all country_day records and then for each country_day record, loading the associated shop.
pluck("DISTINCT name")
will return an array of all of the unique names of shops in the DB, using the DB to perform a SELECT DISTINCT
, so it will not return duplicate records, and the pluck
will avoid loading ActiveRecord instances when all you need is the string name
.
Upvotes: 2
Reputation: 10856
Edit: Read the comments first
You could cache the end result (the joined string or text record in your case), so you'll not have to load several levels of records just to build this result.
1) Add a country_highlights
text column (result might be beyond string column limits)
2) Cache the country_highlights
in your model with a callback, e.g. before every save.
class Country < ActiveRecord::Base
has_many :country_days
before_save :cache_country_highlights
private
def cache_country_highlights
self.country_highlights = country_days.flat_map(&:shops).uniq.map(&:name).join(", ")
end
end
Caching you calculation will invoke a little overhead when saving a record, but having to load only one instead of three model records for displaying should speed up your controller actions so much that it's worth it.
Upvotes: 1