Question on using or equals (||=) in application controller - ruby-on-rails

I have seen the or equals ||= often used in application controller methods to set a variable if it doesn't exist. The most recent in Railscasts 270. But I have a question.. take for example this helper method
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
From my understand #current_user is set if it doesn't already exist. This means that rails doesn't have to go out to the database, performance win, etc.
However I'm confused as to the scope of #current_user. Lets say we have two users of our website. The first one (lets call him "bob") comes to the site and sets #current_user to be his user object. Now when the second one ("john") comes in and rails asks for #current_user... why isn't the user object still bob's? After all #current_user was already set once when bob hit the site so the variable exists?
Confused.

Variables prefixed with # are instance variables (that is, they are variables specific to a particular instance of a class). John's visit to the site will be handled by a separate controller instance, so #current_user will not be set for him.

Related

Can someone explain the def current_user method in rails pls

This is the line I don't quite get.
#_current_user ||= session[:current_user_id] && User.find(session[:current_user_id])
If there is a User model with an email field and a password field what is User.find looking for? ie there is no session field stored in the User model.
Session has to do with the controller in your application.
A session is a way to store information (in variables) to be used across multiple pages. Unlike a cookie, the information is not stored on the users computer.
Sessions are usually saved as a key: value hash pair, and they can get expired.
so, according to the code example you gave:
#_current_user ||= session[:current_user_id]
User.find(session[:current_user_id])
The line: #_current_user ||= session[:current_user_id] is setting the #_current_user to the value of current_user_id in session, if #_current_user is nil.
User.find(session[:current_user_id]) on the other hand is getting the value of current_user_id in session, which should be an id, and is going to the database to find the user with that id.
So, User.find(session[:current_user_id]) is finding by id, and not by email or by password
#current_user is meant as an instance variable to bring back the User object for the currently logged-in user.
The typical use case for #current_user (at least to Devise users like us) is to utilize it within our code infrastructure:
def create
#model = current_user.models.new ...
end
Thus, the answer to your question:
what is User.find looking for?
... is that it's looking for the User object for the signed-in member.
I think you're getting confused with how an authentication system would be expected to work. Namely that once you log in (authenticate), the app sets a session (as described by Sunny K) to denote which user is browsing.
This is why you have User.find(session[:current_user_id]) -- your authentication system (whether homebrew or pre-packed) has already validated the email & password. Now it has to keep track of which user you are, so that each time you send a request, it can rebuild your current_user object.
--
Another important factor is the fact that HTTP is "stateless" - meaning that each request has to be "new" (IE the state has to be recreated each time).
This is opposed to a stateful application, such as a game, where the fact you're running the application allows you to retain its state throughout the session.
As such, when Rails receives requests from the browser, it does not "remember" who you are - it's literally a dumb machine.
If you want access to user-specific information, there needs to be a way to firstly authenticate the user (which you have), and then authorize their request. The authorization part is up to you, but in order to make it work, you basically have to send the current user id to the app each time.
Then, when you invoke an instance of your program, the #current_user object will be available. This is what User.find(session[:current_user_id]) is for.

Instance variable in controller with Ruby On Rails

When someone is logging into my application, I use:
def create
#user = User.authenticate(params[:email], params[:password])
[...]
end
Ok, then, when someone is logging out:
def destroy
user = User.find_by_id(session[:user_id])
[...]
end
Knowledge
As far as I know, variable scopes work based on a scope, at least on Ruby (on Rails).
Our friend said:
In case of controllers, it present for that HTTP request alone, the object and the instance variables.
Ok. My variable scope created on create method is useless for destroy method, but I was thinking about the subject and the following question appears: There's a way to preserve #user for that controller at all, regardless of the HTTP request?
I mean, # in this case seems useless to me because its not flexible. I don't know, just sounds strange for me I can't reuse it when I want to.
That's how the web works and why http is a 'stateless protocol'. You must understand that you are not starting to run a program and stop it when your user logs out. But you 'restart' the program for every single request. It's a new instance, a new process that knows nothing of the last one and for sure shares no memory with it. Actually the Rails instance that handles the create and the one that handles the destroy could easily run on two physically different servers!
There is no state (but what you put in the session storage or the URL params). # in this case means that your view can use this data (which in the Ruby context means that Rails already is doing some tricks to get it handed over there, since these are two different classes and the view would otherwise not know anything about the controllers instance variables).

