"current_user" variable scope confusion following RoR tutorial - ruby-on-rails

Note: before I started the tutorial at railstutorial.org, I completed codecademy's Ruby tutorial.
I'm following Michael Hartl's Ruby on Rails tutorial. I'm on chapter 8, and I'm on the sessions_helper.rb file. There's a variable called "current_user". I learned about scope when following the Ruby tutorial, but now I'm having trouble applying what I learned.
In the tutorial, there is the following code:
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.digest(remember_token))
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def current_user=(user)
#current_user = user
end
def current_user
remember_token = User.digest(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
end
In particular, what I don't understand is the difference between
a) self.current_user
b) #current_user
c) current_user
I have an understanding of global, instance, and local variables in concept, but not in application. If I were to code this by myself without the training wheels of the tutorial, I wouldn't know when to use which. Could somebody help me out here?

current_user if you do not explicitly declare a local variable, it will look for a method in the current scope, in this case your def current_user method.
#current_user is an instance variable. One of Rails magics is to copy all the instance variables from the controller into views. In this case, it probably comes from your controller or ApplicationController, which it probably inherits from.
self.current_user is the same as current_user, except that self.current_user= in your code means you're using the method declared right below it instead of assigning a local variable.
i.e.
current_user = "pedro"
current_user # "pedro"
self.current_user # method called
In case of not declaring a local variable, this is what happens with current_user:
current_user # method called
self.current_user # same method called

Related

In Hartl's Rails tutorial (Rails 5, section 8.2.3), why can an instance variable defined in a method be called without the #?

I'm a newbie working my way through Hartl's Rails 5 tutorial when I came across this piece of confusing code listed below. It looks like #current_user is being defined within a method as an instance variable, so that it may be used outside the method. However, in the logged_in? method, no # symbol is needed to call on it. In fact, if I add rewrite logged_in? to use #current_user, it no longer works as intended!
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
if session[:user_id]
#current_user ||= User.find_by(id: session[:user_id])
end
end
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
end
When I try this in ruby, I'm unable to use an instance variable in this way. The example I tried is below. When I call method2, I get an error that isn't fixed until I add an # symbol in front of var1. Can anyone help me understand this? Thanks!!
def method1
#var1 = 2
#var1
end
def method2
#var1 == 2
end
puts method1
puts method2
current_user is being defined as a method, and then inside the method, an instance variable #current_user is defined, which is only used inside the method. The logged_in? method is calling the current_user method, it is not accessing the #current_user variable directly. Note that in ruby, the #, $, or ## are actually part of the variable name, and are not modifiers. Therefore, current_user and #current_user are not the same entity.

Confusion about SessionsHelper, specifically the current_user method?

I am learning Rails from Michael Hartl's tutorial, but I am really confused about the SessionsHelper module. Not enough information is provided about the duplication of the current_user method. Could someone explain why there are two and what are their individual purposes?
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
end
I understand the sign_in method would trigger the call to current_user=(user) but why the current_user method again? I understand the second method gets the user based on remember_token from the database, but I can't connect the dots regarding these.
current_user is the reader method and current_user= is the writer method for your attribute current_user. These two are separate methods, one would be used to read the value and other to write value of current_user.
EDIT
In your case, current_user= method means that set the value of instance variable #current_user equal to the passed value(user).
def current_user=(user)
#current_user = user
end
current_user method means,
def current_user
## if #current_user = nil then
## set #current_user = User.find_by_remember_token(cookies[:remember_token])
## else return #current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
sign_in method,
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
sign_in method would be called as sign_in user from a controller.
Like Aaron mentioned in the comment, without self, Ruby would simply create a local variable called current_user which would be lost once sign_in method finishes execution.
But by saying, self.current_user = user the value of current_user would be preserved in the current instance(instance of controller in which you have included the module) of class.
For a better understanding of self refer to this Stackoverflow question on self

Use of current_user setter in Ruby on Rails Tutorial by Michael Hartl?

