everybody! Recently, I am working on Michael Hartle's RoR tutorial. In chapter 8, I encounter one problem which has confused me for two days. Here is the problem. In section 8.2.3.
module SessionsHelper
def sign_in(user)
.
.
.
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user # Useless! Don't use this line.
end
end
And Michael writes:
If we did this, we would effectively replicate the functionality of
attr_accessor, which we saw in Section 4.4.5.5 The problem is that it
utterly fails to solve our problem: with the code in Listing 8.21, the
user’s signin status would be forgotten: as soon as the user went to
another page—poof!—the session would end and the user would be
automatically signed out. To avoid this problem, we can find the user
corresponding to the remember token created by the code in Listing
8.19, as shown in Listing 8.22.
The Listing 8.22.
module SessionsHelper
.
.
.
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
end
My questions are: Why the previous code would make the session log out automatically when user go to a new page? Why the second piece of code wouldn't? I think, as long as a user log in, the #current_user's value will always be "user" until he log out explicitly, right?
The previous code doesn't so much log the user out, as doesn't re-create the user on subsequent requests.
State is not shared across requests, and has to be re-created with every request. #current_user is an instance variable, and keeps it's value for the duration of a single request.
To get around the fact that state is not shared, with each request we need to reload necessary variables such as #current_user from something that is common across the session, in this case they're using the remember_token cookie.
The first snippet of code does not reload #current_user on each request, so will forget what value it held as soon as the user browses to another page after logging in, the second snippet attempts to load the current user via the remember_token cookie, so after this has been set when someone logs in, should remember the user until that cookie expires.
What he is saying here is that when we use #current_user in the first example when not on a sign in page, we have not called #current_user = User.find(1). We are relying on it already having been set. Since we are not explicitly setting #current_user the following:
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
Says if the #current_user is not set, then set it by finding the user using the id stored in the cookie. That way when we navigate to a page where we do not explicitly set the #current_user it will have been populated.
Related
application_conroller.rb
def current_user
p 'current_user'
user = decode_jwt(cookies.permanent[:jwt_token]
#current_user ||= user
end
then, I have some.html.erb
<%=current_user.id%>
<%=current_user.first_name %>
<%=current_user.last_name %>
Then, my log prints out
"current_user"
"current_user"
"current_user"
It seems whenever I call current_user inside the html, then it literally goes through what is stated inside current_user.
I start to experience this by switching from conventional rails approach to JWT. Definitely this situation ends up slowing down the request. What would be the good practice to avoid unnecessary current_user execution?
When you say <%= current_user.id %> in your view, you're calling the current_user method and then calling the id method on what is returned. Then you say <%= current_user.first_name %> which calls current_user (again) and then called first_name on what is returned. And so on. There's nothing that will automatically recognize that you're calling the same method to get the same return value and then optimize it all away.
You're attempting to memoize current_user (a common thing) at the end of your method:
#current_user ||= user
but that doesn't help because, presumably, all the work is in decode_jwt(cookies.permanent[:jwt_token]) and you do that every time regardless of what #current_user is.
The memoization would normally look more like:
#current_user ||= decode_jwt(cookies.permanent[:jwt_token])
so that you only recode the JWT once (i.e. when #current_user.nil?) and then reuse it thereafter.
def current_user
p 'current_user'
user = decode_jwt(cookies.permanent[:jwt_token]
#current_user ||= user
end
This is a method. When you type current_user it's calling the method and executing it. If you want to output a variable, use the one you're making #current_user, or set a new variable in the controller: #user = current_user and change your view to be:
<%=#user.id%>
<%=#user.first_name %>
<%=#user.last_name %>
I start to experience this by switching from conventional rails
approach to JWT. Definitely this situation ends up slowing down the
request.
By their nature JWT feel like they make more authentication requests because each request requires checking the token in your code. But in reality, sessions are doing pretty much the same work and they require storing data server-side whereas JWT store them client-side. Plus, if your decode_jwt function is slowing down your server significantly due to three extra calls, you might want to review it.
I'm following railstutorial by Michael Hartl. In chapter 8.2.2 he defines a variable #current_user and a method current_user.
app/helpers/sessions_helper.rb looks like this:
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
end
Hartl defines #current_user an instance variable (of User object I guess); How can #current_user be an instance variable if it is itself an instance of the User class?
This is such a good question. It seems that this arrangement is not something that it was planned for, it simply happened: u need to have a variable that has some kind of memory outside the method (scope), in order to compare if #current_user=#current_user, but at the same time the whole arrangement makes theoretically no sense.
This article was written in 2008 and was proof read from members of the rails core team. This paragraph is very telling of the situation:
http://www.railway.at/articles/2008/09/20/a-guide-to-memoization/
--> "A little note on naming here: Some people seem to prefer prefixing the memoizing variable’s name with an underscore to indicate that it’s not meant to be used as an actual instance variable. To be honest, I don’t think this is really necessary unless you define a whole bunch of instance variables and memoized variables."
The SessionsHelper module is mixed into your controllers, so #current_user will be set as an instance variable of the controller which is handling the current request (Rails creates a new controller instance to handle each request)
I'm currently doing the rails tutorial available at http://ruby.railstutorial.org/chapters/
Somewhere around chapter 7-8 I had an error while deploying to Heroku (I unfortunately forget the error) but I found the solution online was to make the remember_token variable accessible in the user model. I wasn't sure if the solution was legit or not, but it worked, so I had no problem continuing the tutorial.
The remember_token is a variable that is defined as below:
self.remember_token = SecureRandom.urlsafe_base64
In other words, it is a randomized string of characters, so in and of itself, it is pretty safe. I.e. no one could just guess it.
It is used with a cookie to determine if someone was previously logged in:
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
However my user model has the following:
attr_accessible :email, :name, :password, :password_confirmation, :remember_token
...and I'm wondering if this remember_token as an accessible attribute would be a security flaw. Can someone just theoretically use a client command line and obtain the remember_token with something like user.remember_token ? Once they have the remember_token, couldn't they just simulate being previously logged in and thus no password / email combination would be necessary? Can someone with a bit more experience please shed some light on this?
Thanks!
attr_accessible makes the column updateable via mass assignment.
Therefore, allowing it on remember_token means they could theoretically hack up a new POST-form for user#update and set it to whatever they pass in through the form.
However, I don't think that what you are suggesting is possible.
What kind of console could somebody use to give the user access to your models?
To get that kind of access they'd have to be running the console on your server, in your Rails root... which is unlikely.
It's unlikely that a person can access the remember_token of another user, unless you let other users access the details of other users (ie not their own user), via controller actions. Allowing access to a user's own remember_token shouldn't matter - they already have it.
I am going through the great Michael Hartl tutorial to build ruby app here.
I am trying to understand the concept of how to create a session and I am stuck in understanding this line:
self.current_user = user
in this method:
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
end
I understand the whole concept of creating a cookie with the user_token.
But I don't understand what does self.current_user = user means and why is it even necessary to keep this line of code - I have the cookie with the token - why do I need to know the current user?
Also, where does this "self" is being stored - it is not like a flash[:success] parameter I can see in one of my views. so I don't understand where it is.
there are also these 2 methods in the same module:
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
And still I am trying to connect the dots of the purpose for this mysterious current user - is its purpose is to create #current_user global variable to use in the views?
If so - why there are there these 2 duplicated functions def current_user=(user) and def current_user
A few things.
First, you're reading the method names wrong (which is not surprising given how cryptic ruby method naming can be). def current_user=(user) is actually read as defining the method current_user= that takes an argument user, whereas def current_user defines a method current_user that takes no arguments. These are referred to respectively as setters and getters.
Here's a reference: Ruby (programming language): What are setters and getters in Ruby?
So that explains the duplication. On to your next question.
I don't understand what does self.current_user = user means
self is a topic unto itself, worthy of its own discussion, so I won't even try to explain it (here's one reference out of many). For the purposes of this question it's just important to remember that in order to set instance variables, you need to prefix your assignment with self, even within the class (where for other purposes it would be implicit). The rest of the line is a call to the current_user= setter method I mentioned above, with the argument user.
why is it even necessary to keep this line of code - I have the cookie with the token - why do I need to know the current user?
The reason it's necessary is that you don't want to be looking up the user from the token every time you need to get the current user. Take a look at the getter method:
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
What this says is: if I haven't looked up and set the instance variable #current_user yet, then look it up; if I have already set it, then just return it. That saves a lot of looking up.
I think that answers your questions. There are a lot of deeper issues (self, etc.) which you can find more information about elsewhere. Here's one discussion of why you need to include self in setters on SO: Why do Ruby setters need "self." qualification within the class?
UPDATE: Small clarification, that last link about using self for setters within the class is actually a bit off-topic, since you're calling it within a module and not directly from a class. In the context of a module, the self in self.current_user = user will become the class that the module is included inside of, e.g. User.current_user if it was called within the class User, etc. Again, another topic of discussion unto itself...
The method def current_user=(user) is basically a setter that the sign_in method uses in order to set the current_user.
def current_user will return the #current_user or if it is not set it will find it in the Users table by the remember_token. This basically allows you get the current_user at any point in time.
self.current_user in the context of the sign_in method will refer to the calling class or module in this case. It will be calling current_user from the Session Helper module.
I am not using Devise but have implemented a simple authentication scheme (basically outlined here http://railscasts.com/episodes/250-authentication-from-scratch) with the relevant part being here:
application_controller.rb
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
I have a list of assets that a user must be authorized to add. I am using paperclip. A user can has_many and a asset belongs_to a user (although this is essentially irrelevant to where it is assigned since my asset model is polymorphic for different assetable_types).
Where should I assign the current_user id to an asset? I would think in the model; maybe I should do a default_values using the session[:user_id] but that seems to be kinda ugly.
Also, these are nested_attributes and the models that these are nested to, currently don't know anything about the user. So really the source of information for the current_user isn't part of the current association.
thx
edit 1
should I create an instance of a User based upon the session[:user_id] value or just push it in?
If I understand your question correctly, why not assign the user to the asset in whichever controller first finds out that the asset belongs to the user? It's the controller's responsibility to translate web requests (including the session / current user) into something applicable to the model.