Agile Web Development with Rails Chapter 14 - ruby-on-rails

I am following the example write in Chapter 14 "Logging In" of the book.
I have my view in "127.0.0.1:3000/login" working well, but if i insert my user and password it returns this error:
NoMethodError in SessionsController#create
undefined method `authenticate' for #< User:0x9f75978>
How to solve it?
create method sessions_controller.rb is:
def create
user = User.find_by_name(params[:name])
if user and user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to admin_url
else
redirect_to login_url, alert: "Invalid user/password combination"
end
end

It's trying to call authenticate on User, this means you probably don't have an authenticate method on user. If you do, make sure it's not private.

The code you give tries to call the authenticate method on an instance of a User object.
Based on you comment re: User.authenticate(name, password) you have an authenticate method only on the User class - and it takes both name and password as parameters.
To call the User class method, you'd instead use the following:
user = User.find_by_name(params[:name])
if user && User.authenticate(params[:name], params[:password])
Alternatively, look for an instance-level method called authenticate which (as #caulfield mentioned above) would look something like:
def authenticate(password)
# does stuff
end
instead of User.authenticate(name,password)

Related

indicate which users are currently logged in

I'm trying to find a way to display logged in active users on my web app. I'm not using any gem for authentication like Devise. I have a list of users and wanted to show an icon or some type of indicator next to a users name if they are currently on the site.
I'm not sure how to go about this. Possibly I could add a column called currently_logged_in to my User model and could set the value to true when the session is created and then to false when the user session is destroyed?
class SessionsController < ApplicationController
def new
end
def create
if user = User.authenticate(params[:email], params[:password])
session[:user_id] = user.id #session id created off of the
redirect_to(session[:intended_url] || user)
session[:intended_url] = nil #removes url from the sessions
else
flash.now[:error] = "Invalid email/password combination"
render :new
end
end
def destroy
session[:user_id] = nil
redirect_to root_url
end
end
User model
# tries to find an existing user in the database so that they can be authenticated.
def self.authenticate(email, password)
user = User.find_by(email: email) # returns user or nil value
user && user.authenticate(password) # if user exists validate that password is correct
end
It depends what you mean by "currently on the site".
Adding a currently_logged_in column like you described works IF you want to mark users that are currently logged in. However most users don't log out when leaving a website these days so that probably won't do what you want.
A better solution would be to add a last_active_at column which you can update with the current time whenever a user performs some action. Then determine a threshold that makes sense for your website, let's say 15 minutes, and only mark users in your list that have a last_active_at value less than 15 minutes in the past.
Assuming the definition of "active user" for your website involves hitting authenticated endpoints it would be as simple as changing your authenticate method to:
def self.authenticate(email, password)
user = User.find_by(email: email) # returns user or nil value
if user && user.authenticate(password)
user.update!(last_active_at: Time.now)
true
else
false
end
end
Firstly You need to find current user on site.
You may b call current_user method which is in application helper and you should display all current user in wherever you want.
For Example,
module ApplicationHelper
def current_user
#current_user ||= session[:user_id] && User.find_by_id(session[:user_id])
end
end
And you call this method in Session controller as #current_user.

sRails 5 Devise after_sign_in_path

I have a Rails 5 app with Devise. Each user has a role_id where they are assigned a role upon creation. I'm trying to use the after_sign_in_path_for method that Devise gives to redirect to a specific page on login based on the role.
Below is what I have so far, but it doesn't work when trying to sign out a disabled user.
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
case resource.role_id
when Role.find_by(name: "admin").id
root_path
when Role.find_by(name: "disabled").id
destroy_user_session_path
else
super
end
end
end
I'm able to sign in when I'm an admin user and it redirects. But if I try to sign in as a user whose role is disabled, it tries to tear down the session then raises an exception of No route matches [GET] "/users/sign_out". I know the method destroy_user_session_path expects a delete method but how can I pass this in the application controller?
What am I doing wrong here?
Update
I tried the sign_out(resource) as suggested in the first answer, and it raises an exception undefined methodto_model' for true:TrueClassin mymy_sessions_controller.rb` which I use to override the create method to set a login token and limit concurrent sessions. Here is the controller.
class MySessionsController < Devise::SessionsController
skip_before_action :check_concurrent_session
def create
super
set_login_token
end
private
def set_login_token
token = Devise.friendly_token
session[:token] = token
current_user.login_token = token
current_user.save(validate: false)
end
end
You can check roles inside MySessionsController#create and prevent logging if the role not valid instead of allowing user to login then logout
def create
unless current_user.role_id == Role.find_by(name: "disabled").id
super set_login_token
else
redirect_to new_user_session_path, alert: "You can't log in"
end
end
You can also use active_for_authentication? and inactive_message methods in user model to prevent him from login. in /app/models/user.rb:
def active_for_authentication?
super and self.role_id != Role.find_by(name: "disabled").id
end
def inactive_message
"You can't log in"
end
destroy_user_session_path is making [GET] "/users/sign_out" request.
You can use sign_out or reset_session function to delete session directly.
Hope this answer works for you.
Use devise's sign_out(resource) method instead of destroy_user_session_path. This method will destroy the user session.

