getting session/controller objects available in a Model ???

<snip>

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”

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

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.current. With it, you can store a short-live variable value for the executing thread, and it is thread-save.

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

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 really hate this approach… but if you’re saying it’s a proper solution then I guess I’m cool with it.

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.

-Ezra

is the Thread method itself sound though?