I have the following simplified model:
class User
attr_accessible :password
validates :password, :presence => true
validates_confirmation_of :password, :length => { :minimum => 4 }
end
That's pretty straight forward. I'm testing it with RSpec/FactoryGirl, and have the following tests:
it "cannot create with password < 4 characters" do
expect{ model = FactoryGirl.create(:user, :password => "12", :password_confirmation => "12") }.to raise_error
end
it "cannot update to password < 4 characters" do
model = FactoryGirl.create(:user)
model.should be_valid
model.update_attributes({:password => "12", :password_confirmation => "12"})
model.reload
model.password.should_not eq("12")
end
However, the second test fails. For some reason, "12" seems to be able to be saved into the database as a valid password. Why? The API states that update_attributes should not bypass any of the validations!
I don't think it is skipping validations.
I suspect this:
model.update_attributes({:password => "12", :password_confirmation => "12"})
is returning false. Password is probably not a column in your database, rather you probably have crypted_password and password_salt so reloading it does not read the password from the database (this would be bad)
Also, you probably want :
attr_accessible :password, :password_confirmation
Related
I've rolled out my own authentication/authorization system based on Hartl's for my app. I wanted to allow admins to make other users admins, so I did this in my user.rb file:
attr_accessible :name, :email, :password, :password_confirmation, :order_id
attr_accessible :name, :email, :password, :password_confirmation, :order_id, :admin, :as => :administrator
and put this in my user update action:
def update
if current_user.admin?
if #user.update_attributes(params[:user], :as => :administrator)
This works great for me, but it's getting annoying to have to go into console and type
User.find(2).toggle!(:admin)
or whatever, whenever I want to make my first admin user after a db reset, or, for that matter, to have to use the console or individual edits to make other admins. I'd love it if I could seed ":as => administrator", so I tried this in my seed.rb file, but it doesn't work (mass-assign error):
admin = User.create(
:name => "My Name",
:email => "my email",
:password => "password",
:password_confirmation => "password",
:admin => true,
:as => :administrator
)
Any idea if there's a way to do this? It'd make my life a lot easier.
The simplest solution I found was to toggle admin in the seeds.rb file right after creating the user. This way, I avoid "mass" assignment without having to assign in the console. So:
admin = User.create(
:name => "My Name",
:email => "my email",
:password => "password",
:password_confirmation => "password"
)
admin.toggle!(:admin)
# I assume "admin.update_attribute(:admin, true)" would work as well.
Since you have a mass-assign error, I think you should only keep the second line of attr_accessible in User.rb and discard the first line, which is causing the error.
I was looking to perform the same thing and end up doing like this in seeds.rb:
# db/seeds.rb
users = User.create({email: 'email#admin.com', username: 'admin', password: 'sEcReT', password_confirmation: 'sEcReT', role: 'admin'},
:as => :admin)
# models/user.rb
attr_accessible :email, :username, :password, :password_confirmation, :role, :as => :admin
I'm new to using RSpec and FactoryGirl. I'm trying to add RSpec tests to an existing codebase.
I have the following factories defined:
Factory.sequence :email do |n|
"somebody#{n}#example.com"
end
Factory.sequence :login do |n|
"inquire#{n}"
end
Factory.define :user do |f|
f.login { Factory.next(:login) }
f.email { Factory.next(:email) }
f.password 'inquire_pass'
f.password_confirmation 'inquire_pass'
f.first_name 'test'
f.last_name 'guy'
f.newsletter true
f.notify_of_events true
f.terms_of_service true
end
Factory.define :project do |project|
project.title "Example Project Title"
project.association :user
project.association :provider
project.association :project_request
project.association :offering
project.association :offering_type
end
When I try to create a Project factory in my tests, however and assign it to #project:
require 'spec_helper'
describe Charge do
before(:each) do
#provider_user = Factory(:user)
#provider = stub_model(Provider, :user => #provider_user)
#user = Factory(:user)
#project_request = stub_model(ProjectRequest)
#project = Factory(:project, :user => #user, :provider => #provider, :offering_fixed_fee_number => 700,
:project_request_id => #project_request.id)
#attr = {
:user_id => #user.id,
:provider_id => #provider.id,
:charge_client => "0.01"
}
#charge = #project.build_charge(#attr)
end
I get an error message when running the tests indicating that the validations for the associated user have failed:
Validation failed: User email can't be blank, User email is too short (minimum is 3 characters), User email does not look like a valid email address., Login can't be blank
The relevant validations on the User model are:
validates_presence_of :login, :email
validates_uniqueness_of :login, :email, :case_sensitive => false
validates_length_of :login, :within => 5..20
validates_format_of :login, :with => /^[a-z0-9-]+$/i, :message => 'may only contain letters, numbers or a hyphen.'
validates_length_of :email, :within => 3..100
validates_format_of :email, :with => Authentication.email_regex, :message => 'does not look like a valid email address.'
I'm able to create valid User factories (by themselves) with no problem. But when I try to create projects that have a User association, and specify the associated User as the factory user I created earlier, the validations on that User fail. Any ideas what I'm missing here?
Thanks very much,
Dean Richardson
Maybe it's because factory param in association method is missing:
Factory.define :project do |project|
project.association :user, :factory => :user
...
end
But in this case it could be written more easily:
Factory.define :project do |project|
project.user
...
end
See Factory Girl. Getting Started
I'm trying to use has_secure_password on my user model, but have found that while it works in the application it breaks all my tests. I have a simple user model:
class User < ActiveRecord::Base
has_secure_password
attr_accessible :email
validates :email, :presence => true,
:format => { :with => /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true
end
My first test (rspec) simply confirms that I can create a new user with valid attributes:
describe User do
before(:each) do
#attr = { :forename => "Captain",
:surname => "Hook",
:email => "email#test.com",
:password => "password",
:password_confirmation => "password" }
end
it "should create a new instance given valid attributes" do
User.create!(#attr)
end
end
This doesn't work, however when I do
user = User.new(#attr);
user.password = "password";
user.save
it works fine. I believe this is because has_secure_password adds a new method, password, which deals with the generation of the password_digest, so calling it directly like this generates the fields that I need. Is there any way I can use User.create but still call this method?
Turns out the problem was really simple. Since I hadn't added :password to attr_accessible it wasn't populating the field when I called User.create or User.new. The only modification I needed to make to the code was
attr_accessible :email, :password
I have this code in my user model:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
validates :email, :presence => true,
:uniqueness => { :case_sensitive => false },
:format => { :with => /\A[^#]+#[^#]+\z/ },
:length => 7..128
validates :password, :presence => true,
:confirmation => true,
:length => 6..128
private
def encrypt_password
return unless password
self.encrypted_password = BCrypt::Password.create(password)
end
end
Now in my controller when I'm updating some user fields with
#user.update_attributes(params[:user])
the password field is always validated, even when it is not set in the params hash. I figured that this is happening because of the attr_accesor :password which always sets password = "" on update_attributes.
Now I could simply skip the validation of password if it is an empty string:
validates :password, :presence => true,
:confirmation => true,
:length => 6..128,
:if => "password.present?"
But this doesn't work because it allows a user to set an empty password.
Using update_attribute on the field I'd like to change is not a solution because i need validation on that attribute.
If I pass in the exact parameter with
#user.update_attributes(params[:user][:fieldname])
it doesn't solve the problem because it also triggers password validation.
Isn't there a way to prevent attr_accesor :password from always setting password = "" on update?
New answer
This works for me:
validates :password, :presence => true,
:confirmation => true,
:length => { :minimum => 6 },
:if => :password # only validate if password changed!
If I remember correctly it also took me some time to get this right (a lot of trial and error). I never had the time to find out exactly why this works (in contrast to :if => "password.present?").
Old answer - not really useful for your purpose (see comments)
I get around this problem by using a completely different action for password update (user#update_password). Now it is sufficient to only validate the password field
:on => [:create, :update_password]
(and also only make it accessible to those actions).
Here some more details:
in your routes:
resources :users do
member do
GET :edit_password # for the user#edit_password action
PUT :update_password # for the user#update_passwor action
end
end
in your UsersController:
def edit_password
# could be same content as #edit action, e.g.
#user = User.find(params[:id])
end
def update_password
# code to update password (and only password) here
end
In your edit_password view, you now have a form for only updating the password, very similar to your form in the edit view, but with :method => :put and :url => edit_password_user_path(#user)
The solution I have started using to get round this problem is this:
Start using ActiveModel's built in has_secure_password method.
At console
rails g migration add_password_digest_to_users password_digest:string
rake db:migrate
In your model:
class User < ActiveRecord::Base
has_secure_password
attr_accessible :login_name, :password, :password_confirmation
# secure_password.rb already checks for presence of :password_digest
# so we can assume that a password is present if that validation passes
# and thus, we don't need to explicitly check for presence of password
validates :password,
:length => { :minimum => 6 }, :if => :password_digest_changed?
# secure_password.rb also checks for confirmation of :password
# but we also have to check for presence of :password_confirmation
validates :password_confirmation,
:presence=>true, :if => :password_digest_changed?
end
And finally,
# In `config/locales/en.yml` make sure that errors on
# the password_digest field refer to "Password" as it's more human friendly
en:
hello: "Hello world"
activerecord:
attributes:
user:
password_digest: "Password"
Oh, one more thing: watch the railscast
I just wrote a test for testing if a new user creation also consists of an admin setting. Here is the test:
describe User do
before(:each) do
#attr = {
:name => "Example User",
:email => "user#example.com",
:admin => "f"
}
end
it "should create a new instance given valid attributes" do
User.create!(#attr)
end
it "should require a name" do
no_name_user = User.new(#attr.merge(:name => ""))
no_name_user.should_not be_valid
end
it "should require an email" do
no_email_user = User.new(#attr.merge(:email => ""))
no_email_user.should_not be_valid
end
it "should require an admin setting" do
no_admin_user = User.new(#attr.merge(:admin => ""))
no_admin_user.should_not be_valid
end
end
Then, in my User model I have:
class User < ActiveRecord::Base
attr_accessible :name, :email, :admin
has_many :ownerships
has_many :projects, :through => :ownerships
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 :admin, :presence => true
end
I clearly created a new user with an admin setting, so why is it saying it's false? I created the migration for the admin setting as admin:boolean. Did I do something wrong?
Here's the error:
Failures:
1) User should create a new instance given valid attributes
Failure/Error: User.create!(#attr)
ActiveRecord::RecordInvalid:
Validation failed: Admin can't be blank
# ./spec/models/user_spec.rb:14:in `block (2 levels) in <top (required)>'
Oddly enough, when I comment out validates :admin, :presence => true, the test creates the user correctly but fails on "User should require an admin setting"
EDIT: When I change the #attr :admin value to "t" it works! Why doesn't it work when the value is false?
From the rails guides:
Since false.blank? is true, if you want to validate the presence of a
boolean field you should use validates :field_name, :inclusion => {
:in => [true, false] }.
Basically, it looks like ActiveRecord is converting your "f" to false before the validation, and then it runs false.blank? and returns true (meaning that the field is NOT present), causing the validation to fail. So, to fix it in your case, change your validation:
validates :admin, :inclusion => { :in => [true, false] }
Seems a little hacky to me... hopefully the Rails developers will reconsider this in a future release.