Search code examples
ruby-on-railsruby-on-rails-3deviseruby-on-rails-3.2ruby-on-rails-4

How to get Devise's current_user in ActiveRecord callback in Rails?


I'm using Devise and Rails 3.2.16. I want to automatically insert who created a record and who updated a record. So I have something like this in models:

before_create :insert_created_by
before_update :insert_updated_by

private
def insert_created_by
  self.created_by_id = current_user.id
end
def insert_updated_by
  self.updated_by_id = current_user.id
end

Problem is that I get the error undefined local variable or method 'current_user' because current_user is not visible in a callback. How can I automatically insert who created and updated this record?

If there's an easy way to do it in Rails 4.x I'll make the migration.


Solution

  • Editing @HarsHarl's answer would probably have made more sense since this answer is very much similar.

    With the Thread.current[:current_user] approach, you would have to make this call to set the User for every request. You've said that you don't like the idea of setting a variable for every single request that is only used so seldom; you could chose to use skip_before_filter to skip setting the User or instead of placing the before_filter in the ApplicationController set it in the controllers where you need the current_user.

    A modular approach would be to move the setting of created_by_id and updated_by_id to a concern and include it in models you need to use.

    Auditable module:

    # app/models/concerns/auditable.rb
    
    module Auditable
      extend ActiveSupport::Concern
    
      included do 
        # Assigns created_by_id and updated_by_id upon included Class initialization
        after_initialize :add_created_by_and_updated_by
    
        # Updates updated_by_id for the current instance
        after_save :update_updated_by
      end
    
      private
    
      def add_created_by_and_updated_by
        self.created_by_id ||= User.current.id if User.current
        self.updated_by_id ||= User.current.id if User.current
      end
    
      # Updates current instance's updated_by_id if current_user is not nil and is not destroyed.
      def update_updated_by
        self.updated_by_id = User.current.id if User.current and not destroyed?
      end
    end
    

    User Model:

    #app/models/user.rb
    class User < ActiveRecord::Base
      ...
    
      def self.current=(user)
        Thread.current[:current_user] = user
      end
    
      def self.current
        Thread.current[:current_user]
      end
      ...
    end
    

    Application Controller:

    #app/controllers/application_controller
    
    class ApplicationController < ActionController::Base
      ...
      before_filter :authenticate_user!, :set_current_user
    
      private
    
      def set_current_user
        User.current = current_user
      end
    end
    

    Example Usage: Include auditable module in one of the models:

    # app/models/foo.rb
    class Foo < ActiveRecord::Base
      include Auditable
      ...
    end
    

    Including Auditable concern in Foo model will assign created_by_id and updated_by_id to Foo's instance upon initialization so you have these attributes to use right after initialization, and they are persisted into the foos table on an after_save callback.