What’s the recommended way of getting session information to be availabe
in a model (or each model)?
For acts_as_audited I need a way to have access to a user object from my
model *.rb files. i.e. acts_as_audited adds methods etc into the model
objects and one of the aspects of the auditing is that it can audit the
USERID of the user who made the changes, however you need to specify
within the model.rb files the name of the User object/method where it
can get this information. So I’m trying to work out how to construct an
object (or is it class?) to use for the plugin.
============================
Just add the acts_as_audited declaration in any of the models that you
want to have audited.
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.
>
> 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”
----------------------------------------
class ContactsController < ApplicationController
before_filter { Suberb.current_user_gregs “test_user_it_worked” }
<>
end
----------------------------------------
class Contact < ActiveRecord::Base
a = Suberb.current_user_gregs() <=== ERROR IS HERE
end
----------------------------------------
class Suberb < ActiveRecord::Base
def self.current_user_gregs=(user)
@@current_user_gregs = user
end
<>
end
----------------------------------------
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.
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();
}
}
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.
concurrent thread executions may provides current_user value with
shared
Cheers,
Max
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.
gets dispatched inside of a mutex. So if you set a class variable
-Ezra
Ezra, I’ve been setting
session[:current_user] = current_user
right inside the acts_as_auth plugin’s login method and just using
session[:current_user] instead of the current_user variable throughout
the app. Is there a down-side to this method? Is it less secure?
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.
Actually I don’t think that is accurate. The way rails works is it
current_user on each request you will be fine.
jp
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.
concurrent thread executions may provides current_user value with
shared
Cheers,
Max
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.
-Ezra
Ezra, I’ve been setting
session[:current_user] = current_user
right inside the acts_as_auth plugin’s login method and just using
session[:current_user] instead of the current_user variable throughout
the app. Is there a down-side to this method? Is it less secure?
thanks,
jp
This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.