By default, the plugin will call User.current_user to get the user that
is performing the action. To change this behavior, you can specify a
:user_class_name and :user_method.
Use a berfore_filter in your controller. Your model shoud *never* try
to access information in the session. If it needs access to something
that happens to be stored in the session, inject it into the model -
don't try and let the model retrieve it itself:
class ApplicationController
before_filter { User.current_user = User.find(session[:current_user_id]) }
...
end
Max - is the User object supposed to already exist, or can I just create “User.current_user” out-of-the-blue like that in Ruby? Is it actually creating a variable called “User.current_user” OR creating an object instance of a new User class on the fly, and then setting an attribute call “current_user”? i.e. unless User is a standard class or somethign that I’m missing???
ohhh…you were no doubt meaning I should use Ruby’s dynamic capability to add a class or method to the Model class from within the controller as a means to pass the information across?
I’m looking for a solution to pass the same session data (
e.g. userid) to every Model generically, as I’ll be using it for audit. Any suggestions regarding how to do this? Do things such as “cattr_accessor” or “class_eval do extend” help here. For example how could I in Ruby code go:
a) From application_controller.rb
work out the actual controller class, e.g. Invoices
add a new attribute (say “userid”) to the associated Invoices model class
set this to the current session value
b) From the model class then access this value via: “self.userid”
ohhh...you were no doubt meaning I should use Ruby's dynamic capability to
add a class or method to the Model class from within the controller as a
means to pass the information across?
I'm looking for a solution to pass the same session data (e.g. userid) to
every Model generically, as I'll be using it for audit. Any suggestions
I think he was just suggesting you write a typical class method in your
User class:
class User
...
def self.current_user(user)
@@current_user = user
end
end
then in your controller, you can say something like
User.current_user = # your user object here
and this is especially convenient as a before_filter method, since it
will be called regardless of what action is being called in your
controller.
Since models can talk to each other, any model can call
User.current_user.
I think so Jeff (thanks), in particular “models can talk to each other” - I didn’t know this. Can I clarify? -:
Do you mean that say I’m in model “invoices” which has the acts_as_audited plugin applied to it, within this invoices model context I could access still User.current_user? Would it access a variable injected in it directly without trying to get it from the database then (for which there wouldn’t be such an method, i…e if I inject a method “test_method” into User?
Also doesn’t the @@ imply that this is a class variable? In this case wouldn’t this be shared with all users logged in and hence you might pickup the username for someone else? (or am I still thinking java)
PS. Here’s an example - anyone point out what I’m doing wrong? Again I’m trying to set a variable in a controller (Contacts controller in this case) available in a model (the Contacts model). I’m trying to do this with via using the Model class Suberb (just as a test).
I get the error = “undefined method `current_user_gregs’ for Suberb:Class”
You only defined a setter method for the current_user_gregs attribute.
Use either:
class Suberb < ActiveRecord::Base
def current_user_grege= value
@@current_user_gregs = value
end
def current_user_gregs
@@current_user_gregs = value
end
end
Or
class Suberb < ActiveRecord::Base
cattr_accessor :current_user_gregs
end
In Rails, nothing is shared between requests, which is unlike Java.
The closest analogy in Java would be a ThreadLocal pseudo-singleton,
for example
public class UserManager {
static ThreadLocal users = new ThreadLocal();
static public void setCurrentUser( User u ) {
users.set(u);
}
static public User getCurrentUser()
{
return (User)users.get();
}
}
This way however is prone to concurrency issue, because RoR
Dispatcher is not thread-safe and class variable is a single instance
variable. Thus concurrent thread executions may provides current_user
value with the other concurrently-executing-logged-in user.
Another way is to use Thread.current. With it, you can store a short-live variable value for the executing thread, and it is thread-save.
This way however is prone to concurrency issue, because RoR Dispatcher is
not thread-safe and class variable is a single instance variable. Thus
concurrent thread executions may provides current_user value with the other
concurrently-executing-logged-in user.
Another way is to use Thread.current. With it, you can store a short-live
variable value for the executing thread, and it is thread-save.
===========================
How does this align with what you were explaining re "nothing is shared
between requests" do you know?
I wasn't aware of that... thanks for pointing this out. The
Thread.current solution explained in the article is pretty much the
same as the ThreadLocal stuff in the Java example I posted. That's
probably the best way to go.
I'll have to have a look at the dispatcher code to try and understand
exactly what is happening there.
Actually I don't think that is accurate. The way rails works is it gets dispatched inside of a mutex. So if you set a class variable @@current_user and setup an accessor for it you can do it without the thread stuff. The main thing to remember is that you *must* set the current_user on *every* single request. This means a before_filter in application.rb that always either sets the current user to the correct user or sets it to nil if there is no current_user. Doing it this way is perfectly safe and I have been using it in production without issues for a while now. The way rails works you can assume that any given rails process(mongrel fcgi or whatever) will only serve one request at a time. So as long as you always set the current_user on each request you will be fine.
I thought so. Don't ask me why but storing class variables in the
session is the path to ultimate disaster. When the get unmarshaled all
sorts of weird things happen.
At least, that's what I've seen, but I didn't dig into it much.
Its is ok for just controller access where the session is available. What I was talking about was for pulling the current_user into your models from the session on each request so you could have a user audit trail.
Oh wait I see what you are saying. Yes don't do that. user the current_user accessor in your controllers and views. There is not need to set session[:current_user] directly in acts as auth. current_user caches the results so that if you call current_user more then once it only does the db lookup once per request.