开发者

Rails Devise: How do I (mem)cache devise's database requests for the user object?

Every time I hit an authenticated page, I notice devise issuing an SQL statement :

User Load (0.2ms) SELECT users.* FROM users WHERE (users.id = 1) LIMIT 1

(I'm using Rails 3 btw .. so cache_money seems out as a solution and despite a lot of searching I've found no substitute).

I tried many overrides in the user model and only find_by_sql seems called. Which gets passed a string of the entire SQL statement. Something intuitive like find_by_id or find doesn't seem to get called. I 'can' override this method and glean the user-id and do a reasonable cache system from that - but that's quite ugly.

I also tried overriding authenticate_user which I can intercept one SQL attempt but then calls to current_user seems to try it again.

Simply, my user objects change rarely and its a sad state开发者_如何学编程 to keep hitting the db for this instead of a memcache solution. (assume that I'm willing to accept all responsibility for invalidating said cache with :after_save as part but not all of that solution)


The following code will cache the user by its id and invalidate the cache after each modification.

class User < ActiveRecord::Base

  after_save :invalidate_cache
  def self.serialize_from_session(key, salt)
    single_key = key.is_a?(Array) ? key.first : key
    user = Rails.cache.fetch("user:#{single_key}") do
       User.where(:id => single_key).entries.first
    end
    # validate user against stored salt in the session
    return user if user && user.authenticatable_salt == salt
    # fallback to devise default method if user is blank or invalid
    super
  end

  private
    def invalidate_cache
      Rails.cache.delete("user:#{id}")
    end
end


WARNING: There's most likely a better/smarter way to do this.

I chased this problem down a few months back. I found -- or at least, I think I found -- where Devise loads the user object here: https://github.com/plataformatec/devise/blob/master/lib/devise/rails/warden_compat.rb#L31

I created a monkey patch for that deserialized method in /initializers/warden.rb to do a cache fetch instead of get. It felt dirty and wrong, but it worked.


I've been struggling with this, too.

A less convoluted way of doing this is to add this class method to your User model:

def self.serialize_from_session(key, salt)
  single_key = key.is_a?(Array) ? key.first : key
  Rails.cache.fetch("user:#{single_key}") { User.find(single_key) }
end

Note that I'm prepending the model name to the object ID that is passed in for storing/retrieving the object from the cache; you can use whatever scheme fits your needs.

The only thing to worry about, of course, is invalidating the user in the cache when something changes. It would have been nice instead to store the User in the cache using the session ID as part of the key, but the session is not available in the model class, and is not passed in to this method by Devise.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