Topic: Accessing Restful_Authentication data from a model

Is there a way to access #current_user from inside another model?

I am attempting to assign a 'created_by_user_id' field as part of a model callback function.

  def before_create
    self.created_by_user_id = current_user   #current_user is part of Restful_Authentication
  end

The code consistently throws the error "undefined local variable or method `current_user'"

All the methods in the AuthenticatedSystem module are protected, although two of them (#current_user and #logged_in?) are extended into the ActionView module.  I've fiddled with the AuthenticatedSystem module, attempting to make various things public (which causes session errors in migrations) and tried including AuthenticatedSystem in other modules.  Nothing I've tried has worked.

All other aspects of Restful_Authentication are working correctly.  Using Rails v2.0.2 and a recent version of RA.

Re: Accessing Restful_Authentication data from a model

Did you find a way of achieving this?

Re: Accessing Restful_Authentication data from a model

The error thrown at you is perfectly normal, understand Ruby and OOP before using Rails.

Re: Accessing Restful_Authentication data from a model

Yes we know it is normal, but what we want to know is how to access current_user from within another model. Is it possible?

Re: Accessing Restful_Authentication data from a model

Johnson wrote:

The error thrown at you is perfectly normal, understand Ruby and OOP before using Rails.

I've got a solid grasp of OOP (and how it applies to Ruby), but Rails is still a little new to me.  Even so, how does your response help me?  I've done the various changes that should work, such as including the RA library in the model, and tweaking which methods are public/protected/private.  The errors that result are not from Ruby but from Rails's MVC compartmentalization.

You've got to admit the Restful_Authentication plugin does some fancy stuff with Ruby and Rails that's beyond the average programmer.  It's primarily because of the advanced tricks (and the high regard that Technoweenie's plug-ins seem to have in the RoR community) that I'm surprised it isn't easier to access #current_user outside of a controller or view.

I've put this little task on hold for the past week, hoping that the community at large will be of assistance.  Could I really be the first person who wants to combine Restful_Authentication's #current_user with a model's "created_by" field?  (doubtful)

I'll get back to hacking this item next week.

Re: Accessing Restful_Authentication data from a model

I figured it out. Make current_user a global variable, by prepending $ the current_user. Like this:


$current_user

Re: Accessing Restful_Authentication data from a model

joelmoss wrote:

I figured it out. Make current_user a global variable, by prepending $ the current_user. Like this:


$current_user

That's more of a dirty trick typical from former php developpers.

To access the current_user attributes inside a model, you have to pass its attributes to a model's method such as:

in your controller: model_method(current_user.login, current_user.email)

then your model:

def model_method(login, email)
...
end

This is simply interface programming. You also need to understand the separation that occurs in the MVC pattern, and therefore why current_user which is a variable created in a controller is not available in the models.

By the way, the same applies to params...

Re: Accessing Restful_Authentication data from a model

joelmoss wrote:

I figured it out. Make current_user a global variable, by prepending $ the current_user. Like this:


$current_user

Unfortunately, this solution does not work with the Restful_Authentication plugin, because current_user is the name of a method and not a variable.  (Sure looks like a variable, though.)

(Even if I weren't using Restful_Authentication, this solution will pose problems in a multi-user environment.  What if a second user comes along and changes the global variable before the first user's process has finished?)

The root of the problem has to do with the way that models and controllers are segregated.  A model knows nothing about the calling controller.  In a multi-user environment, there could be multiple calls to the model at the same time - which instance of the controller has the current_user data that we're looking for?  The only way for controller data to get into a model is when those specific controller data items are passed into the model as part of a model.method call.

For example, the "easy" solution to my problem is to always pass the value of current_user.
Sample code snippets:

#--model
class Comment < ActiveRecord::Base
  def create(comment, user_id)
    #add the comment to the database and set `created_by` to user_id
  end
end

#--controller
class CommentController < ApplicationController
  def add
    Comment.create("sample comment text", current_user.id)
    #Remember - current_user is a method added by Restful_Authentication
  end