Fewer queries on Rails views

In my application I have a header that has many rules, since I have many types of profiles.
I want to make just one query (maybe two) to get the user and make all the checks that I need
In my application controller I have this method:
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
To check the current user, but I keep calling it on my header.html.erb, if I check it, like, 10 times, I will do 10 queries...
As far as I know when I first call current_user I would get a variable #current_user to use. But this is clear for me when I have a controller. I call current_user and on my view I just check #current_user.something. since /layouts/_header.html.erb doesn't have a controller, how can I do this ?
What you have should not perform multiple queries if the lookup is successful -- it will set #current_user to the return value of the query and then just return that in the future. However, if no user is found, #current_user will be initialized to nil and so additional calls to current_user will execute the query again.
One possible workaround is to used the defined? operator like so:
def current_user
return #current_user if defined?(#current_user)
#current_user = User.find_by_remember_token(cookies[:remember_token])
end
However I don't love this implementation, because if anything defines #current_user in the current closure, then this will return the value whether the lookup has been done or not. You could also set another variable that tracks whether the current user has been looked up and only execute the query if it has, but that seems ugly to me, too.
For a more robust solution to this general problem, see the memoist gem. Or better yet, have you considered whether devise and/or warden might be right for your authentication needs?
since /layouts/_header.html.erb doesn't have a controller, how can I do this ?
Your layouts do have controllers. It's impossible for them to be rendered otherwise.
What you're doing now will work as it's written, and it will minimize the number of queries, and your layout files will have access to the current_user helper, provided you've made it available to your views via helper_method :current_user in your controller.
There's really nothing wrong with your code, whatsoever; all the problems you think you're having, you're probably not really having.
From the following
" I have a header that has many rules, since I have many types of profiles"
I guess you are verifying multiple conditions based on user role. If my assumption is correct then
Better create separate partials for each role.For example if you have admin role then
create partial in layouts/_admin.html.erb
Then you can include it as
<%= render "layouts/" + current_user.role %>

Ruby on Rails - login with cookie or username / password - always hitting the db

