Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails -- "remember me" capability

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?

like image 356
Kvass Avatar asked Oct 11 '22 16:10

Kvass


1 Answers

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.

like image 65
Michael Venable Avatar answered Nov 01 '22 21:11

Michael Venable