end


The problem with this technique is that it requires the programmer to overload the existing #create and #save methods so the user_id can be passed in and processed.  This becomes particularly egregious if you want to add created_by/updated_by in numerous models.  The result is a lot of duplicate code, a veritable slap in the face to the DRY (Don't Repeat Yourself) mantra of Rails.

All that being said, I may have found a solution that is good enough: setting a custom value in the Thread.current object.

Ruby has a Thread class that can be used to spawn tasks that can run independently of the main task.  The Thread class also has an object that tracks the current task, conveniently available via Thread.current.

Thread.current appears to be accessible from the moment a controller is accessed, through any model calls, and looks to vanish once the view has been rendered.  For the problem set forth in this thread, the key is "through any model calls".

On it's own, Thread.current primarily stores the id of the current thread.  But we can also add our own items to the Thread.current object.  The following snippet can add the user data of Restful_Authentication's current_user object to Thread.current:

Thread.current["user"] = current_user

You could stash nearly anything in Thread.current, and you're not limited to one item:
Thread.current["user"] = current_user      #attach the current_user data
Thread.current["start"] = Time.now         #attach the current time
Thread.current["favorite color"] = "blue"  #you get the idea...

Some initial testing of this theory has been positive.  I've added the following lines to my application controller:
  before_filter :set_thread_user
 
  def set_thread_user
    Thread.current["user"] = current_user  #current_user is part of Restful_Authentication
  end

"before_filter" will trigger when the application controller is accessed, before anything else happens in that particular controller.  It is calling the method "set_thread_user" (which ideally should live in a protected or private area of the class).  By sticking this code in the application controller, it will trigger whenever any controller is called.

And then the model has gained a couple of simple lines:

  def before_create
    self.created_by_user_id = Thread.current["user"].id
  end

  def before_save
    self.updated_by_user_id = Thread.current["user"].id
  end


This code will need to be inserted into any other models that also want to track created_by/updated_by.

The end result is that the model has access to Restful_Authentication's current_user data from the calling controller.

I freely admit that this code is not very robust, and I have not tested it thoroughly.  At this stage of the game, it's the best idea I've come up with and the proof-of-concept code appears to be working as desired.  I'll continue to tweak and test over the next couple of weeks.

Last edited by GG Crew (2008-06-09 05:25:21)

Re: Accessing Restful_Authentication data from a model

Thanks for suffering through my development pains.  Here's the compact solution that I'm testing:

(Again, this would be much easier if Restful_Authentication's #current_user method (or any instanced controller variable, for that matter) were available inside a model, but the "separation of church and state" of Rails's MVC framework essentially prohibits such crossover.)

lib/user_stamp.rb

module UserStamp
  def self.append_features(base)
    base.before_create do |base_class|
      base_class.created_by_user_id = Thread.current["user"].id if base_class.respond_to?(:created_by_user_id) && !Thread.current["user"].nil?
    end
    base.before_save do |base_class|
      base_class.updated_by_user_id = Thread.current["user"].id if base_class.respond_to?(:updated_by_user_id) && !Thread.current["user"].nil?
    end
  end
end

config/environment.rb
require 'user_stamp'

ActiveRecord::Base.class_eval do
  include UserStamp
end


app/controllers/application.rb
class ApplicationController < ActionController::Base
 
  include AuthenticatedSystem      # Restful_Authentication courtesy of Technoweenie
 
  before_filter :set_thread_user
 
  def set_thread_user
    # Attach Restful_Authentication's #current_user data to the current Thread
    Thread.current["user"] = current_user
  end

  ...  # the rest of your application controller

end


I'm not real excited about attaching data to Thread.current -- it just feels kludgy to me.  But it should work until someone comes up with a better solution.

Last edited by GG Crew (2008-06-09 14:18:39)

Re: Accessing Restful_Authentication data from a model

userstamp might be what you're looking for.

Re: Accessing Restful_Authentication data from a model

You can make current_user accessible to the User class and so to other models by adding this line to the User model:

cattr_accessor :current_user

