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)