Factory Girl sequences not incrementing - ruby-on-rails-3

I'm trying to get FactoryGirl to generate some names for me, but the sequence doesn't seem to increment.
# spec/factories/vessel.rb
require 'factory_girl'
FactoryGirl.define do
sequence :vessel_name do |n|
"TK42#{n}"
end
factory :vessel do
name FactoryGirl.generate(:vessel_name)
vessel_type 'fermenter'
volume_scalar 100.0
volume_units 'bbl'
end
end
# spec/models/vessel_spec.rb
require 'spec_helper'
describe Vessel do
context 'working in the factory' do
it 'makes a valid vessel' do
vessel = FactoryGirl.create(:vessel)
vessel.should be_valid, "Invalid vessel #{vessel.valid? || vessel.errors.messages.inspect}"
end
it 'makes another valid vessel' do
vessel = FactoryGirl.create(:vessel)
vessel.should be_valid, "Invalid vessel #{vessel.valid? || vessel.errors.messages.inspect}"
end
end
end
The spec output is
Vessel
working in the factory
makes a valid vessel
makes another valid vessel (FAILED - 1)
Failures:
1) Vessel working in the factory makes another valid vessel
Failure/Error: vessel = FactoryGirl.create(:vessel)
ActiveRecord::RecordInvalid:
Validation failed: Name has already been taken
# ./spec/models/vessel_spec.rb:13:in `block (3 levels) in <top (required)>'
# app/models/vessel.rb
class Vessel < ActiveRecord::Base
attr_accessible :name, :vessel_type, :volume_scalar, :volume_units
validates :name, :presence => true, :uniqueness => true
end
0 HAL:0 work/nrb-brewery-management % bundle show factory_girl_rails rspec
/home/brundage/.rvm/gems/ruby-1.9.3-p0/gems/factory_girl_rails-3.5.0
/home/brundage/.rvm/gems/ruby-1.9.3-p0/gems/rspec-2.11.0
0 HAL:0 work/nrb-brewery-management % rails c test
Loading test environment (Rails 3.2.6)
1.9.3p0 :001 > FactoryGirl.generate :vessel_name
=> "TK422"
1.9.3p0 :002 > FactoryGirl.generate :vessel_name
=> "TK423"
1.9.3p0 :003 > FactoryGirl.generate :vessel_name
=> "TK424"
1.9.3p0 :004 > FactoryGirl.generate :vessel_name
=> "TK425"
Why doesn't FactoryGirl generate a sequence of names in my spec?

That works, bit it will mean that you can't override the name anywhere in the specs, because the after build hook will always run and overwrite any name.
The reason your original example doesn't work is that you're invoking the sequence when the factory is defined, rather than when the factory is run. You can provide a block to attribute definitions which will be invoked every time the factory runs. This way, you get a chance to generate a value for each instance, rather than generating one value for all instances. This is most frequently used for sequences and times.
You can fix your original example with this snippet:
sequence :vessel_name do |n|
"TK42#{n}"
end
factory :vessel do
name { generate(:vessel_name) }
vessel_type 'fermenter'
volume_scalar 100.0
volume_units 'bbl'
end
If all names can be generated with the same format, you could also leave out the value entirely by renaming your sequence:
sequence :name do |n|
"TK42#{n}"
end
factory :vessel do
name
vessel_type 'fermenter'
volume_scalar 100.0
volume_units 'bbl'
end
However, that won't work if you need different name formats for different factories.

And the answer is:
require 'factory_girl'
FactoryGirl.define do
sequence :vessel_name do |n|
"TK42#{n}"
end
factory :vessel do
vessel_type 'fermenter'
volume_scalar 100.0
volume_units 'bbl'
after :build do |v|
v.name = FactoryGirl.generate(:vessel_name)
end
end
end

Related

Rails 3 & Strong Parameters, getting mass assignment errors