UPDATE: All, thanks for the responses - here is some more significant info.
I'm using a Neo4J graph database for the back-end, ROR 3 (MRI), and one single Neo4J database server accessed via REST.
If you don't know much about Neo4j, to use more than one database server (master/master) for data costs $26,000, which means I have to code for optimization now, not later, or come up with $26k...I'm sure you can guess which way I'm going with this..And I'm using it via rest, not locally etc, so performance matters...
I have one database server that has to handle all of the database work, and yes 1 ms counts under this scenario where some other queries take up to 40 ms. So no, I don't want to hit the database unnecessarily as it will simply add unnecessary work to it.
It might be easy to say "don't code for optimizations or problems you don't have yet" yet given the bottleneck and steep costs - and the fact I already have what I need done except for the authentication piece, it really doesn't apply.
What I simply wanted to know, was if the #current_user ||= is valid across pages.. The answer is that it's valid in a request, and not across them or pages. Yes this is a simple question, but sometimes they have to be asked in the midst of R&D and advanced stuff. Hence my gut feeling to stick with sessions to hold the id of the user logged in.
Thanks for your help!
I'm trying to allow a user to login to my site either by cookies or by username and password. The username/password part works fine, but having a problem with introducing cookies....
I've ready plenty of "how tos" including: http://ruby.railstutorial.org/chapters/sign-in-sign-out
I do not want to use a gem, devise, etc.
PROBLEM:
I notice that requests from the page to read current_user (a helper in the application controller) results in database reads, even though I'm using ||=..
I've tried the #current_user ||= User.find by blah blah blah
and it ALWAYS hits the database. This shoudn't be the case right? It should hit once and then that's it, correct?
But before you suggest any tutorial - I've seen tons of them - here is my question.. is #current_user saved across pages, or just for the current page? The link above mentions it only saves it for the current page...
You see, I don't want to keep hitting the database needlessly to find out the same person previously is logged in.. I can do that with session variables.
I really just want to check for a cookie,and would be happy to keep on using session[:user_id] after that.. for performance reasons, I do not want to keep hitting the database.
Any help?
My NEW code is below (and this too always hits the database as it should in this instance). I removed the typical #current_user ||= find_by.. because it was useless - it was always hitting the db.
.. I already tried https://github.com/cliftonm/basic-auth and http://ruby.railstutorial.org/chapters/sign-in-sign-out etc..
(ROR 3.2...)
class ApplicationController < ActionController::Base
protect_from_forgery
#before_filter :set_var
helper_method :current_user
private
def current_user
#current_user = User.find_by_id(session[:user_id]) #if session[:user_id]
if (#current_user)
return #current_user
end
#current_user =User.find_by_remember_token(cookies[:auth_token]) if cookies[:auth_token]
if (#current_user)
return #current_user
end
end
User.find will always hit the database unless you have some kind of cache extension loaded. Rails.cache can be configured several ways, but the most popular, if this sort of thing is required, is Memcached.
Unless you're dealing with massive scaling issues, the time required to fetch a user record should be less than 1ms, so it's hardly worth fussing about. Check your log/development.log to see the execution times of your various queries and focus first on the slowest ones.
Instance variables like #current_user persist only for the duration of the request. Remember that the HTTP protocol is stateless, each request exists independent of the others, and the only way to communicate state is via cookies, a persistent store like a database, a temporary store like an in-memory cache, or by parameters sent in with the request itself via GET or POST.
If you want to save something across pages, add it to the session, but be careful. You should only be persisting things like strings, numbers, or booleans. You should not be adding models. Further, using the default cookie store, each thing you put in the session increases the overhead on all requests made to your application from that point forward until you remove that from your session store.
Don't sweat the little things until you've got all the other problems solved. Business logic first, optimization second.
Neither of these should be slamming the database every time. Please let me know.
if (#current_user)
#current_user
else
#current_user = User.find_by_id(session[:user_id])
end
or
#current_user ||= User.find_by(id: session[:user_id])
Have you tried using a before_filter (instead of a helper) to load current_user only once? You would then store it / access it using an instance variable.
class ApplicationController < ActionController::Base
before_filter :load_current_user
def load_current_user
#current_user = # your code goes here.
end
end
Edit:
My bad. How about storing a server-side encrypted current_user record in the cookie, and keeping the hashsum in the session for later checkup?

Need clarification, why the wrapper methods? Ruby on Rails Tutorial chapter 9 section 9.3.3 current-user -

I understand that the helpers are not generally accessible in controllers. But why all the wrapping around a instance variable #current_user?
Why does the helper have "current_user=" and "current_user" defined as methods? Why can #current_user be all that is needed for this section?
I think you need to continue reading. From the text:
If we did this, we would effectively replicate the functionality of attr_accessor, first seen in Section 4.4.5 and used to make the virtual password attribute in Section 7.1.1.8 The problem is that it utterly fails to solve our problem: with the code in Listing 9.15, 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 session user corresponding the cookie...
I have the same question. Yes, I think that current_user is structured this way to prevent code replication. But I also think it might be a syntax issue. If you made current_user a public property of SessionHelper, (I think) you'd access it with SessionHelper.current_user, but if you define a current_user method, and a current_user=(user) method, you can use the current_user property without the extra clutter.
So, I think it's just a style issue. Your approach works fine too.
I did get a good answer from a co-worker.
module SessionsHelper
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
self.current_user = user
end
end
def current_user
#current_user ||= user_from_remember_token
end
It is a refactor issue. You don't want to replicate the code from current user everywhere.
I still think the current_user= method is not needed. I don't see a reason not to use the instance variable for assignment.

Resources