Thread-safe Rails controller actions - setting instance variables? - ruby-on-rails

I have to write a threaded Rails app because I am running it atop of Neo4j.rb, which embeds a Neo4j graph database inside the Rails process, and thus I have to serve multiple requests from the same process. Yeah, it'd be cool if connecting to a Neo4j database worked like SQL databases, but it doesn't, so I'll quit complaining and just use it.
I'm quite worried about the implications of writing concurrent code (as I should be), and just need some advice on how to handle common a common scenario - a controller sets an instance variable or a variable in the session hash, then some stuff happens. Consider the following crude code to demonstrate what I mean:
# THIS IS NOT REAL PRODUCTION CODE
# I don't do this in real life, it is just to help me ask my question, I
# know about one-way hashing, etc.!
class SessionsController
def create
user = User.find_by_email_and_password(params[:email], params[:password])
raise 'auth error' unless user
session[:current_user_id] = user.id
redirect_to :controller => 'current_user', :action => 'show'
end
end
class CurrentUserController
def show
#current_user = User.find(session[:current_user_id])
render :action => :show # .html.erb file that uses #current_user
end
end
The question: Are there any race conditions in this code?
In SessionsController, are the session hash and the params hash thread-local? Say the same browser session makes multiple requests to /sessions#create (to borrow Rails route syntax) with different credentials, the user that is logged in should be the request that hit the line session[:current_user_id] = user.id last? Or should I wrap a mutex lock around the controller action?
In the CurrentUserController, if the show action is hit simultaneously by two requests with different sessions, will the same #current_user variable be set by both? I.e. will the first request, as it is processing the .html.erb file, find that it's #current_user instance variable has suddenly been changed by the second thread?
Thanks

Each request gets a new instance of your controller. As a consequence controller instance variables are thread safe. params and session are also backed by controller instance variables (or the request object itself) and so are also safe.

It's important to know what is shared between threads and what isn't.
Now back to your specific example. Two requests hit CurrentUserController#show simultaneously, hence they are handled by two concurrent threads. The key here is that each thread has its own instance of CurrentUserController, so there are two #current_user variables which don't interfere. So there's no race condition around #current_user.
An example of race condition would be this:
class ApplicationController < ActionController::Base
before_each :set_current_user
cattr_accessor :current_user
def set_current_user
self.class.current_user = User.find_by_id(session[:current_user_id])
end
end
# model
class LogMessage < ActiveRecord::Base
belongs_to :user
def self.log_action(attrs)
log_message = new(attrs)
log_message.user = ApplicationController.current_user
log_message.save
end
end
On more general note, because of GIL (Global Interpreter Lock) benefits from using threads in MRI ruby are rather limited. There are implementation which are free from GIL (jruby).

Related

