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.
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.
Also see ExtendingActiveRecordExample for more examples on how to extend ActiveRecord to your needs.
This code is from this blog post
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.
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