ArgumentError in Chapter 7 of "Rails Tutorial" by Michael Hartl - ruby-on-rails

everyone. I've got the following problem:
After implementing has_password? in section 7.2.3 RSpec displays the following error for "should create a new instance given valid attributes" test for example
1) User should create a new instance given valid attributes
Failure/Error: User.create!(#attr)
ArgumentError:
wrong number of arguments (1 for 0)
# ./app/models/user.rb:42:in secure_hash'
# ./app/models/user.rb:39:inmake_salt'
# ./app/models/user.rb:30:in encrypt_password'
# ./spec/models/user_spec.rb:14:inblock (2 levels) in '
Finished in 1.47 seconds
1 example, 1 failure
<-- Slave(1) run done!
I don't understand, what exactly causes the problem.
Here is my user.rb code:
require 'digest'
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
# Automatically create the virtual attribute "password_confirmation"
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
# Return 'true' if the user's password matches the submitted password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash
Digest::SHA2.hexdigest(string)
end
end
What can it be?
Thank you in advance!

Your secure_hash method needs to take an argument.
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end

Related

Ruby on Rails Tutorial (Hartl) Chapter 7.2.3 RSpec test failing

I am working my through Hartl's Rails Tutorial. I have gotten up to chapter 7, but one of my RSpec test cases is failing, specifically the one dealing with passwords that do not match:
describe "has_password? method" do
it "should be true if the passwords match" do
#user.has_password?(#attr[:password]).should be_true
end
it "should be false if the passwords don't match" do
#user.has_password?("invalid").should be_false
end
end
Output from Terminal:
Failures:
1) User password encryption has_password? method should be false if the passwords don't match
Failure/Error: #user.has_password?("invalid").should be_false
expected "273725daa81e74764ea1e941a0789da7d580656cd321c64e39d1389f6a7e14d9" to be false
# ./spec/models/user_spec.rb:111
Here is my /user.rb code:
require 'digest'
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40}
before_save :encrypt_password
def has_password? (submitted_password)
encrypted_password = encrypt(submitted_password)
end
private
def encrypt_password
self.salt = make_salt unless has_password? (password)
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
I can't seem to figure out what is wrong for the life of me. I appreciate the help!
In your has_password? method
encrypted_password = encrypt(submitted_password) needs to read:
encrypted_password == encrypt(submitted_password)

Rails, update method and validation

I have a problem and I need an idea how to fix my update method. I have an admin panel where I can create users. This form include name, mail, password, repeated password fields and it works fine. Then I want to have a list of all users and to edit these who I want. The problem is that I want to edit part of the information which is not included in the form of the registration and default is empty. In edit mode my form has two new fields - notes and absences. When I change these fields and call update method I see message that password and repeated password don't match which is validation in the registration but I do not have these files in edit mode. How could I fix this problem. This is part of my code:
class UsersController < ApplicationController
def edit
#user = User.find(params[:id])
#title = "Edit user"
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated."
redirect_to #user
else
#title = "Edit user"
render 'edit'
end
end
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => true
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
private
def encrypt_password
self.salt = make_salt unless has_password?(password)
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
The validation for password presence is true when you are creating a user, but once a user has an encrypted password, you don't want to force it to be present in all form submissions in the future.
Active record supports adding conditions to validations, so I would suggest putting a condition on the password validation to make it only execute if the user object does not already have an encrypted password. The relevant snippet would be:
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 },
:if => :needs_password?
def needs_password?
encrypted_password.nil?
end

Rspec: Result matched using == but still gives "Failed" test result

