Ruby on Rails: Is there a way to find with ActiveRecord that will first try the cache and if it fails run a query?
Let's say that I'm looking for a specific item in a has_many association.
Is there a way to find that won't rehit the database if what I'm requesting is already cached?
Here's my situation (which is not working and does not do what I requested):
class User
has_many :friendships
def some_function(buddy)
f = friendships.detect{|friendship| friendship.user == self &&
friendship.friend == buddy}
f.do_something
e开发者_如何学Pythonnd
end
The function is susceptible to N+1 problems, but most of the time I will already have either used :include or preloaded the user's friendships elsewhere. However, if I didn't do this, then this current function will fall back to requesting all the friendships, even though it only needs one. The solution would seem to be something like this:
f = Friendship.find_by_user_id_and_friend_id id, buddy.id
However, this will hit the database again even if all the friendships are cached and I have what I need right there.
Is there any way to either use the cached copy, or if none is available, search a specific record?
What you want is the #loaded? method of the association proxy:
class User
has_many :friendships
def some_function(buddy)
if friendships.loaded? then
f = friendships.detect{|friendship| friendship.user == self &&
friendship.friend == buddy}
else
Friendship.find_by_user_id_and_friend_id id, buddy.id
end
end
end
Unfortunately, #loaded? does not appear in the public documentation. You can always guard your call to #loaded? with friendships.respond_to?(:loaded?).
Documentation for AssociationProxy#loaded? on GitHub.
If you already gave called @user.friendships and want to access the cached version, you could use
def some_function(buddy)
f = friendships.select { |f| f.buddy_id == buddy.id }
f.do_something
end
The select method does not make a DB query and uses the cached friendships relationship. If you have not already called @user.friendships, the first call to this method will run the query and subsequent calls will use the cached version.
If you have already accessed the @user.friendships attribute (and thus, hitting the database to get the data), those associations will be stored in memory and you will no longer need to hit the database.
When it comes to finding a specific record out of an associated :has_many
dataset, I'm not quite sure. One option is to do a find_by_sql to write the sql to grab that specific record. You could also do something like this:
class User
has_many :friendships
def some_function(buddy)
f = Friendships.find_by_user_id(self.id, :conditions => "buddy_id = #{buddy.id}")
f.do_something
end
end
Which does a basic SQL search to match any records that have the current users's ID and the buddy ID that is passed into the function.
精彩评论