Ruby on Rails
Howto Add created_by and updated_by (Version #20)

Warning
This code does not work in rails 2

If you are having trouble look at
http://dev.rubyonrails.org/changeset/7749

The instance_method_already_implemented? changes break this module.

Reverting to the previous version in green fixes the issue.

Much like how Timestamping callbacks are set up by default for columns named created_at/created_on and updated_at/updated_on, (See http://rails.rubyonrails.com/classes/ActiveRecord/Timestamp.html) this will automaticaly populate created_by and updated_by fields with the user who created/updated items.

Also see ExtendingActiveRecordExample for more examples on how to extend ActiveRecord to your needs.

This code is from this blog post

The Code

Place this in /lib/usermonitor.rb

module ActiveRecord

  module UserMonitor

    def self.included(base)

      base.class_eval do
        alias_method :create_without_user, :create
        alias_method :create, :create_with_user

        alias_method :update_without_user, :update
        alias_method :update, :update_with_user
      end
    end

    def create_with_user
        user = user_model.current_user
        self[:created_by] = user.id if respond_to?(:created_by) && created_by.nil?
        self[:updated_by] = user.id if respond_to?(:updated_by)
        create_without_user
    end

    def update_with_user
        user = user_model.current_user
        self[:updated_by] = user.id if respond_to?(:updated_by)
        update_without_user
    end

    def created_by
      begin
        user_model.find(self[:created_by])
      rescue ActiveRecord::RecordNotFound
        nil
      end
    end

    def updated_by
      begin
        user_model.find(self[:updated_by])
      rescue ActiveRecord::RecordNotFound
        nil
      end
    end
  end

  class Base

    @@default_user_model = :users
    cattr_accessor :user_model_name

    def user_model_name
      @@user_model_name || @@default_user_model
    end

    def self.relates_to_user_in(model)
      self.user_model_name = model
    end

    def user_model
      Object.const_get(user_model_name.to_s.singularize.humanize)
    end
  end
end

Then add the following to the bottom of /config/environment.rb

require 'usermonitor'
ActiveRecord::Base.class_eval do
  include ActiveRecord::UserMonitor
end

Now if you add created_by or updated_by to your database tables, you will be able to access the user who created/updated the objects by calling object.created_by and object.updated_by respectively. Likewise you can set the user by assigning a user object to either one.

This defaults to assuming you have a user model called User, which can be overriden by calling relates_to_user_in in your model like so:


class WebPage < ActiveRecord::Base
  relates_to_user_in :monkeys
end

The user model must contain “current_user” that references the currently logged in user.

An example of setting up a “current_user” method in your User model is as follows

In my model I have an accessor for current_user:
——user.rb——
cattr_accessor :current_user
————————-

Then in my application controller I have a before_filter which grabs the user ID from the session (if there is one) and sets the current_user in the model.
——application.rb——
before_filter do |c|

User.current_user = User.find(c.session[:user]) unless c.session[:user].nil?
end
————————-

So now you can call User.current_user to get the current logged in user.

——————————————
Note: an update to the blog post mentioned below says that thread safety is not actually an issue here …

I’m afraid that current_user method is not thread-safe, so I use Thread.current instead to retrieve currently logged-in user.

Find out more info at this blog post.

Here is the updated version with Thread.current

/app/controllers/application.rb


class ApplicationController < ActionController::Base
….

before_filter :set_current_user def set_current_user Thread.current‘user’ = session[:user] end

end

/lib/usermonitor.rb


module ActiveRecord

module UserMonitor
def self.included(base)
base.class_eval do
alias_method_chain :create, :user
alias_method_chain :update, :user

def current_user Thread.current‘user’ end end end def create_with_user user = current_user if !user.nil? self[:created_by] = user.id if respond_to?(:created_by) && created_by.nil? self[:updated_by] = user.id if respond_to?(:updated_by) end create_without_user end def update_with_user user = current_user self[:updated_by] = user.id if respond_to?(:updated_by) && !user.nil? update_without_user end def created_by begin current_user.class.find(self[:created_by]) if current_user rescue ActiveRecord::RecordNotFound nil end end def updated_by begin current_user.class.find(self[:updated_by]) if current_user rescue ActiveRecord::RecordNotFound nil end end end

end

Note alias_method_chain is currently only available if you are using Rails on Edge.

If you are using the LoginGenerator, neither of the two beforementioned codes work! After working on a fix for several hours, I decided to post it here, so that others can possibly use this very usefull feature. I’ve merged code here and there from the two and am using the thread-safe Thread.current to store the current_user. There also was a typo (or maybe it works, but only on edge!) that prevented the current_user to be determined. The following code works with 1.1.6 and the newest LoginGenerator. As alias_method_chain is only supported on Edge I’m not using it.

application.rb (inside the class)


before_filter :set_current_user

def set_current_user Thread.current‘user’ = @session‘user’ end

@environment.rb (insert before end of file)


require ‘usermonitor’
ActiveRecord::Base.class_eval do
include ActiveRecord::UserMonitor
end

/lib/usermonitor.rb


module ActiveRecord

module UserMonitor def self.included(base) base.class_eval do alias_method :create_without_user, :create alias_method :create, :create_with_user alias_method :update_without_user, :update alias_method :update, :update_with_user def current_user Thread.current‘user’ end end end def create_with_user user = current_user if !user.nil? self[:created_by] = user.id if respond_to?(:created_by) && created_by.nil? self[:updated_by] = user.id if respond_to?(:updated_by) end create_without_user end def update_with_user user = current_user self[:updated_by] = user.id if respond_to?(:updated_by) && !user.nil? update_without_user end def created_by begin current_user.class.find(self[:created_by]) if current_user rescue ActiveRecord::RecordNotFound nil end end def updated_by begin current_user.class.find(self[:updated_by]) if current_user rescue ActiveRecord::RecordNotFound nil end end end

end

That’s it!

category:Howto, Example

Warning
This code does not work in rails 2

If you are having trouble look at
http://dev.rubyonrails.org/changeset/7749

The instance_method_already_implemented? changes break this module.

Reverting to the previous version in green fixes the issue.

Much like how Timestamping callbacks are set up by default for columns named created_at/created_on and updated_at/updated_on, (See http://rails.rubyonrails.com/classes/ActiveRecord/Timestamp.html) this will automaticaly populate created_by and updated_by fields with the user who created/updated items.

Also see ExtendingActiveRecordExample for more examples on how to extend ActiveRecord to your needs.

This code is from this blog post

The Code

Place this in /lib/usermonitor.rb

module ActiveRecord

  module UserMonitor

    def self.included(base)

      base.class_eval do
        alias_method :create_without_user, :create
        alias_method :create, :create_with_user

        alias_method :update_without_user, :update
        alias_method :update, :update_with_user
      end
    end

    def create_with_user
        user = user_model.current_user
        self[:created_by] = user.id if respond_to?(:created_by) && created_by.nil?
        self[:updated_by] = user.id if respond_to?(:updated_by)
        create_without_user
    end

    def update_with_user
        user = user_model.current_user
        self[:updated_by] = user.id if respond_to?(:updated_by)
        update_without_user
    end

    def created_by
      begin
        user_model.find(self[:created_by])
      rescue ActiveRecord::RecordNotFound
        nil
      end
    end

    def updated_by
      begin
        user_model.find(self[:updated_by])
      rescue ActiveRecord::RecordNotFound
        nil
      end
    end
  end

  class Base

    @@default_user_model = :users
    cattr_accessor :user_model_name

    def user_model_name
      @@user_model_name || @@default_user_model
    end

    def self.relates_to_user_in(model)
      self.user_model_name = model
    end

    def user_model
      Object.const_get(user_model_name.to_s.singularize.humanize)
    end
  end
end

Then add the following to the bottom of /config/environment.rb

require 'usermonitor'
ActiveRecord::Base.class_eval do
  include ActiveRecord::UserMonitor
end

Now if you add created_by or updated_by to your database tables, you will be able to access the user who created/updated the objects by calling object.created_by and object.updated_by respectively. Likewise you can set the user by assigning a user object to either one.

This defaults to assuming you have a user model called User, which can be overriden by calling relates_to_user_in in your model like so:


class WebPage < ActiveRecord::Base
  relates_to_user_in :monkeys
end

The user model must contain “current_user” that references the currently logged in user.

An example of setting up a “current_user” method in your User model is as follows

In my model I have an accessor for current_user:
——user.rb——
cattr_accessor :current_user
————————-

Then in my application controller I have a before_filter which grabs the user ID from the session (if there is one) and sets the current_user in the model.
——application.rb——
before_filter do |c|

User.current_user = User.find(c.session[:user]) unless c.session[:user].nil?
end
————————-

So now you can call User.current_user to get the current logged in user.

——————————————
Note: an update to the blog post mentioned below says that thread safety is not actually an issue here …

I’m afraid that current_user method is not thread-safe, so I use Thread.current instead to retrieve currently logged-in user.

Find out more info at this blog post.

Here is the updated version with Thread.current

/app/controllers/application.rb


class ApplicationController < ActionController::Base
….

before_filter :set_current_user def set_current_user Thread.current‘user’ = session[:user] end

end

/lib/usermonitor.rb


module ActiveRecord

module UserMonitor
def self.included(base)
base.class_eval do
alias_method_chain :create, :user
alias_method_chain :update, :user

def current_user Thread.current‘user’ end end end def create_with_user user = current_user if !user.nil? self[:created_by] = user.id if respond_to?(:created_by) && created_by.nil? self[:updated_by] = user.id if respond_to?(:updated_by) end create_without_user end def update_with_user user = current_user self[:updated_by] = user.id if respond_to?(:updated_by) && !user.nil? update_without_user end def created_by begin current_user.class.find(self[:created_by]) if current_user rescue ActiveRecord::RecordNotFound nil end end def updated_by begin current_user.class.find(self[:updated_by]) if current_user rescue ActiveRecord::RecordNotFound nil end end end

end

Note alias_method_chain is currently only available if you are using Rails on Edge.

If you are using the LoginGenerator, neither of the two beforementioned codes work! After working on a fix for several hours, I decided to post it here, so that others can possibly use this very usefull feature. I’ve merged code here and there from the two and am using the thread-safe Thread.current to store the current_user. There also was a typo (or maybe it works, but only on edge!) that prevented the current_user to be determined. The following code works with 1.1.6 and the newest LoginGenerator. As alias_method_chain is only supported on Edge I’m not using it.

application.rb (inside the class)


before_filter :set_current_user

def set_current_user Thread.current‘user’ = @session‘user’ end

@environment.rb (insert before end of file)


require ‘usermonitor’
ActiveRecord::Base.class_eval do
include ActiveRecord::UserMonitor
end

/lib/usermonitor.rb


module ActiveRecord

module UserMonitor def self.included(base) base.class_eval do alias_method :create_without_user, :create alias_method :create, :create_with_user alias_method :update_without_user, :update alias_method :update, :update_with_user def current_user Thread.current‘user’ end end end def create_with_user user = current_user if !user.nil? self[:created_by] = user.id if respond_to?(:created_by) && created_by.nil? self[:updated_by] = user.id if respond_to?(:updated_by) end create_without_user end def update_with_user user = current_user self[:updated_by] = user.id if respond_to?(:updated_by) && !user.nil? update_without_user end def created_by begin current_user.class.find(self[:created_by]) if current_user rescue ActiveRecord::RecordNotFound nil end end def updated_by begin current_user.class.find(self[:updated_by]) if current_user rescue ActiveRecord::RecordNotFound nil end end end

end

That’s it!

category:Howto, Example