ActiveRecord callback access to session data - ruby-on-rails

I am trying to add current user information to record using ActiveRecord callback, but I don't see way how to do that.
I tried Thread.current[:user], but in results I see that thread value is accessed from another user sessions.
I am using Passanger in production, but in the same time I am using acts_as_audited who get user value correctly.
Whats the best/safest way how to do that?

The current user is not accessible from within an ActiveRecord model. This is due to the separation of concerns in Rails--the current user and the session are concepts that pertain to the realm of the controller.
You need to get the relevant data and pass it into the model in order for your callback to work. One way to do this is with an accessor method:
# model
attr_accessor :current_user
def my_callback
# do something with current_user
self.some_attribute = current_user
end
# controller
#model = MyModel.find(params[:id])
#model.current_user = current_user # assuming you have a controller method that does this
#model.save!
You should rename the current_user accessor to be meaningful: eg. if you are tracking the author of a blog post, call it author or responsible_user or some such, since current_user means nothing once the model is saved.

Related

Using current user in Rails in a model method

I'm currently trying to implement simple audit for users (just for destroy method). This way I know if the user has been deleted by an admin or user deleted itself. I wanted to add deleted_by_id column to my model.
I was thinking to use before_destroy, and to retrieve the user info like described in this post :
http://www.zorched.net/2007/05/29/making-session-data-available-to-models-in-ruby-on-rails/
module UserInfo
def current_user
Thread.current[:user]
end
def self.current_user=(user)
Thread.current[:user] = user
end
end
But this article is from 2007, I'm not sure will this work in multithreaded and is there something more up to date on this topic, has anyone done something like this lately to pass on the experience?
Using that technique would certainly work, but will violate the principle that wants the Model unaware of the controller state.
If you need to know who is responsible for a deletion, the correct approach is to pass such information as parameter.
Instead of using callbacks and threads (both represents unnecessary complexity in this case) simply define a new method in your model
class User
def delete_user(actor)
self.deleted_by_id = actor.id
# do what you need to do with the record
# such as .destroy or whatever
end
end
Then in your controller simply call
#user.delete_user(current_user)
This approach:
respects the MVC pattern
can be easily tested in isolation with minimal dependencies (it's a model method)
expose a custom API instead of coupling your app to ActiveRecord API
You can use paranoia gem to make soft deletes. And then I suggest destroying users through some kind of service. Check, really basic example below:
class UserDestroyService
def initialize(user, destroyer)
#user = user
#destroyer = destroyer
end
def perform
#user.deleted_by_id = #destroyer.id
#user.destroy
end
end
UserDestroyService.new(user, current_user).perform

Rails/devise current_user accessed in model but it's nil

I needed to access the current_user in my another model. I googled and found a few solutions and tried this.
But just one difference: I didn't set the User.current_user in the controller. WHY?
Like in the answer I followed I'm not adding data to my second model from views but by rendering data via a url (with open-uri, it's csv data I'm fetching).
So in my second model I do:
Person.create(username: User.current_user.username)
It gives:
NoMethodError: undefined method `username' for nil:NilClass
which isn't working (which is obvious I guess). In console when I do User.current_user it shows:
1.9.3p448 :002 > User.current_user
=> nil
I think why it isn't working is because I'm accessing the User.current_user directly from model and model cannot get the current_user unless it is given that. (right?)
But this would definitely work if I access it via a login page and set the User.current_user in the controller.
But as I'm directly fetching the data from url, I'm directly making new entries for my Person model in model itself.
So how do I set the User.current_user?
Is there any workaround for this? Edit's for the question's title are required.
current_user is available by default as a helper method within Devise. From the documentation:
# For the current signed-in user, this helper is available:
current_user
The current_user isn't accessed directly via the model, per se, but rather, though the Devise module, which looks up the User object that is logged into the current session, and then returns to current_user.
Thus, the current user isn't accessed via User.current_user (there is no current_user method on User, as the error message is saying). You access it purely by invoking the current_user helper method within a controller or view.
UPDATE:
You're well advised to keep your controller and model layers separate. One way of doing what you've proposed is to create a class function on the Person model wherein you explicitly pass the username of your User object from within your controller:
# in your controller
Person.create_with_username(:username => current_user.username)
# app/models/person.rb
def self.create_with_username(username)
self.create(username)
end

Rails scope model data by user_id

I have a Rails application where a user longs in and I have the user_id in the session. The next step is to create a scope for all model data shown to the user where data.user_id = session[:user_id].
I know I can do the following in each of my controllers
Controller.find_all_by_user_id(session[:user_id])
Yet to me it seems there is probably a better solution. I found the possibility to add a scope to the model, yet the session is not known here and MVC pattern wise it is probably not a good idea to have it there. Is there a solution to apply such a user_id restriction to all data coming from the models or should I just use the find_all_by_user_id for every controller function that has userdata in it?
If I understand you correctly you want to access some data by user_id. Which means that you can define relationship in the user model as has_many :this_and_that or something like that. It it is right, then you can create a before_filter or even better a function in your application controller in which you get your current user instance. Trough this instance, you can access all available data to that user. You can even make that function a helper function, and you can use that in a view.
#User.erb
has_many :other_data
#ApplicationConroller.erb
def current_user
#current_user ||= User.find_by_id(session[:user_id])
end
#OtherControllers
def index
#other_datas = current_user.other_datas
end
You can have before_filter :load_user_data, :if => current_user or something like that.
Method will look like that:
def load_user_data
#data = ModelName.where(user_id: session[:user_id])
end
#data will be ActiveRecord::Relation and will be chainable. Also using find_all_by_user_id in each controller not that bad, if you really need it.

where's the appropriate place to handle writing the current_user.id to an object

I am not using Devise but have implemented a simple authentication scheme (basically outlined here http://railscasts.com/episodes/250-authentication-from-scratch) with the relevant part being here:
application_controller.rb
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
I have a list of assets that a user must be authorized to add. I am using paperclip. A user can has_many and a asset belongs_to a user (although this is essentially irrelevant to where it is assigned since my asset model is polymorphic for different assetable_types).
Where should I assign the current_user id to an asset? I would think in the model; maybe I should do a default_values using the session[:user_id] but that seems to be kinda ugly.
Also, these are nested_attributes and the models that these are nested to, currently don't know anything about the user. So really the source of information for the current_user isn't part of the current association.
thx
edit 1
should I create an instance of a User based upon the session[:user_id] value or just push it in?
If I understand your question correctly, why not assign the user to the asset in whichever controller first finds out that the asset belongs to the user? It's the controller's responsibility to translate web requests (including the session / current user) into something applicable to the model.

Setting a class variable for finding current user

I need to find the current logged in user in my model.
I defined cattr_accessor current_logged_in in User model.
Now, when a user logs in I set User.current_logged_in = current_user.id.
Later, in other models I access the variable using User.current_logged_in_user. As of now it works.
Is it the right way to implement this?
A good way to implement it, unless you're just doing your code as a learning exercise is to use a plugin like devise.
That said, you should avoid accessing the current_user in models. current_user is a session thing and should not be tied to the model. Instead pass in the current_user as a parameter to methods in the model. Something like:
def can_delete_item(user)
if user.is_admin?
....
else
.....
end
.....
end

Resources