Rails: where does the infamous "current_user" come from? - ruby-on-rails

I've been looking around recently into Rails and notice that there are a lot of references to current_user. Does this only come from Devise? and do I have to manually define it myself even if I use Devise? Are there prerequisites to using current_user (like the existence of sessions, users, etc)?

It is defined by several gems, e.g. Devise
You'll need to store the user_id somewhere, usually in the session after logging in. It also assumes your app has and needs users, authentication, etc.
Typically, it's something like:
class ApplicationController < ActionController::Base
def current_user
return unless session[:user_id]
#current_user ||= User.find(session[:user_id])
end
end
This assumes that the User class exists, e.g. #{Rails.root}/app/models/user.rb.
Updated: avoid additional database queries when there is no current user.

Yes, current_user uses session. You can do something similar in your application controller if you want to roll your own authentication:
def current_user
return unless session[:user_id]
#current_user ||= User.find(session[:user_id])
end

Related

making Session mandatory in a Rails app

I want to make it so that a session has to be present in order to use a site. If not, then redirect to the root path, so that a user can choose whether to browse the site as a guest, log in or register. I'm using a basic authentication made from scratch, based on a Railscast.
in the app controller
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :set_artists
before_filter :check_session
helper_method :current_user
private
def set_artists
#artists = Artist.all
end
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def check_session
unless current_user
redirect_to root
end
end
end
I had a guest user logged in, then wiped out all of the guest users through the Rails console: User.where(guest: true).destroy_all. I have a rake task that wipes out guest sessions that are 1 day old, so this would a pretty typical situation. Trying to reload after that, the error comes up: Couldn't find User with 'id'=8
The problem is that your users will keep their cookies, so when you run:
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
the user still has the session cookie with a :user_id and the call to User.find is failing to find the now-deleted user.
A solution is to replace this with a call that can fail, such as:
def current_user
#current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
end
Where find will raise an exception if the User cannot be found, find_by will just return nil leaving your current_user empty.
That being said, I agree with the earlier commenter that this is not really the "Rails way." Deleting all the guest users each day is probably not the best way to get the behavior you want.
Instead, one idea is that you could add a timestamp to the User model indicating when the user was last asked to validate as a guest, and then if more than 24 hours have elapsed, you could bounce guest users back to the page that prompts them to register. You don't have to delete their User entry; you can just reuse it when they re-register.

How do I check if my user is an admin in rails even after new http requests?

My user model has the attributes password, password_confirmation, username, email, and admin.
I'm wondering how exactly do I check whether the currently logged in user is an admin. How would I go about the methods? I've tried if user.admin? on my views, but it seems that doesn't work.
I'm new to rails so any suggestions would be helpful!
There is a "session" hash which persists through the whole session.
Once a user has logged in, you would store the current user's id in the session hash, like so
session[:user_id] = user.id
Now, if you want the current user to be accessible from your controllers and in your views, you can go to apps/controllers/application_controller and make some useful methods...
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user, :signed_in?, :is_admin?
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def signed_in?
!!current_user
end
def is_admin?
signed_in? ? current_user.admin : false
end
end
Now in your views you can use "is_admin?" to check if the current user is an admin, use
"signed_in?" to check if a user is signed in, and "current_user" to access the user object if it exists (if no user is logged in, "current_user" will be nil)
Cheers
I suggest you to consult with this Devise guide. It shows how to create a basic user model with help of Devise and how to perform admin checks. And yes, by giving you this link, I strongly encourage you to use Devise gem for users and all that stuff ;)

detecting if user is logged in at different domain