Explanation of User.new in Ruby on Rails? (From Michael Hartl's tutorial)

I have searched everywhere to try to find an explanation of how this works/what its purpose is, but I cant find anything helpful.
I am doing Michael Hartl's tutorial, and my question is mainly about the two actions: 'new' and 'create'.
The new action has the following:
def new
#user = User.new
end
In the view corresponding to the 'new' action, there is a form_for helper, where users can type in their attributes and hit submit. As expected, the beginning of the form_for helper looks like this:
form_for(#user)
However here is where I am stumped... In the create action, there is the following code:
def create
#user = User.new(user_params)
#user_params is a function we defined which simply returns the permitted params from the user.
What is the purpose of #user = User.new in the 'new' action? What does User.new even accomplish? I am assuming that the instance variable #user is necessary to pass to the form, but in that case, why do we have to redeclare an #user isntance variable in 'create'? Isn't it sufficient to have only #user = User.new(user_params) in our 'create' action? Is the User.new somehow necessary to make the form function properly?
I am mainly just trying to figure out what #user = User.new accomplishes in our 'new' action and its corresponding 'new' view (with the form), and why it is necessary when we have a 'create' action which actually CREATES the object. ANY help is SO GREATLY APPRECIATED. Thank you all for always doing your best to explain. Thank you ahead of time to anyone who answers this.
The new and create are different actions. New is called when you get the new route. Create is called when you post to the new route. So, you have to create the user in new so they're available in the form. You have to create the user with the form contents in create so you can save it to the database.
You can't assume that the request to new will go to the same rails instance as the request to create. It's common to run multiple instances of your app behind a proxy.
It's called object orientated programming
HTTP
In Ruby, each variable you define is an object. These objects are then manipulated throughout each instance of the app.
In traditional (stateful) applications, your computer is able to store a number of objects in memory, and since your application is always in state, you'll be able to manipulate them from a single invocation.
In HTTP (stateless) applications, you have to rebuild the objects with each call. Because your application doesn't retain state (memory) between each request, you have to build the objects again.
This is why Rails "variables" are called with a class function on the model (class): User.find ...
--
Thus, when using the following:
#app/controllers/your_controller.rb
class YourController < ApplicationController
def new
#user = User.new #-> invokes a new user object
end
def create
#user = User.new user_params #-> invokes a new user object & populates with your params
#user.save #-> "saves" the new record
end
def show
#user = User.find params[:id] #-> stateless means you have to rebuild the object again
end
end
... what you're doing is rebuilding the object each time your actions are invoked.
This is one of the pitfalls of using HTTP - your server is "dumb" and cannot retain state between requests. Although Rails does a great job at making it a seamless process, it can be difficult if you haven't got your head around it yet.
Most generally, users enter data and we programmer-types traditionally store it in a relational database.
This creates an "impedance" between a relational model (i.e., tables and rows) and an object-oriented one (roughly, classes and instances).
ORMs like ActiveRecord help abstract much of this tedium, and in this way model instances--such as those we're creating in controller actions--serve as helpful containers for data.
This lets us easily represent models in views when gathering user input, and bind inputs to model attributes when persisting it (basic CRUD).
The separate controller actions merely represent these two steps in the process, as any Web-based app ultimately speaks HTTP.
This is really the whole benefit and genesis of Rails and similar MVC frameworks, born in a time of relational databases and server-side rendering. (Though they are increasingly coping with and adapting to an environment that now includes document/object-oriented databases and client-scripted front-ends.)

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

Setting current_user in pg_audit_log

I'd like to use pg_audit_log for logging in a rails app. The audit log must not only show the columns that have changed, but also the user who made those changes. The docs don't show how to do this, but after looking through the pg_audit_log source (postgresql_adapter.rb) I see it reads user information from a thread local variable, ala:
current_user = Thread.current[:current_user]
I've considered setting/unsetting this in before and after filters like so:
Thread.current[:current_user] = current_user
(using the current_user helper method in the controller to get the currently logged in user), but that seems dangerous. I'm now spending time trying to understand how the rails request cycle and threads interact, to get a better feel for just how dangerous. In the mean time, I was curious if any SO users currently using pg_audit_log have solved the need to log the user_id and user_unique_name to the log tables each time the user makes a change to a record.
Setting the current user the way you describe is a common way to do it. See, for example, http://rails-bestpractices.com/posts/47-fetch-current-user-in-models
Some example code could look like:
# in your model
class User < ActiveRecord::Base
def self.current
Thread.current[:current_user]
end
def self.current=(user)
Thread.current[:current_user] = user if user.nil? || user.is_a?(User)
end
end
# in your controller
class ApplicationController < ActionController::Base
before_filter :set_current_user
def set_current_user
User.current = user_signed_in? ? current_user : nil
end
end
Relying on the Thread.current hash to provide model-level access to objects managed by the controller is indeed controversial. For example, see the following:
Safety of Thread.current[] usage in rails
It is worrisome that this particular feature is undocumented in the pg_audit_log gem.
Suppose you had not actively explored the gem's source code, and suppose you had independently decided to define Thread.current[:current_user] = something in your own application, for your own purpose. In that case, pg_audit_log would audit that object, without your knowledge.
Granted, the name current_user is so universally accepted to mean the currently logged-on user as defined by authentication routines that it's difficult to imagine this potential bug as a concrete problem, but from a design standpoint? Ouch.
On the other hand, since you know what you are doing, ensuring that Thread.current[:current_user] is set/unset at the beginning/end of each and every response cycle should make the process safe. At least that's what I gather from reading lots of posts on the topic.
Cheers, Giuseppe

Why are my thread variables intermittent in Rails?

I have the following in my application controller:
before_filter :set_current_subdomain
protected
def set_current_subdomain
Thread.current[:current_subdomain] = current_subdomain
#account = Account.find_by_subdomain(current_subdomain)
end
def current_subdomain
request.subdomain
end
and then the following in some of my models:
default_scope :conditions => { :account_id => (Thread.current[:account].id unless Thread.current[:account].nil?) }
Now, this works - some of the time. I for instance load up an index method and get back a list of records with the scope applied, but also sometimes get an empty list as Thread.current[:account_id] is coming out as nil, even though queries earlier in the request are working using the same value.
Question is, why is this not working, and is there a better way to set a variable that's global to the current request?
Manipulating the Thread local variables is a really bad idea and is going to lead to nothing but sadness, heartache, and pain. There's no guarantee that different parts of the request processing will be handled by the same thread, and because of this, your variables might end up getting lost.
The Rails convention is to create instance variables in the context of ApplicationController. In simple terms, all you really do is this:
class ApplicationController < ActionController::Base
before_filter :set_current_subdomain
attr_reader :current_subdomain
helper_method :current_subdomain
protected
def set_current_subdomain
#current_subdomain = request.subdomain
#account = Account.find_by_subdomain(#current_subdomain)
end
end
Any #... type variables you create will be attached to the instance of the ApplicationController associated with the current request. It's important to note that each request will be issued a brand-new instance of the appropriate controller class.
You're free to create whatever instance variables you want provided they don't somehow conflict with those used by Rails itself but in general terms this doesn't happen very often and conflicts typically occur on method names instead.
Class-level instance variables will persist between requests in environments where the "cache classes" flag is enabled. In the development environment your controller class is re-loaded each time a request is made to ensure it reflects the current state of your source files.

Rails 3 ActiveRecord validation based on user permissions

I'm shifting code from an application built in a non-standard custom PHP framework into Ruby on Rails (version 3). In the PHP version all the controllers are really fat, with thin models, which I've always disagreed with, so I'm enjoying the way Rails does validation at the model level, which is probably 90% of what's happening in these fat controllers currently.
One problem I'm facing, and unsure how to resolve however, is that of differing validation rules based on who's making the change to the model. For example, an administrator, or the original creator of the record should be able to do things like flag a record as deleted (soft delete) whereas everybody else should not.
class Something < ActiveRecord::Base
...
validates :deleted, :owned_by_active_user => true
...
end
class OwnedByActiveUserValidator < ActiveModel::EachValidator
validate_each(record, attr_name, attr_value)
# Bad idea to have the model know about things such as sessions?
unless active_user.admin? || active_user.own?(record)
record.errors.add :base, "You do not have permission to delete this record"
end
end
end
Since the model itself is (in theory) unaware of the user who is making the change, what's the "rails way" to do this sort of thing? Should I set the active user as a virtual attribute on the record (not actually saved to DB), or should I just perform these checks in the controller? I have to admit, it does feel strange to have the model checking permissions on the active user, and it adds complexity when it comes to testing the model.
One reason I'm keen to keep as much of this as possible in the model, is because I want to provide both an API (accessed over OAuth) and a web site, without duplicating too much code, such as these types of permissions checks.
It is really the controller's job to handle authorization, or to delegate authorization to an authorization layer. The models should not know about, nor have to care about, who is currently logged in and what his/her permissions are - that's the job of the controller, or whatever auth helper layer the controller delegates that to.
You should make :deleted in-attr_accessible to mass assignment via new, create, or update_attributes. The controller should check the authenticated user's authorizations separately and call deleted= separately, if the authenticated user is authorized.
There are several authorization libraries and frameworks to help with authorization or to function as an authorization layer, such as cancan.
I would solve this with a before_filter in my controller, instead of with validations in my model.
class SomethingController < ApplicationController
before_filter :require_delete_permission, :only => [:destroy]
def destroy
# delete the record
end
private
def require_delete_permission
unless current_user.is_admin || record.owner == current_user
flash[:error] = 'You do not have delete permissions'
redirect_to somewhere
end
end
end
I have come across the same issue in Rails 2.3 and finally come up with this solution. In your model you define some atribute, depending on which you switch on/off validation. Than you your control you set this attribute depending on the date available to controller (such as user privileges in your case) as follows:
Class Model < ActiveRecord::Base
attr_accessor :perform_validation_of_field1 #This is an attribute which controller will use to turn on/off some validation logic depending on the current user
validates_presence_of :field1, :if => :perform_validation_of_field1
#This validation (or any similar one) will occur only if controller sets model.perform_validation_of_field1 to true.
end
Class MyController < ActionController::Base
def update
#item = Model.find(params[:id])
#item.update_attribute(params[:item])
#The controller decides whether to turn on optional validations depending on current user privileges (without the knowledge of internal implementation of this validation logic)
#item.perform_validation_of_field1 = true unless active_user.admin?
if #item.save
flash[:success] = 'The record has been saved'
redirect_to ...
else
flash.now[:error] = 'The record has not passed validation checks'
render :action => :edit
end
end
I think that in Rails 3 it can be done in similar manner.

Resources