31 Aug 2008, Posted by Jo Hund in Ruby/Rails, 7 Comments
Recipe: Make request environment available to models in Rails
How often do you wish you had access to current_user in one of your models? I needed it for an app that required auditing. I had ActiveRecord call backs on the audited models to create audit entries on every record operation. The problem was that the models had no access to the currently logged in user.
Objective
To store web request environment parameters accessibly everywhere in my Rails app. Examples:
- the current user needs to be accessible to my Models to implement an audit trail via Active Record callbacks.
- I want to store the current time zone so that it can be used by the various parts of my app (This is actually what Rails does for TimeZone support).
You might say “Auditing can be done in the controller”. True, however I find it much more elegant to put it into the models. So how did I do it? I used ActiveRecord call backs on creating, updating, and deleting records. I stored the current user in a way that the models can access it. The same way Rails stores the current time zone for a request: In the current thread. Check out the rails source for details how it is done there.
Summary
- implement accessors for ‘current’ on each class that is a request environment parameter. In this example I implement User.current
- set the variable via the accessors in an application_controller before_filter
- accessors store current request parameters in Thread.current[:parameter_name]
Please see github for the code for this and my other recipes.
Here is the code to store a variable in the current thread so that it is accessible everywhere in the Rails app:
In application controller
class ApplicationController < ActionController::Base
before_filter :set_request_environment
private
# stores parameters for current request
def set_request_environment
User.current = current_user # current_user is set by restful_authentication
# You would also set the time zone for Rails time zone support here:
# Time.zone = Person.current.time_zone
end
end
In your User model
class User < ActiveRecord::Base
#-----------------------------------------------------------------------------------------------------
# CLASS METHODS
#-----------------------------------------------------------------------------------------------------
def self.current
Thread.current[:user]
end
def self.current=(user)
raise(ArgumentError,
"Invalid user. Expected an object of class 'User', got #{user.inspect}") unless user.is_a?(User)
Thread.current[:user] = user
end
end
There is a danger of abusing this for global variables. They are evil. However I think it is perfectly valid to make the current time zone, the current user, and possibly any other scoping resource globally available. They are like gravity — they’re everywhere, completely pervasive.
And the nice thing is that this also works on the console. Before I manipulate data, I can set Person.current to a user, and will have an audit trail for things I did outside of the running rails app.

7 Comments
December 20, 2008 2:47 am
yawningman
Thanks for this info, it was just what I needed to solve a similar problem of needing to store variables that where unique per request from within a rails plugin.
March 2, 2009 10:12 pm
ginty
This is great stuff, thanks for that and your other related post.
Are you currently running this in production?
I’m just slightly nervous (but without a good reason) given some of the discussion about the use of Thread.current in some way being a hack.
I really don’t believe there is a better solution though, it makes so much sense to put the gatekeeper in the model, and it seems to be a violation of MVC to have to put that responsibility in the controller.
March 02 2009 22:22 pm
Jo Hund
@ginty: Yes, I use this approach in production. I had a quick conversation with lifo on his blog about this. It feels a bit dirty. Global state. Not very object oriented. I am still wrestling with this. I believe permissions need to be delegated to the model. I don't care about MVC in this case. In a Graphics software, you do a lot of presentation work in the model, because that is the main concern of the software. So in a multi user web application, permissions are part of business logic and should be handled in the model.
The challenge is to give the model access to the current user. There are other solutions (sweepers), but using those for permissions feels just as much like a hack.
I am looking at other MVC implementations for inspiration. E.g. OS X software. What is Cocoa's way of handling this? I didn't dig very deep, however it seemed like you get get to the current user via a global accessor.
March 3, 2009 9:19 am
ginty
@Jo: Yep I agree, definitely part of the business logic and this definitely provides the means to put it where it belongs.
Thanks for the reply, I’m converting my app over to something like this as we speak.
I plan to do a slight variation on your approach and only access my models from the controller via new methods Model.all_available (replacing find_all from the controller) and Model.get (replacing find(x)).
Then within these I will put the permission checks to return only what the current user is entitled to.
Cheers!
October 15, 2009 3:34 am
James Bebbington
Just adding a couple of manual trackbacks that are worth checking out:
http://stackoverflow.com/questions/1568218/access-to-currentuser-from-within-a-model-in-ruby-on-rails
http://www.pluitsolutions.com/2006/08/15/rails-auto-assign-created-by-and-updated-by/#comments
October 27, 2009 5:53 pm
Braxton Beyer
Could you achieve this same thing using Rails.cache? (http://www.highdots.com/forums/ruby-rails-talk/best-way-store-global-variable-285504.html)
What are the pros/cons of each method?
December 18, 2009 9:05 am
LeipeLeon
hmm….
seems to me that Thread.current is the thread of the rails process.
what about when 2 different users update simultaneously?
Posting your comment...
Leave A Comment