I'm developing a rails app that creates a widget that you can put on your website. When the widget is displayed (on a website with a different host) to a user who is logged in my app, I would like to display him some additional admin options.
What would be the best and easiest way to figure out if the user is logged in the app?
I was thinking of storing the IP when user logs in, and then compare the IP from the request that is sent to the widget controller.
I followed the Omniauth Railscast Episode and have been using session variables and a SessionsController to create and destroy sessions when the user logs in and out.
class SessionsController < ApplicationController
def create
# create user if new user, or find user account if returning user.
session[:user_id] = user.id
redirect_to root_url # or wherever
end
def destroy
session[:user_id] = nil
redirect_to root_url # or wherever
end
end
Then in the Application Controller,
class ApplicationController < ActionController::Base
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
You can then easily determine if a user is logged in or not via if current_user, or equivalently, if session[:user_id] is nil.
IP could be deceptive. Try cookies.
Edit: not only in an actively deceptive manner (i.e. spoofing/Tor) but rather if two people are on separate sites from the same public IP, then you have a false correlation.

Rails - Why use self.current_user = user in sign_in method

I have finished the Ruby on Rails Tutorial by Michael Hartl. I know some basic ideas about instance variable, getters and setters.
The sign_in method is here
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
Now I'm stuck at this line
self.current_user = user
I found this related question, but I still don't get it.
After sign_in, the user will be redirected to another page, so #current_user will be nil. Rails can only get current_user from cookie or session, then set #current_user, so that it doesn't need to check cookie or session again in current request.
In sign_out method
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
For the same reason, why do we need self.current_user = nil since the user would be redirected to root_url?
Here's the code for getter and setter
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
You are right that the #current_user is not set after the redirection.
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
This statement helps avoid repeated calls to the database, but is only useful if current_user variable is used more than once for a single user request. Consequently, setting the current user is only helpful during a single call.
Similarly, setting the current user to nil and removing the token from cookies during sign_out ensures that subsequent processing will take the signing out into account. Otherwise, there is a risk of other methods referring current user variable and thinking that the user is still logged in.
You have a full explanation on the next section of the book
Current User
Basically when you do self.current_user= you invoque the method 'def current_user= ()' this is the setter, you will be probably not only assigning the #current_user variable here but also keeping some reference in the cookies or session for future reference. In the same way you will probably be creating an accessor that will look like
def current_user
#current_user ||= get_user_from_cookies
end
In order to have accesible the current user. I think you just went to fast and the book is trying to go step by step for users not familiarised with web dev
I believe you're right in saying that for the code you've written so far it doesn't make much difference.
However it doesn't make sense for your sign_in/sign_out methods to know the ins and outs of how users travel through you application. It would be very brittle (and not its business) if it assumed that the only thing your application did after login was to redirect the user to the root page.
You could be doing all sorts of things, from collecting audit data (record every time someone logs in for example) to redirecting them to a different page depending on the users preferences or some other attribute of the user.

Ruby on rails global variable?

I'm trying to set the current user into a variable to display "Logged in as Joe" on every page. Not really sure where to begin...
Any quick tips? Specifically, what file should something like this go in...
My current user can be defined as (I think): User.find_by_id(session[:user_id])
TY :)
You might want to use something like Authlogic or Devise to handle this rather than rolling your own auth system, especially when you aren't very familiar with the design patterns common in Rails applications.
That said, if you want to do what you're asking in the question, you should probably define a method in your ApplicationController like so:
def current_user
#current_user ||= User.limit(1).where('id = ?', session[:user_id])
end
You inherit from your ApplicationController on all of your regular controllers, so they all have access to the current_user method. Also, you might want access to the method as a helper in your views. Rails takes care of you with that too (also in your ApplicationController):
helper_method :current_user
def current_user ...
Note: If you use the find_by_x methods they will raise an ActiveRecord::RecordNotFound error if nothing is returned. You probably don't want that, but you might want something to prevent non-users from accessing user only resources, and again, Rails has you covered:
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
before_filter :require_user
private
def current_user
#current_user ||= User.limit(1).where('id = ?', session[:user_id])
end
def require_user
unless current_user
flash[:notice] = "You must be logged in to access this page"
redirect_to new_session_url
return false
end
end
end
Cheers!
It belongs in your controllers.
All your controllers inheirit from Application Controller for exactly this reason. Create a method in your Application Controller that returns whatever you need and then you can access it in any of your other controllers.

Resources