I'm following railstutorial by Michael Hartl. In chapter 8.2.2 he defines a variable #current_user and a method current_user.
app/helpers/sessions_helper.rb looks like this:
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
# Returns the current logged-in user (if any).
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
end
Hartl defines #current_user an instance variable (of User object I guess); How can #current_user be an instance variable if it is itself an instance of the User class?
This is such a good question. It seems that this arrangement is not something that it was planned for, it simply happened: u need to have a variable that has some kind of memory outside the method (scope), in order to compare if #current_user=#current_user, but at the same time the whole arrangement makes theoretically no sense.
This article was written in 2008 and was proof read from members of the rails core team. This paragraph is very telling of the situation:
http://www.railway.at/articles/2008/09/20/a-guide-to-memoization/
--> "A little note on naming here: Some people seem to prefer prefixing the memoizing variable’s name with an underscore to indicate that it’s not meant to be used as an actual instance variable. To be honest, I don’t think this is really necessary unless you define a whole bunch of instance variables and memoized variables."
The SessionsHelper module is mixed into your controllers, so #current_user will be set as an instance variable of the controller which is handling the current request (Rails creates a new controller instance to handle each request)
Related
I'm new to Ruby and Ruby on Rails, coming from a background of C-like languages.
Here is some code that I found in the application_controller.rb file:
def current_user
#current_user ||= Renter.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def authorize_user
redirect_to '/login' unless current_user
end
Here is what I don't understand about it:
- On line 4, is :current_user invoking the current_user instance method, or directly accessing the #current_user instance variable?
- On line 7, is current_user invoking the current_user instance method, or directly accessing the #current_user instance variable?
- On line 2, is :user_id a variable or is it more like a string literal being used as a key? Kind of like in JavaScript one might write session["user_id"] to get theuser_id property of the session object.
class methods aren't relevant in this example - they aren't being used here.
Instance variables will never call methods when they are get/set.
Although the opposite does sometimes happen. It's a very common pattern to create getter/setter methods for instance variables, so common that attr reader/writer/accessor helpers are defined in ruby core. If you write attr_accessor :foo, then foo= and foo will get/set the instance variable.
But this does not happen by default.
To answer your other question:
A symbol :user_id starts with a colon and is similar to a string. The difference between a symbol and a string may seem arbitrary, but it is an important concept in Ruby and making the distinction in your head is a good idea.
To respond to your comment:
Line 4, helper_method :current_user is really something specific to rails, consider it "rails magic" if you like. In effect this is making your current_user method callable from views (whereas by default it would only be available in the controller). :current_user is a symbol which is used to reference the current_user method. Not necessarily something you have to understand in total detail, it would suffice to know that helper_method takes a symbol with the same name as a method and makes that method available to views. As far as I'm aware, it's only relevant to Rails controllers.
It's somewhat common in Ruby to use symbols that refer to method names. It's a more intermediate concept. You can see another example in send:
def asd
return 0
end
class Foo
def instance_method_bar
return 0
end
def self.class_method_bar
return 0
end
end
# how the methods are typically called
asd
Foo.new.instance_method_bar
Foo.class_method_bar
# another way to call them, using send
send(:asd)
Foo.new.send(:instance_method_bar)
Foo.send(:class_method_bar)
I'm not recommending you use send unless you need to, but hopefully it will make it more clear how the symbol :current_user is being used in helper_method
Line 7 is the current_user method being called.
Let's tackle your questions one at a time.
On line 4, :current_user is a method, most likely used to return the current_user, so you can access username, or email, or whatever value the user has.
On line 7, it is still the same method. In this case, Ruby is checking whether a current_user object exists. You can think of unless as if not. So the code will redirect to login if current_user is false, which will happen if current_user == nil. If the user is logged in, current_user != nil, and the redirect does not happen.
On line 2, :user_id is a symbol, and session is a hash, which is key-value pair, such as { a: 1, b: 2 }, and you access the value with the key using the [] method, so session[:user_id] is returning the value of the user_id. In Ruby, you can use anything as the key, symbols are used because they are always unique, the object id of :two and :two is the same, whereas the id is different for "two" and "two".
A common pattern is to use current_user in many places, but check whether it is set.
if current_user
#your code
end
But instead of injecting an if check just about every time you want to use current_user, how and where can you wrap the current_user method in a different method ONCE, so that the you won't have to deal with your code breaking due to a nil value for devise's default current_user method?
The current_user method is added to ApplicationController, then I think you can override it in ApplicationController doing somethig like:
# in application_controller.rb
alias_method :devise_current_user, :current_user
def current_user
if ...#your validation
devise_current_user # || User.new # <-- or whatever other non-nil result
end
end
Creating a custom classes that have the methods you are trying to use on current_user throughout your application is one way.
http://littlelines.com/blog/2013/06/22/how-to-guard-against-ruby-nil-errors/
If you do not want to account for nil value possibilities while working ruby, you're going to be swimming upstream, and become very frustrated. I know I've had similar feelings. It can be really annoying to write:
if current_user && current_user.attr == 'val'
But I've developed ways to make that less awkward over time...
--edit to show some of the things I do to avoid this--
I often do some of these things. This isn't necessarily a recommendation, or a best practice, but I find that it makes my code less verbose and more readable sometimes
If I expect an array, but the array might be nil:
my_array ||= []
That way I can safely do things that require array-ness, usually when this is needed in various places in the scope.
my_array.size
and not have things choke.
In a similar way I might do
car ||= Car.new
Then I can treat car like any car object. But I probably wouldn't do this with current_user, though.
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
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).
I am going through the great Michael Hartl tutorial to build ruby app here.
I am trying to understand the concept of how to create a session and I am stuck in understanding this line:
self.current_user = user
in this method:
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
end
I understand the whole concept of creating a cookie with the user_token.
But I don't understand what does self.current_user = user means and why is it even necessary to keep this line of code - I have the cookie with the token - why do I need to know the current user?
Also, where does this "self" is being stored - it is not like a flash[:success] parameter I can see in one of my views. so I don't understand where it is.
there are also these 2 methods in the same module:
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
And still I am trying to connect the dots of the purpose for this mysterious current user - is its purpose is to create #current_user global variable to use in the views?
If so - why there are there these 2 duplicated functions def current_user=(user) and def current_user
A few things.
First, you're reading the method names wrong (which is not surprising given how cryptic ruby method naming can be). def current_user=(user) is actually read as defining the method current_user= that takes an argument user, whereas def current_user defines a method current_user that takes no arguments. These are referred to respectively as setters and getters.
Here's a reference: Ruby (programming language): What are setters and getters in Ruby?
So that explains the duplication. On to your next question.
I don't understand what does self.current_user = user means
self is a topic unto itself, worthy of its own discussion, so I won't even try to explain it (here's one reference out of many). For the purposes of this question it's just important to remember that in order to set instance variables, you need to prefix your assignment with self, even within the class (where for other purposes it would be implicit). The rest of the line is a call to the current_user= setter method I mentioned above, with the argument user.
why is it even necessary to keep this line of code - I have the cookie with the token - why do I need to know the current user?
The reason it's necessary is that you don't want to be looking up the user from the token every time you need to get the current user. Take a look at the getter method:
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
What this says is: if I haven't looked up and set the instance variable #current_user yet, then look it up; if I have already set it, then just return it. That saves a lot of looking up.
I think that answers your questions. There are a lot of deeper issues (self, etc.) which you can find more information about elsewhere. Here's one discussion of why you need to include self in setters on SO: Why do Ruby setters need "self." qualification within the class?
UPDATE: Small clarification, that last link about using self for setters within the class is actually a bit off-topic, since you're calling it within a module and not directly from a class. In the context of a module, the self in self.current_user = user will become the class that the module is included inside of, e.g. User.current_user if it was called within the class User, etc. Again, another topic of discussion unto itself...
The method def current_user=(user) is basically a setter that the sign_in method uses in order to set the current_user.
def current_user will return the #current_user or if it is not set it will find it in the Users table by the remember_token. This basically allows you get the current_user at any point in time.
self.current_user in the context of the sign_in method will refer to the calling class or module in this case. It will be calling current_user from the Session Helper module.