I am having trouble understanding the implementation of "Remember Me" in the Ruby on Rails Tutorial by Michael Hartl. He creates a SessionsHelper module with methods for signing in, containing the following:
module SessionsHelper
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
current_user = user
end
def current_user=(user)
@current_user = user
end
def current_user
return @current_user ||= user_from_remember_token
end
private
def user_from_remember_token
#passes array of length two as a parameter -- first slot contains ID,
#second contains SALT for encryption
User.authenticate_with_salt(*remember_token)
end
def remember_token
#ensures return of a double array in the event that
#cookies.signed[:remember_token] is nil.
cookies.signed[:remember_token] || [nil,nil]
end
end
NOTE: The authenticate_with_salt
method in the User model finds the user by the first parameter (the id) and if the user is defined and its salt is equivalent to the second parameter (the salt) then the user is returned, otherwise nil is returned.
I am having trouble understanding why we go to such lengths to test if the user has already been signed in:
In the event that the user is signed in, @current_user
is already defined by the sign_in
method and therefore the ||= in the current_user
method is meaningless.
In the event that the user is not signed in, the ||= operator in the current_user
method returns the value returned by the user_from_remember_token
method, but since cookies.signed[:remember_token] would be nil, User.authenticate_with_salt
would be passed the [nil,nil] argument and would return nil, and therefore, the current_user
method would return nil.
In short, if the current_user
method is returning @current_user if it is defined and nil otherwise, wouldn't it be much simpler to just use the conventional accessor method:
def current_user
return @current_user
end
Michael Hartl's book says that doing this would be useless because the user's sign in status would be forgotten. Why would that be the case??? Can someone please explain why we do not do this and instead use the much more intricate version posted above?
The line
return @current_user ||= user_from_remember_token
is a form of lazy initialization to avoid initializing the @current_user
variable until it is actually needed. @current_user
will never be initialized the first time this function is called, but it will have a value on each successive call (assuming user_from_remember_token
returns something other than nil).
He could have written
def current_user
return user_from_remember_token
end
This code will always initialize the current user to whatever it reads from the cookie. This would behave correctly, but he probably wanted to avoid repeatedly reading the cookie, so he does it once and stores it in a variable.
He cannot do
def current_user
return @current_user
end
because the @current_user variable does not persist across page requests. After the page renders and is sent back to the client, the @current_user variable is destroyed and its value is forgotten.
Hope this helps. Look into maintaining state in web applications for more information on why it's necessary to jump through these hoops.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With