Then in the application controller add:

before_filter :put_current_user_into_model

<other methods>

protected

  def put_current_user_into_model
    @user = User.find_by_id(session[:user_id])
    if @user
      User.current_user = @user
    end
  end


Then in any model where you need to get attributes of the current user you can use:

User.current_user

For example:
if User.current_user.login=="Bob"

Although people argue about giving models access to session information (current_user) you have to ask yourself where business logic belongs in the Model-View-Controller paradigm used by Rails. If you have business rules that restrict access to data based on something about the current user, then it's not unreasonable to say the models (which is where business logic belongs) should have direct access to the session (which contains business logic in this case).

Re: Accessing Restful_Authentication data from a model

Both the Thread.current and the User::cattr_accessor approaches (above) are predicated on Rails being single-threaded, right? Eventually that should change, and when/if that happens, it seems to me that the Thread.current approach would be easier to search out and fix. I mean, the name itself would be a red flag that that piece of code wasn't going to work!

Thread.current has the added bonus of automatically resetting itself with each new request.

[edit:] Not sure what I was thinking... Thread.current will work on multi-threaded rails (hence its name). But class variables won't. RE: threads in general, and subject of Thread.current being a hack, http://m.onkey.org/2008/10/23/thread-sa … your-rails (see discussion in comments).

Last edited by whatcould (2008-10-27 20:16:06)

Re: Accessing Restful_Authentication data from a model

Why can't I just get my User object by the following line of code within every control I want to?

@user = User.find_by_id(session[:user_id])

What is the problem with this solution? After logging in the current user id is stored within the session variable :user_id. Isn't it? So I can grep my user data every time and everywhere I need it.

Re: Accessing Restful_Authentication data from a model

Right, session[:user_id] works in controllers. But the idea is to access that data elsewhere, generally in a model, to do scoping or stamping. See, for example, the "def before_create" callback above.

You can pass in this from the controller to the model every time, but it gets tedious and repetitive. Hence the search for solutions.

Re: Accessing Restful_Authentication data from a model

Seems like Chris Bartlet's solution makes a lot of sense when you really do need heavy use of the current_user info in the business logic. For what you are doing though, you can pass in info like this within the controller

def create
   Model.new(params[:model].merge {:created_by_user_id => current_user.id})
   ...
end

This is quite a bit simpler than Chris Barlet's solution if all you need to do is just automatically set the value of such a field and not necessarily go through a bunch of logic with it.

The one problem I have encountered is what to do when you are writing tests. In such a case, it's not clear how you would create a new session within the test logic in order for those controller calls to current_user to return something other than nil. I started another post on this - http://railsforum.com/viewtopic.php?pid=111059#p111059 - if anyone has any ideas.

Last edited by metasoarous (2009-10-27 05:29:36)

Re: Accessing Restful_Authentication data from a model

It looks like I was the last person to post to this and it was so long ago now, I don't even remember what it was about. In any case, I will suggest that you use authlogic. I have found it to be way cleaner, I believe it is more Rails 3 compatible, and it doesn't have all of the nasty testing/speccing issues which Restful_auth seems to have. Plus, there is a nifty little utility within the gem that allows you to migrate over from an existing restful authentication solution to an authlogic solution.

Chris

Re: Accessing Restful_Authentication data from a model

joelmoss wrote:

I figured it out. Make current_user a global variable, by prepending $ the current_user. Like this:


 $current_user 

What a good idea.
Global vars are shared between threads. It means only ONE user can be authentified in your application at a time. Each time someone will refresh a page, it will override $current_user content.

Global vars are awful in all languages. Using it is very specific and respond to a particular problem.

"Global vars" in OOP are static properties of a class. A class Context wich extends Struct or OpenStruct is perfect for this.

This said, don't put your current_user in a Context class to use it in models. It will strongly bind your model to its context and that's not good for reusability.
Metasoarous's solution is the best. current_user has a sense only in controller. In model, it's just a user with a role.

Last edited by Mulasse (2011-01-20 08:30:03)