I'm trying to use strong parameters in a single model in my Rails 3 project that has around 40-50 models.
I've done the following, but when I try to create or update an instance of this model, I get the same error regarding mass assignment, as below, which shows every field of the model.
I've tried removing the accepted_nested_attributes_for from the model and restarting the webserver, but it didn't have an effect on the error I'm receiving.
config/application.rb
config.active_record.whitelist_attributes = false
app/models/my_service.rb (concatenated for brevity)
class CallService < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
belongs_to :account
has_many :my_service_chargeables
accepts_nested_attributes_for :my_forward_schedules, allow_destroy: true
validates :start_date, :username, :account_id, :plan_id, presence: true
audited associated_with: :account
scope :enabled, where(enabled: true)
scope :within, lambda{|month| where(start_date: (month.beginning_of_month..month.end_of_month))}
end
app/controllers/my_services_controller.rb
def update
#my_service = MyService.find(params[:id])
if #my_service.update_attributes(permitted_params.my_service)
flash[:success] = "Service Updated"
redirect_to #my_service
else
render 'edit'
end
end
app/controllers/application_controller.rb
def permitted_params
#permitted_params ||= PermittedParams.new(current_user, params)
end
app/models/permitted_params.rb
class PermittedParams < Struct.new(:user, :params)
def my_service
if user && user.role?(:customer)
params.require(:my_service).permit(*service_customer_attributes)
else
params.require(:my_service).permit!
end
end
def service_customer_attributes
[:timeout, :pin, :day]
end
end
ERROR WHEN UPDATING
ActiveModel::MassAssignmentSecurity::Error in MyServicesController#update
Can't mass-assign protected attributes: account_id, plan_id, start_date, username
I've run a debugger to confirm the code hits the params.require(:my_service).permit! line from the PermittedParams class, yet this exception still keeps getting thrown, when as far as I can tell, there should be nothing causing this model to require declaring attributes as attr_accessible's.
Can anyone shed some light on this behavior?
I'm using gem versions (from my Gemfile.lock):
strong_parameters (0.2.0)
rails (3.2.11)
I'm not sure what your exact use case is, but doing params.require(:my_service).permit! still seems like a bad idea here, at the very least someone could still override your model's PK. Rather than params.require(:my_service).permit! why not do:
params.require(:my_service).permit(:timeout, :pin, :day, :account_id,
:plan_id, :start_date, :username)
Or keep these in another array and merge them with your existing service_customer_attributes to keep it DRY.
This would take care of your mass assignment error, and will be more secure and more explicit.

Testing Multiple Custom Validators with RSpec

