Getting session/controller objects available in a Model?

Hi,

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.

Extract from doco on plugin: (Reference =
acts_as_audited - Brandon Keepers)

============================
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.

acts_as_audited :user_class_name => ‘Person’, :user_method =>
‘current’

============================

Thanks

> > 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

Cheers,
Max

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”

Can a ruby guru help me out here please :slight_smile:

Thanks

Greg H wrote:

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.

Does this help?
Jeff

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)

Thanks heaps

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
----------------------------------------

tKS

Tks Max, how about this however, which I just come across -
http://www.pluitsolutions.com/2006/08/15/rails-auto-assign-created-by-and-updated-by/

In brief they say
========extract==============

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.currenthttp://corelib.rubyonrails.org/classes/Thread.html#M001131.
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?

Tks again
Greg

On 9/7/06, Greg H [email protected] wrote:

<<cut>>
<<CUT>>

end

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();
}
}

Max

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.

Cheers,
Max

On Sep 7, 2006, at 3:14 PM, Max M. wrote:

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

On Fri, 2006-09-08 at 03:51 +0200, Jeff P. wrote:

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.


Zed A. Shaw

http://mongrel.rubyforge.org/
http://www.lingr.com/room/3yXhqKbfPy8 – Come get help.

@Ezra:

I really hate this approach… but if you’re saying it’s a proper
solution
then I guess I’m cool with it.

On Sep 7, 2006, at 6:51 PM, Jeff P. wrote:

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.

-Ezra

is the Thread method itself sound though?

Ezra Z. wrote:

On Sep 7, 2006, at 3:14 PM, Max M. wrote:

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