Good day! I'm practising materials from "Ruby on Rails Tutorial" by Michael Hartle.
Below is the failure message I received, even though the "expected" and "got" seems to match. Would you please give me some suggestion to see how I should approach this issue?
Thank you so much!
Below is the implementation code:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :emp_id, :dept_id, :password, :password_confirmation
validates :emp_id, :presence => true
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(emp_id, submitted_password)
user = find_by_emp_id(emp_id)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
Below is the SPEC code:
require 'spec_helper'
describe User do
before(:each) do
#attr = {:name=>"Example", :dept_id=>01, :emp_id=>10, :password=>"pwdabcd", :password_confirmation => "pwdabcd" }
end
.
.
.
describe "password encryption" do
before(:each) do
#user = User.create!(#attr)
end
.
.
.
describe "authenticate method" do
it "should return the user on emp_id password match" do
matching_user = User.authenticate(#attr[:emp_id], #attr[:password])
matching_user.should == #user
end
end
end
end
Thank you so much for your kind assistance.
Have a nice day!
Kevin - when you see a failure message like that, the representation of the object (#<User ...>) is up to the object, so it's possible that it doesn't show you everything that is being compared by ==. My guess is it has something to do with :password_confirmation, but I'm not certain. It doesn't look like the implementation is using it yet, so try removing password_confirmation from #attr in the spec, and from the attr_accessible declaration, and see if it passes.

Updating a User model resets password? Rails question

In my controller, I try updating a user instance's rank attribute (integer). For example from 1 to 2.
I do this by:
#user = User.find(params[:id])
#user.rank = 2
#user.save(:validate => false)
For some reason the password for the user being saved gets erased, so that they can log in to my site without a password at all. I've tried with and without the :validate => false parameter.
Any reason why? help? Thanks a bunch
Model Code
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :login, :email, :fname, :lname, :password, :password_confirmation, :rank, :hours, :wars
email_filter = /\A[\w+-.]+#[a-z\d-.]+.[a-z]+\z/i
validates :login, :presence => true, :length => { :maximum => 15, :minimum => 4 }, :uniqueness => true
validates :fname, :presence => true, :length => {:minimum => 2 }
validates :lname, :presence => true, :length => {:minimum => 2 }
validates :email, :presence => true, :format => { :with => email_filter}, :uniqueness => { :case_sensitive => false }
validates :password, :presence => true, :confirmation => true, :length => { :within =>4..40 }
validates :lane_id, :presence => true
before_save :encrypt_password
has_many :reports
has_many :accomplishments
belongs_to :lane
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(login, submitted_password)
user = find_by_login(login)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
def current_report
report = (Report.order("created_at DESC")).find_by_user_id(#user.id)
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
You only want to encrypt the password if one is present, so add a condition to your callback
before_save :encrypt_password, :unless => "password.blank?"
Also, you do not want to validate the password every time you update the user record. You can remove the :presence => true validation, and add a condition to run the other validations only when the password is present.
validates :password, :confirmation => true, :length => { :within =>4..40 }, :unless => "password.blank?"
You have a before_filter that encrypts the password everytime you save your model. Instead of a before_filter use something like this:
def password=(new_password)
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(new_password)
end
I know this is severely late, but I actually just stumbled across this article on therailsways.com that was written back in 2009, but still worked for me in case anyone else who comes here through Google might have this same problem.
before_save :encrypt_password, :if => :password_changed?
I was having the same problem where my password would be re-encrypted on update, but I only wanted to encrypt it on user creation.
I was looking for alternatives to before_save, but none of them really did the trick. This however, certainly did, and all I had to do was add that if condition. It worked perfectly.

Rails Tutorial ch 7.2.3 has_password? undefined method

In Michael Hartl's Ruby on Rails Tutorial, ch 7.2.3, rspec is returns the following errors:
Failures:
1) User has_password? method should be true if the passwords match
Failure/Error: #user.has_password?(#attr[:password].should be_true)
NoMethodError:
undefined method `has_password?' for nil:NilClass
# ./spec/models/user_spec.rb:132:in `block (3 levels) in <top (required)>'
2) User has_password? method should be false if the passwords don't match
Failure/Error: #user.has_password?("invalid").should be_false
NoMethodError:
undefined method `has_password?' for nil:NilClass
# ./spec/models/user_spec.rb:136:in `block (3 levels) in <top (required)>'
Finished in 0.23931 seconds
18 examples, 2 failures
Failed examples:
rspec ./spec/models/user_spec.rb:131 # User has_password? method should be true if the passwords match
rspec ./spec/models/user_spec.rb:135 # User has_password? method should be false if the passwords don't match
In the console I'm also getting an undefined local variable error on 'password confirmation'
I've thoroughly checked my code and can't find the discrepancy but I'm obviously doing it wrong.
here's my users model:
require 'digest'
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
# Automatically create the virtual attribute 'password_confirmation'.
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
self.encrypted_password == encrypt(submitted_password)
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
Make sure this bit is in your spec, sounds like it's missing
before(:each) do
#user = User.create!(#attr)
end

Resources