Rails5 - Undefined local variable but it's global - ruby-on-rails

I am getting the following error in Rails:
undefined local variable or method `current_user' for #<UserController:0x0000000458d708> Did you mean? #current_user
The code fragment corresponding to that error is:
authorize #current_user
As you can see, I clearly mean #current_user like the error message suggests, and I also use #current_user like the error message suggests. Why is Rails thinking I mean a local unexisting variable when I expect it to be global? The authorize method is from Pundit.
I added a print statement to verify if the global variable exists, and the following code effectively prints out a valid User:
p #current_user
authorize #current_user

To use Pundit, you must define current_user (or pundit_user): https://github.com/elabs/pundit/blob/master/lib/pundit.rb#L270
When you do authorize #current_user, that is assuring that the current_user can perform actions on #current_user using the UserPolicy.

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/.

Rails Tutorial: use of instance variables

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.

undefined method `update_attribute' for nil:NilClass, signing out

I have been stuck on this problem for a while, but still can't figure out what the problem is
def sign_out
#return unless signed_in? # you are already signed out
current_user.update_attribute(:remember_token,User.digest(User.new_remember_token))
cookies.delete(:remember_token)
self.current_user = nil
end
rails returns me a error on #current_user, saying the update attribute undefined?
As far as i understand, my current user is already defined by this method
def current_user
remember_token = User.digest(cookies[:remember_token])
current_user ||= User.find_by(remember_token: remember_token)
end
Am i missing something here? I'm following the rails tutorials if it helps, but I kind of strayed off and added a additional page after the user creates a profile..... but I'm sure its ok because i saved the #user by doing
User.find(params[:id])
Or can someone explain to me the error in better detail?
Thank you
The method current_user will define the #current_user instance variable, but this is for it's own purposes - to stop it having to do a db query every time it's called.
You can access #current_user in your own code, but it won't exist if current_user hasn't been called yet, and will therefore evaluate to nil.
It's therefore safer to always use current_user and never use #current_user.
Can someone explain to me the error in better detail?
--
current_user
update_attribute' for nil:NilClass
This error is nothing to do with update_attribute, and everything to do with the object you're trying to call the method on - in your case current_user (basically means current_user is not defined)
The answers & comments you've been given basically are trying to help define the object you're calling the method on - I.E that current_user needs to be defined somewhere
From looking at your code, there are two things you need to consider:
Is current_user method being created / called?
Is the correct object being defined in this method?
--
Data
You've provided the following method:
def current_user
remember_token = User.digest(cookies[:remember_token])
current_user ||= User.find_by(remember_token: remember_token)
end
As pointed out by sevenseacat, the first thing I would do is ensure you've got a user who can be located with the remember_token. An important point actually - what's .digest?. Is it a class method in your model?
The likely problem you've got is trying to use the User.digest and User.find_by with the remember_token. Do you know where / how the remember_token will be set?
--
Method
Secondly, you need to make sure you're calling the current_user method. I'm guessing this is a helper method? If so, are you sure you're calling include xxxHelper in your controller?
never mind, found the bug.Had some unwanted code in my application controller

Resources