I'm a little confused here. In chapter 8 of the fabulous Ruby on Rails Tutorial by Michael Hartl, I can't figure out why Michael uses a setter to set the instance variable #current_user to user.
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
self.current_user = user
end
def current_user=(user)
#current_user = user
end
end
Why did he not just skip the setter method and do it this way instead?
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
#current_user = user
end
end
Is it just because it's best practice to implement a setter or I don't get something?
This is just an OO question. Because the current_user= is part of the session's contract with the world. It is promising that, no matter where the user is ultimately stored (such as in User.current_user), you can always set it thru that interface.
I haven't read it, but he could be
Making a point
Following best practice
Intending to use the setter elsewhere, earlier or later in the tutorial
I am pretty sure he reuses that method later when the user loads another page. Something like:
# in application_controller
before_filter :authenticate
private
def authenticate
if cookies[:remember_token]
self.current_user = User.authenticate(cookies[:remember_token])
end
end
# in user.rb
def self.authenticate(token)
find_by_remember_token(User.encrypt(token))
end

Signing in a User (Ruby on Rails Tutorial Ch. 8)

Why is the middle method necessary? It seems to me like it's just an intermediary step to connect the first and third methods.
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
end
It is the setter method or the helper method to set the current_user with the user who is currently online. This is used by devise gem to identify the current user.
So whenever you need to find the online user for your application, you just use the following code-
if current_user
#Do something important
else
#You do not have enough privileges. Please login.
#Your offline stuff
end

Rails Tutorial — 9.3.3 Current_User

So I'm following the Rails Tutorial, and I've gotten to the portion where we want to sign a user in with a sign_in SessionHelper.
Question 1:
module SessionsHelper
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
current_user = user
end
def current_user=(user) #set current_user
#current_user = user
end
def current_user #get current_user
#current_user
end
What I'm having difficulty with is the part that reads:
The problem is that it utterly fails to solve our problem: with the code the user's signin status would be forgotten: as soon as the user went to another page.
I don't understand how this is true? I read on and understand the added code makes sure #current_user is never nil. But I'm not seeing how current_user would revert to nil if we just established it in 5th line.
Question 2:
The updated code reads as such:
module SessionsHelper
def sign_in(user) #in helper because used in view & controller
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
current_user = user
end
def current_user=(user) #set current_user
#current_user = user
end
def current_user #get current_user
#current_user ||= user_from_remember_token #<-- short-circuit evaluation
end
private
def user_from_remember_token
User.authenticate_with_salt(*remember_token) #*=use [] instead of 2 vars
end
def remember_token
cookies.signed[:remember_token] || [nil, nil]
end
end
In the remember_token helper, why does it use cookies.signed[] instead of cookies.permanent.signed[] & why doesn't it use ||= operator we just learned about?
Question 3:
Why do we need to authenticate_with_salt? If I authenticate & sign_in can see the id & salt attributes from the user who was passed to it, why do we need to double_check it? What kind of situation would trigger a mixup?
Remember that instance variables like #current_user are only set for the duration of the request. The controller and view handler instances are created specifically for rendering once and once only.
It is often easy to presume that because you've set a variable somewhere that it will continue to work at some point in the future, but this is not the case. To preserve something between requests you need to store it somewhere, and the most convenient place is the session facility.
What's missing in this example is something along the lines of:
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
Generally it's a good idea to use the write accessor to map out the functionality of the sign_in method you've given as an example:
def current_user=(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
#current_user = user
end
It's odd that there is a specific "sign in" method when the act of assigning the current user should be the same thing by implication.
From a matter of style, though, it might be more meaningful to call these methods session_user as opposed to current_user for those situations when one user is viewing another. "Current" can mean "user I am currently viewing" or "user I am currently logged in as" depending on your perspective, which causes confusion. "Session" is more specific.
Update:
In response to your addendum, the reason for using cookies to read and cookies.permanent to assign is much the same as using flash.now to assign, and flash to read. The .permanent and .now parts are intended to be used when exercising the assignment operator.

Resources