Rails Tutorial: use of instance variables - ruby-on-rails

I am in section 8.4.3 - Forgetting Users of the Rails Tutorial.
# app/helpers/session_helper.rb
module SessionsHelper
...
# Returns the user corresponding to the remember token cookie.
def current_user
if (user_id = session[:user_id])
#current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
#current_user = user
end
end
end
# Logs out the current user
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
...
end
In the current_user method we assign a user to the #current_user instance variable. I don't understand why we don't use that same instance variable in the log_out method (in that method current_user is not prepended with an # symbol). Where does current_user come from then since it isn't passed in as an argument to that method?

Ruby doesn't make difference between a method invocation and a local variable. That is the reason why current_user might look like both.
In your case it's the defined method.
You can actually use both #current_user and current_user as the returned value of that method should be equal to #current_user.

current_user is the method defined at the snippet you posted of the page.
#current_user is the instance variable set BY current_user
So when you look at the code:
# Defines a method called log_out
def log_out
# Calls the 'forget' method on the result of the 'current_user' method,
# which sets #current_user
forget(current_user)
# Deletes the session variable named 'user_id'
session.delete(:user_id)
# Resets #current_user to nil, because there should no longer be one
#current_user = nil
end
If I wanted to look at it differently, I could say:
def log_out
# Sets #current_user
current_user
# Calls the forget method on the variable #current_user
forget(#current_user)
session.delete(:user_id)
#current_user = nil
end
But I could NOT change the last line to current_user = nil instead of #current_user = nil, because then I am assigning the nil value to the name of a method instead of to the instance variable.
Part of the reason method calls and variables look so similar in Ruby is that everything is handled as an object, so methods are essentially just a variable whose value is a block of code (there are technical differences to what we call a "block" and a method, but conceptually this is a decent analogy). When we define a method, we save it into what is, more or less, a variable. Thus if we think about the method "current_user", we may instead think of it as a variable "current_user" whose value is forget(current_user); session.delete(:user_id); #current_user=nil.
This is an oversimplification, of course, but it is worth keeping in mind as you go forward with your learning.

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.

Rails helper methods: # vs normal variable

In Michael Hartl's rails tutorial we have a current_user method defined as such:
# Returns the user corresponding to the remember token cookie.
def current_user
if (user_id = session[:user_id])
#current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(:remember, cookies[:remember_token])
log_in user
#current_user = user
end
end
end
At first I assumed #current_user was needed as opposed to some other local variable like the_current_user (assuming you can't use current_user since that's the name of the method.
When using helper methods that return something, do we need an # variable or can we just use any variable type? (I saw other helper_methods use normal variable_names). (Im assuming #current_user was just convenient)
# makes variables available throughout the class.
If was just current_user instead of #current_user it would only be accessible inside of that specific method.
You can use current_user as a variable name inside of a method named current_user
you don't need to use # before an instance variable to return a value.
def some_method
user = "Jimmy"
user
end
> puts some_method
"Jimmy"
Here's a common Ruby idiom, up close:
def current_user
#current_user ||= User.find_by(id: something)
end
You can call current_user as often as you like, now, because it will only spend time hitting the database the first time you call it. On subsequent calls, #current_user has a value, so the ||= evaluates trivially as #current_user = #current_user.
The effect lasts as long as the # instance exists. If it's a controller, it will last for the current action, and then disappear. This means a new action with a different user will not trip over the previous value of #current_user.
Because def current_user occupies a namespace different from #current_user, the Ruby idiom is to name the "memento pattern" variable the same as the method it optimizes.
Your example makes the memento pattern a little confusing, because there are two ways to generate a current user.
You can return normal variable in helper methods.
# is just a syntax used to define an instance variable.
#current_user is just a convention, you can use any name like #whatever_user
For example, if we are using current_user instead of #current_user then you will not be able to access current_user from any of your views.
For example, if you want to show the name of the user if the user is signed in:
<%= #current_user.name if #current_user %>
It is possible only because we have used the instance variable.
Following will help you in knowing more about instance variable:
https://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/instancevars.html
http://ruby-for-beginners.rubymonstas.org/writing_classes/instance_variables.html
To know about all the types of variables available in Ruby:
https://www.studytonight.com/ruby/types-of-variables-in-ruby
Side Note
#current_user ||= User.find_by(id: user_id)
This pattern is called memoization. It is a very common pattern in Ruby/Rails. You can read more about it here:
https://www.justinweiss.com/articles/4-simple-memoization-patterns-in-ruby-and-one-gem/

From Rails Tutorial Ch8 - why is instance variable not used?

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
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
end
Currently working through Hartl's Rails Tutorial in Ch 8 where he gets you to write code for users to log in and stay logged in.
Under method logged_in?, why is local variable current_user used instead of #current_user?
current_user is not a local variable, it's an instance method.
why is instance variable not used?
It is being used.
When you call current_user method it returns an instance variable #current_user, which happens to be either a User object or nil.
That isn't a local variable! He's calling the current_user method which returns the #current_user value and so chains nil off of it. You need to look into scope in ruby to see how methods and instance variables and local variables interact with one another!
Because current_user - is method defined in same module. This technique called memoization. You can read about here http://www.justinweiss.com/articles/4-simple-memoization-patterns-in-ruby-and-one-gem/.

Why is the variable nil even if I don't set it to nil?

I don't understand why current_user is nil after deleting user_id from session. When the current_user function is called wouldn't it return the old current_user value again?
#Returns the current logged-in user (if any).
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
# Logs out the current user.
def log_out
session.delete(:user_id)
# #current_user = nil
end
I assume these methods are defined in a controller. Controller instance variables only have a lifespan of a single HTTP request. Session values last longer, but you've deleted the session value that matters here.
When the next request comes in, #current_user needs to be set again, and since session[:user_id] is nil, you are essentially calling User.find_by(id: nil), which will return nil.

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

Resources