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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With