Rails seed.rb as admin - ruby-on-rails

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

Related

Rails "update_attributes" skipping validation?

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

Validation error on associated factory when using FactoryGirl with RSpec

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

Rails 3.1 - find_or_create_by Not Saving

I have this code ...
recipient = User.find_or_create_by_email(params[:recipient_email],
{ :password => 'password',
:password_confirmation => 'password',
:first_name => 'First',
:last_name => 'Last',
:active => false })
which doesn't work. The recipient isn't saved in the database as it should be. However this ...
recipient = User.find_or_create_by_email(params[:recipient_email],
{ :password => 'password',
:password_confirmation => 'password',
:first_name => 'First',
:last_name => 'Last'})
does work in that it creates the recipient and saves it in the database, but the :active flag is now set to the default true.
In the User model I have ...
attr_accessible :first_name, :last_name, :email, :password, :password_confirmation, :active
and
validates :active, :presence => true
Any ideas as to what's going on here?
Your :active is either true or false (or nil unless you defined a default value in your migration file). Maybe false is interpreted as non-present, I don't really know. BUT this is how I would do this:
Put t.boolean :active, :default => false in your file (or if you need to create a migration, do change_column :table, :active, :boolean, :default => false) and then remove your validation.
Every user is now saved as false unless you provide a param with a different value. If most of your users should be true (so the whole thingm, but vice-versa), then change it so. This makes your validation needless.
Hope I got the point of your problem.. otherwise, ignore me.

Creating users with has_secure_password

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

attr_accessor and password validation on update

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

Resources