I am trying to run specs for two custom validators:
spec/validators/email_validator_spec.rb
spec/validators/phone_validator_spec.rb
When I run bundle exec rspec spec/validators/ the phone_validator_spec.rb spec fails:
1) PhoneValidator with a valid phone number should be valid
Failure/Error: subject.should be_valid
expected valid? to return true, got false
# ./spec/validators/phone_validator_spec.rb:20:in `block (4 levels) in <top (required)>'
# ./spec/validators/phone_validator_spec.rb:18:in `each'
# ./spec/validators/phone_validator_spec.rb:18:in `block (3 levels) in <top (required)>'
However, when I run that spec individually using the command bundle exec rspec spec/validators/phone_validator_spec.rb, it passes.
When I remove the email_validator_spec.rb then phone_validator_spec.rb passes using the command bundle exec rspec spec/validators/.
I expect both specs to pass when I run bundle exec rspec spec/validators/. Can anyone explain to me what is happening?
Update:
Used zetetic's tip to print out the error hash:
1) PhoneValidator with a valid phone number should be valid
Failure/Error: subject.errors.should == {}
expected: {}
got: #<ActiveModel::Errors:0x37b2460 #base=#<Validatable:0x37b2700 #validation_context=nil, #errors=#<ActiveModel::Errors:0x37b2460 ...>, #phone_number="1112223333">, #messages={:email=>["is invalid"]}> (using ==)
Diff:
## -1 +1,8 ##
+#<ActiveModel::Errors:0x37b2460
+ #base=
+ #<Validatable:0x37b2700
+ #errors=#<ActiveModel::Errors:0x37b2460 ...>,
+ #phone_number="1112223333",
+ #validation_context=nil>,
+ #messages={:email=>["is invalid"]}>
# ./spec/validators/phone_validator_spec.rb:21:in `block (4 levels) in <top (required)>'
# ./spec/validators/phone_validator_spec.rb:18:in `each'
# ./spec/validators/phone_validator_spec.rb:18:in `block (3 levels) in <top (required)>'
It appears the Validatable class definitions are combined when both specs are run. Is this behavior expected? If I use distinct class names, both specs pass.
spec/validators/phone_validator_spec.rb
require 'active_model'
require 'rspec/rails/extensions'
require File.expand_path('app/validators/phone_validator')
class Validatable
include ActiveModel::Validations
attr_accessor :phone_number
validates :phone_number, phone: true
end
describe PhoneValidator do
subject { Validatable.new }
describe "with a valid phone number" do
it "should be valid" do
phone_numbers = ["1112223333", "123222ABCD"]
phone_numbers.each do |phone_number|
subject.phone_number = phone_number
subject.should be_valid
end
end
end
end
app/validators/phone_validator.rb
class PhoneValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
return if value.blank?
unless value =~ /^[A-Za-z0-9]{10}$/
object.errors[attribute] << (options[:message] || "is not formatted properly")
end
end
end
spec/validators/email_validator_spec.rb
require 'active_model'
require 'rspec/rails/extensions'
require File.expand_path('app/validators/email_validator')
class Validatable
include ActiveModel::Validations
attr_accessor :email
validates :email, email: true
end
describe EmailValidator do
subject { Validatable.new }
describe "with a valid email address" do
it "should be valid" do
addresses = %w[user#foo.COM A_US-ER#f.b.org frst.lst#foo.jp a+b#baz.cn]
addresses.each do |valid_address|
subject.email = valid_address
subject.should be_valid
end
end
end
describe "with an invalid phone number" do
it "should be invalid" do
addresses = %w[user#foo,com user_at_foo.org example.user#foo]
addresses.each do |invalid_address|
subject.email = invalid_address
subject.should be_invalid
end
end
end
end
app/validators/email_validator.rb
require 'mail'
class EmailValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
begin
m = Mail::Address.new(value)
# We must check that value contains a domain and that value is an email address
r = m.domain && m.address == value
t = m.__send__(:tree)
# We need to dig into treetop
# A valid domain must have dot_atom_text elements size > 1
# user#localhost is excluded
# treetop must respond to domain
# We exclude valid email values like <user#localhost.com>
# Hence we use m.__send__(tree).domain
r &&= (t.domain.dot_atom_text.elements.size > 1)
rescue => e
r = false
end
object.errors[attribute] << (options[:message] || "is invalid") unless r
end
end
Using rails 3.2.11, rspec-rails 2.11.0
Your model instance is invalid but you don't know why. Try changing
subject.should be_valid
to
subject.valid?
subject.errors.should == {}
Now the failure message will print out the error hash.
Another tip: Don't rescue Exception.
EDIT
It appears the Validatable class definitions are combined when both specs are run. Is this behavior expected?
Yes, that is normal for Ruby classes. When both spec files are required, each Validatable class body is executed, so you end up with a class that contains both validations.
You need to isolate the validations either by making the subjects pass the validation that is not under test, eg:
subject { Validatable.new(:email => "some value") }
or testing for the specific error message from the validation under test:
subject.valid?
subject.errors(:email).should include("is invalid")
PS. Seriously -- don't rescue Exception. Nothing good will come of that.
I run in to this problem myself, and yes you could rename the class but the solution I used is to create and teardown the Validatable class inside your spec.
Here's a code snippet:
describe "HttpUriValidator",
"Custom validator to ensure URL is a valid URI." do
# Create the dummy class once when the test is run.
before(:all) do
class Validatable
include ActiveModel::Validations
attr_accessor :url
validates :url, http_uri: true
end
end
# Must tearing down the class or it will taint other tests using its
# name.
after(:all) { Object.send(:remove_const, :Validatable) }
subject { Validatable.new }
EDIT::
Just a heads up when you are declaring a Module wrapping your tested class (to avoid namespacing other classes in the test) ie.
module Foo::Bar
describe Something do
after(:all) { Foo::Bar.send(:remove_const, :Testable) }
end
end
you will have to remove the constant from that namespace rather then Object.

Accessing Rails Helper files from models

I am trying to validate the input of a state field using:
include ActionView::Helpers
class Credentials < ActiveRecord::Base
attr_accessible :license_number, ...:address_zip_code,
...
validates :license_number, presence: true, length: { minimum: 4 }
...
validates_inclusion_of :current_practice_address_state, :in => state_list
end
The variable state_list is an array described in helpers/credentials_helper.rb
Testing the model I run into undefined local variable error
$ bundle exec rspec spec/models/credentials_spec.rb
in `method_missing': undefined local variable or method `state_list' for #<Class:0x007f86a1844250> (NameError)
The helper class looks like:
module CredentialsHelper
state_list = %w(AL AK...WY)
end
Your call to include has to be inside the class:
class Credentials < ActiveRecord::Base
include ActionView::Helpers
...
end

Rail 3.2.2/Devise: deprecation warning with rspec

I recently upgraded an app to rails 3.2.2.
I'm using Factory_girl
Factory.sequence :name do |n| "name-#{n}" end
Factory.define :user do |u| u.first_name{ Factory.next(:name) }
u.last_name { |u| 'last_' + u.first_name } u.password 'secret'
u.password_confirmation { |u| u.password } u.sequence(:email) { |i|
"user_#{i}#example.com" }
end
and this simple test
specify { Factory.build(:user).should be_valid }
generate the following warning
DEPRECATION WARNING: You're trying to create an attribute user_id'.
Writing arbitrary attributes on a model is deprecated. Please just use
attr_writer` etc. (called from block (2 levels) in
at...
How can I get rid of it?
It's probably because you haven't prepared/migrated your test database with updated column definitions, thus it thinks you're trying to arbitrarily set the attribute.
Run rake db:test:prepare to make sure it's updated.
Here's the source code of that method, where you can see Rails checks for the column or attribute first, then warns if they're not found.
I've met the same warning with the following code:
Ad model:
class Ad < ActiveRecord::Base
belongs_to :user
end
Factories:
FactoryGirl.define do
factory :ad do
association :user
end
end
FactoryGirl.define do
factory :user do
first_name {Factory.next(:first_name)}
last_name {Factory.next(:last_name)}
email {|x| "#{x.first_name}.#{x.last_name}#{Factory.next(:count)}#test.com"}
password Forgery(:basic).password
confirmed_at Date.today << 10
end
end
Test
require 'spec_helper'
describe Ad do
before(:each) do
#ad = Factory.build(:ad)
end
"it is not valid without a user"
end
Running the test gave me a similar error.
Adding
attr_accessor :user
to the Ad model fixed the warning.
I hope it helps.
I had this same warning while doing tests in Rspec and my issue was that I had a Parent model and Child model where I accidentally had this:
class Child < ActiveRecord::Base
belongs_to :parent
end
......
class Parent < ActiveRecord::Base
belongs_to :child
end

rails 3.0 attr_encrypted existing database

In a rails 3.0 I need to encrypt an existing text field.
There a table memos that contains a text field "note". I've create an encrypted_note field
and added in the model:
attr_encrypted :note, :key => 'a secret key'
For now when I load an existing record the "note" is empty. I'm assuming that attr_encrypted try to decrypt...but the field has note been encrypted yet!
attr_encrypted works well for new records but wondering what would be the best strategy to encrypt the existing records?
Does instance_variable_get('#note') or read_attribute('note') work?
If so, you can probably do something like this in the Rails console:
User.all.each do |user|
user.note = user.instance_variable_get('#note')
user.save
end
Here is the trick to clear the unencrypted column as the encrypted one get populated!
in the model add:
before_update :clear_note
def clear_note
if encrypted_note != nil && read_attribute('note') != nil
write_attribute('note','')
end
end
Assuming you start with your model Thing with the un-encrypted attribute note.
1) Add a migration to add a field encrypted_note and populate it
class EncryptThing < ActiveRecord::Migration
def up
rename_column :things, :note, :old_note
add_column :things, :encrypted_note, :string
# if you want to use per-attribute iv and salt:
# add_column :things, :encrypted_note_iv, :string
# add_column :things, :encrypted_note_salt, :string
Thing.find_each do |t|
t.note = t.old_note
t.save
end
remove_column :things, :old_note
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
2) Add a line to your model to specify the encrypted attribute:
attr_encrypted :note, :key => Rails.application.config.key
# if you want to use per-attribute iv and salt, add this to the line above:
# , :mode => :per_attribute_iv_and_salt
3) run your migration
rake db:migrate