Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force load association on model instance

Consider this:

class Post < ActiveRecord::Base
  has_many :comments

  def last_comment
    comments.last
  end
end

railsconsole> p = Post.last
  Post Load (0.2ms)  SELECT  `posts`.* FROM `posts`  WHERE ORDER BY `posts`.`id` DESC LIMIT 1
railsconsole> p.last_message
  Post Load (0.4ms)  SELECT `comments`.* FROM `comments`  WHERE `messages`.`post_id` = 14
railsconsole> p.last_message
  Post Load (0.4ms)  SELECT `comments`.* FROM `comments`  WHERE `messages`.`post_id` = 14

You would think that only 2 queries should be occuring here: the initial find and then the loading of the association. Subsequent calls to the association should be cached. However, they are not. The association is never loaded and cached because Rails is trying to be smart and only load the last record. And since Rails isn't keeping track of the last record(or first or any other further custom query on the association), it just returns the answer every single time.

However, what if you want to cache the association? I searched SO and could not find a direct answer to this.

like image 388
Peter P. Avatar asked Feb 12 '23 18:02

Peter P.


2 Answers

When you call an association on an ActiveRecord instance, you get back a proxy object, and in the case of a has_many object, you get a CollectionProxy.

What's weird is that you can call #load on the collection proxy but this does not load the association cache.

Hidden in the Rails docs: http://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-load_target

#load_target

|

class Post < ActiveRecord::Base
  has_many :comments

  def last_comment
    comments.load_target unless comments.loaded?
    comments.last
  end
end

Hope others find this useful.

like image 55
Peter P. Avatar answered Feb 27 '23 16:02

Peter P.


I would make last_comment an association

has_one :last_comment, -> { order 'created_at DESC' }, class_name: 'Comment'

That way you can include, join, or eager_load too.

like image 32
James Daniels Avatar answered Feb 27 '23 15:02

James Daniels