Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)

like image 951
user724148 Avatar asked Apr 25 '11 18:04

user724148


2 Answers

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
like image 121
Mic92 Avatar answered Sep 18 '22 13:09

Mic92


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.

like image 30
Adam Rubin Avatar answered Sep 17 '22 13:09

Adam Rubin