How to define current_user in Devise?

I want to print current user email in my RoR app. For this I did use the next code:
User.current_user
and it prints the next error: undefined methodcurrent_user' for #`
but when I used just current_user it doesn't print anything. I did search in Google and Stack, tried to use the answers of them, but nothing.
How can I get the user email?
In controllers, current_user alone will return the current signed in user. So current_user.email will return the email of the signed_in user. For non-signed in users, current_user will return nil.
To print the current user email in controller,
class TestController < ApplicationController
def example
p current_user.try(:email) # try is used because it will return nil if the user is signed in. otherwise, it will raise an error undefined method 'user' for nil class
end
end
Try this.
User.current_user.email

Why does the log_in helper method work in Hartl tutorial?

Here is the helper method log_in in the Hartl tutorial in app/helpers
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
end
IN the controller we call it like this:
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
Why does this work? The method seems to be an instance method but who is the receiver of the method in the create action?
This method is called on the controller's instance. Controller instantiates during HTTP request flow. SessionsController includes methods from SessionsHelper module. Try to rename method's name slightly and invoke in the SessionsController log_inn instead of log_in and you will see something similar to undefined method 'log_inn' for #<SessionsController:0x1047d4825>. So as you see receiver is the instance of the SessionsController class.
The login method "works" by simply assigning the user_id to the session variable. The session is persisted between requests and used to determine whether or not a user is logged in or not. For more information on sessions see this rails guide: http://guides.rubyonrails.org/security.html#sessions

What does self do in ruby on rails?

I'm on railcasts just practicing some rails and have come across something I'm trying to understand.
I didn't get what the "self" on the authenticate method was doing. So I deleted it and tested the login of my app to see if it would show an error and it did:
error:
**NoMethodError in SessionsController#create
undefined method `authenticate' for #<Class:0x00000102cb9000**>
I would really appreciate if someone could explain exactly what that "Self" is doing. I was trying to figure out exactly what was going on but can't get my head around it.
Method is defined in model and called in sessions_controller..
I've been continuously deleting my app and starting from scratch to get the hang of it and many things make sense to me each time i start again but I'm stuck at "self".
I'm just the type of person who likes to understand why something works.
controller:
def create
user = User.authenticate(params[:email], params[:password])
if user
session[:user_id] = user.id
redirect_to root_path, :notice => "Logged In"
else
flash.now.alert = "Invalid credentials"
render "new"
end
end
model:
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
This is a basic ruby question. In this case, self is used to define a class method.
class MyClass
def instance_method
puts "instance method"
end
def self.class_method
puts "class method"
end
end
Which are used like this:
instance = MyClass.new
instance.instance_method
Or:
MyClass.class_method
Hope that clears things up a little bit. Also refer to: http://railstips.org/blog/archives/2009/05/11/class-and-instance-methods-in-ruby/
self defines a method of the class instead of the instance of the class. So with def self.authenticate you can do the following:
u = User.authenticate('email#domain.com','p#ss')
Instead of doing…
u = User.new
u.authenticate('email#domain.com','p#ss')
That way, you don't have to create an instance of user to authenticate one.
For the sake of completion and to thwart future headaches, I'd like to also point out that the two are equivalent:
class User
def self.authenticate
end
end
class User
def User.authenticate
end
end
Matter of preference.
class User
def self.xxx
end
end
is one way of defining class method while
class User
def xxx
end
end
will define an instance method.
If you remove the self. from the def, you will get a method not found error when you do
User.authenticate
because you are trying to call a method on a class rather than an instance of the class. To use an instance method, you need an instance of a class.

Resources