I'm having a lot of trouble understanding sessions and authentication with Rails. So basically, I tried to set a session based on user_id, following a tutorial from a book. Here is the create method in my sessions_controller.rb file:
def create
if user = User.authenticate(params[:email], params[:password])
session[:id] = user.id
redirect_to root_path, :notice => 'Logged in successfully'
else
flash.now[:alert] = "Invalid login/password combination"
render :action => 'new'
end
end
But, when I try to define a current_user method in my application_controller.rb file, it asks me to reference the session based on user_id:
def current_user
return unless session[:user_id]
#current_user = User.find_by_id(session[:user_id])
end
Here is what confuses me - user_id is an attribute of each Recipe (equivalent of articles or posts in my app) that creates a has_one relationship with a user. nowhere else in my application is user_id defined. so user_id shouldn't be an attribute of a User, right?
Just to clarify, I've listed the parameters for User objects and Recipe objects from the rails console:
2.0.0-p598 :019 > Recipe
=> Recipe(id: integer, title: string, body: text, published_at:
datetime, created_at: datetime, updated_at: datetime, user_id:
integer, photo_of_recipe_file_name: string,
photo_of_recipe_content_type: string, photo_of_recipe_file_size:
integer, photo_of_recipe_updated_at: datetime)
2.0.0-p598 :020 > User
=> User(id: integer, email: string, hashed_password: string,
created_at: datetime, updated_at: datetime, username: string)
In your create method it should be
session[:user_id] = user.id
You are setting up a key :user_id in the session hash and storing the authenticated user's id user.id in it for later use
Key name can be anything
Related
This is part of the railstutorial.org for rails 7.I'm testing the correct working of the login with remembering
require 'test_helper'
class SessionsHelperTest < ActionView::TestCase
def setup
#user = users(:tony)
remember(#user)
end
test 'current user returns right user when session is nil' do
assert_equal #user,current_user
assert is_logged_in?
end
test 'current user returns nil when remember digest is wrong' do
#user.update_attribute(:remember_digest,User.digest(User.new_token))
assert_nil current_user
end
end
they refers to this helper
def current_user
if (user_id = session[:user_id])
#current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.encrypted[: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
the first test fails
FAIL SessionsHelperTest#test_current_user_returns_right_user_when_session_is_nil (0.10s)
--- expected
+++ actual
## -1 +1 ##
-#<User id: 339078012, name: "tony fusco", email: "tony#example.com", created_at: "2022-10-04 07:33:09.980286000 +0000", updated_at: "2022-10-04 07:33:09.980286000 +0000", password_digest: [FILTERED], remember_digest: nil>
+nil
test/helpers/sessions_helper_test.rb:10:in `block in <class:SessionsHelperTest>'
and if i comment out part of a line
if user #&& user.authenticated?(cookies[:remember_token])
the second test fails
FAIL SessionsHelperTest#test_current_user_returns_nil_when_remember_digest_is_wrong (2.29s)
Expected #<User id: 339078012, name: "tony fusco", email: "tony#example.com", created_at: "2022-10-04 08:42:46.512404000 +0000", updated_at: "2022-10-04 08:42:48.782076000 +0000", password_digest: [FILTERED], remember_digest: "$2a$04$Haqx77Y7NRV/fCcirQiEU.tZnuqqXGIxD0n81FKTy84..."> to be nil.
test/helpers/sessions_helper_test.rb:16:in `block in <class:SessionsHelperTest>'
p.s I'm reviewing the other questions about this argument,since they are all different I'm posting this, yet this topic is full of issues
Per the title, I'm setting session[:user_id] in my controller if a user is authenticated successfully and then am testing that the session value matches the user id, but the session is nil with the following setup:
sessions_controlller.rb:
class SessionsController < ApplicationController
def create
user = User.find_by username: params[:username]
if user.try(:authenticate, params[:password])
session[:user_id] = user.id
redirect_to user_posts_path, notice: "Successfully logged in!"
else
redirect_to new_session_path, alert: "Invalid credentials."
end
end
end
session_controller_test.rb:
test "should sign in user with correct credentials" do
user_to_log_in = users(:one)
post :create, { password: "password", username: "yes" }
assert_equal user_to_log_in.id, session[:user_id]
end
user.rb:
class User < ActiveRecord::Base
has_secure_password
end
users.yml:
one:
id: 1
username: yes
password_digest: <%= BCrypt::Password.create('password', cost: 4) %>
How do I write a passing test here?
You may not necessarily have access to server data, like session, in your tests. Check out rack_session_access https://github.com/railsware/rack_session_access for a possible answer.
Hah, so apparently, with a username of 'yes' in users.yaml, the record in the test DB gets saved with a username of 't', so I assume it tries to convert it to some form of 'true'... Anyway changing the username in the fixture fixed the problem.
Here is my Rspec when testing an API end point related to Users:
context "updating a user" do
let(:user) { User.create! }
it "should let me update a user without an email" do
put "/api/v1/users/#{user.id}", {:user => {:first_name => 'Willy'}}.to_json, {'CONTENT_TYPE' => 'application/json', 'HTTP_AUTHORIZATION' => "Token token=\"#{auth_token.access_token}\""}
p user.inspect
end
And the controller action that I am testing looks like this:
def update
begin
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
p #user.inspect
render json: #user, :except => [:created_at, :updated_at]
else
render json: { :errors => #user.errors }, :status => :unprocessable_entity
end
rescue ActiveRecord::RecordNotFound
head :not_found
end
end
Surprisingly, the #user.inspect in the controller shows this:
"#<User id: 2, first_name: \"Willy\", last_name: nil, email: nil, state: nil, created_at: \"2013-06-22 11:21:22\", updated_at: \"2013-06-22 11:21:22\">"
And the user.inspect in the rspec, right after the call to the controller has been done, looks like this:
"#<User id: 2, first_name: nil, last_name: nil, email: nil, state: nil, created_at: \"2013-06-22 11:21:22\", updated_at: \"2013-06-22 11:21:22\">"
Why does the Rspec not catch the updates? I mean, I have tested this manually and the database gets updated correctly.
What am I missing here?
In rspec example you define user method with let, which returns ActiveRecord object. Your controller is creating different object, that points to the same database entry. Change in db is not reflected in user object in rspec example, as there is no callback mechanism that would notify it to change.
Using #reload method on AR object in test should solve your problem, as it forces reloading data from db.
I have the following User and Post relationships:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
has_many :posts
end
class Post < ActiveRecord::Base
attr_accessible :content
belongs_to :user
end
I am trying to create a new Post through a User, but I get an error. I am not sure why:
1.9.3-p392 :001 > #user = User.where(:id => 1)
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1
=> [#<User id: 1, email: "test#test.com", encrypted_password: "$2a$10$ltpBpN0gMzOGULVtMxqgueQHat1DLkY.Ino3E1QoO2nI...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 6, current_sign_in_at: "2013-03-04 05:33:46", last_sign_in_at: "2013-03-03 22:18:17", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", created_at: "2013-03-02 03:41:48", updated_at: "2013-03-04 05:33:46", avatar_file_name: nil, avatar_content_type: nil, avatar_file_size: nil, avatar_updated_at: nil>]
1.9.3-p392 :002 > #user.posts.create(:content => "This is the content")
NoMethodError: undefined method `posts' for #<ActiveRecord::Relation:0x000000024ca868>
There is a difference between where and find in the ActiveRecord Relationships.
The query:
#user = User.where(:id => 1) is giving your the array hash.
So when you do something like #user.posts for the above query, it gives error of NoMethodError on ActiveRecord::Relation as there is no such post associated with this hash. So in order to convert it into the user whose id is 1, you do like this:
#user = User.where(:id => 1).first
or
#user = User.find(:id => 1)
Both will give you the user whose id is 1 (only one record) and then you can use this:
#user.posts
The above will give the associated posts of user with id 1.
Then you can do:
#user.posts.create(:content => "Post of user 1")
So what you are trying to do is actually giving you the hash (set of users) but actually you want only one user to create the relevant post.
Also, See the difference between find and where.
Your code
User.where(:id => 1)
does not give you a model instance, but a relation. Hence the NoMethodError on ActiveRecord::Relation.
Change your first line to
User.find(1)
and you're fine.
Use this
#user = User.where(:id => 1).shift
#user.posts.create(:content => "This is the content")
OR
#user = User.find(1)
#user.posts.create(:content => "This is the content")
Here is the rspec error for update in customers controller:
5) CustomersController GET customer page 'update' should be successful
Failure/Error: put 'update', id => 1, :customer => {:name => 'name changed'}
<Customer(id: integer, name: string, short_name: string, contact: string, address: string, country: string, phone: string, fax: string, email: string, cell:
string, sales_id: integer, test_eng_id: integer, safety_eng_id: integer, web: string, category1_id: integer, category2_id: integer, active: boolean, biz_status: str
ing, input_by_id: integer, quality_system: string, employee_num: string, revenue: string, note: text, user_id: integer, created_at: datetime, updated_at: datetime)
(class)> received :find with unexpected arguments
expected: (1)
got: ("1")
# ./app/controllers/customers_controller.rb:44:in `update'
# ./spec/controllers/customers_controller_spec.rb:41:in `block (3 levels) in <top (required)>'
Here is the rspec code:
it "'update' should be successful" do
customer = mock_model(Customer)
Customer.should_receive(:find).with(1).and_return(customer)
customer.stub(:update_attributes).and_return(true)
put 'update', :id => 1, :customer => {:name => 'name changed'}
response.status.should == 302 #redirect()
end
Here is the update in controller:
def update
#customer = Customer.find(params[:id])
if #customer.update_attributes(params[:customer], :as => :roles_new_update)
if #customer.changed
#message = 'The following info have been changed\n' + #customer.changes.to_s
#subject ='Customer info was changed BY' + session[:user_name]
notify_all_in_sales_eng(#message,#subject)
end
redirect_to session[('page'+session[:page_step].to_s).to_sym], :notice => 'Customer was updated successfaully!'
else
render 'edit', :notice => 'Customer was not updated!'
end
end
Any thoughts? Thanks.
The values posted (and accessed via the params hash) are strings. The easiest way to correct your test is to have Customer.should_receive(:find).with("1").and_return(customer).
Notice we now have "1" (i.e. a String) as the expected argument instead of 1 (a FixNum).
All params are passed through as strings and I believe the find method is doing an implicit conversation to an integer. Either make it explicit using to_i in the controller or change your spec to expect the string "1".