Rails Force Models to Eager Load

Rails force models to eager load

After a great deal of trial and error, I recently learned that Jason Waldrip's answer is somewhat incomplete. As of Rails 5, Rails.application.eager_load! will indeed load all of the directories specified in config/application.rb. But it doesn't match Rails' actual behavior in production. To do that, one must instead mirror what Rails does:

Rails.configuration.eager_load_namespaces.each(&:eager_load!)

The salient difference between the approaches is that OPs answer won't eager load the files within app directories of Engines that live in the gems or vendor folders. Whereas Rails itself will identify where subclasses of Engine exist and will see to it that the appropriate app subdirectories are eager-loaded.

Behind the scenes

Rails 5 adds eager load directories in railties-5.0.2/lib/rails/engine/configuration.rb:39, where it runs

      paths.add "app",                 eager_load: true, glob: "{*,*/concerns}"
paths.add "app/assets", glob: "*"
paths.add "app/controllers", eager_load: true
paths.add "app/channels", eager_load: true, glob: "**/*_channel.rb"
paths.add "app/helpers", eager_load: true
paths.add "app/models", eager_load: true
paths.add "app/mailers", eager_load: true

These directories are not currently included in a default Rails.application.eager_load!

How can I force Rails to load all models?

rails 2:

Dir[Pathname(RAILS_ROOT) + 'app/models/**/*.rb'].each do |path|
require path
end

rails 3:

Dir[Rails.root + 'app/models/**/*.rb'].each do |path|
require path
end

another way:

(ActiveRecord::Base.connection.tables - %w[schema_migrations]).each do |table|
table.classify.constantize rescue nil
end

Force full object load with eager loading and nested objects

This is going to be difficult to do in only one query. The most straightforward I could get using just ActiveRecord methods was this:

Order.includes(:transactions, line_items: [:product])
.where(id: LineItem.where(product: product).pluck(:order_id))

I don't know much about Arel, but I think it might be able to accomplish this in one query rather than two.

Eager load in model?

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.

rails eager loading for one-to-many association

Your templates table does not have an article_id column according to the schema.rb you posted so you will need to create that reference.

Change

has_one :template 

in the articles model to

belongs_to :template

Eager loading last subordinate record

Yes,

Lets take this example

class Song < ActiveRecord::Base
has_many :votes
end

class Vote < ActiveRecord::Base
belongs_to :song
end

Add a new association to the Song

# for Rails version <4:
has_one :last_vote, :class_name => 'Vote', :order => 'created_at DESC'

# for Rails version 4+
has_one :last_vote, -> { order(created_at: :desc) }, class_name: 'Vote'

this new association will always return the most recently created Vote for a Song.

To eager load it

# for Rails version <3:
songs = Song.find(:all, :conditions => 'artist_name = "frank"', :include => :last_vote)

# for Rails version 3+:
songs = Song.includes(:last_vote).where(artist_name: 'frank')

Always eager load association with entity

You can include a "default_scope" in your model.

For Rails 4:

class Book
has_many :chapters
default_scope { includes(:chapters) }
end

For Rails 3:

class Book
has_many :chapters
default_scope includes(:chapters)
end

For Rails 2:

class Book
has_many :chapters
default_scope :include => :chapters
end


Related Topics



Leave a reply



Submit