I use FactoryBot's sequence method to generate unique values, which work great in tests.
Example:
FactoryBot.define do
factory :my_factory do
sequence(:label) { |n| "Label #{n}" }
end
end
However, if I try to generate records in my dev environment, I hit lots of errors because the sequence counter has reset. I would love to set it manually, something like set_sequence(123456), is anything like that possible?
Sequence accepts a seed value parameter so something like this should work
FactoryBot.define do
factory :my_factory do
sequence(:label, 123456) { |n| "Label #{n}" }
end
end
You could now set the start value in your configuration
# config/environments/production.rb
Rails.application.configure do
config.x.factory_bot.sequence_seed_value = 1234
end
# config/environments/test.rb
Rails.application.configure do
config.x.factory_bot.sequence_seed_value = 0
end
FactoryBot.define do
factory :my_factory do
sequence(:label, Rails.configuration.x.factory_bot.sequence_seed_value) { |n| "Label #{n}" }
end
end
https://github.com/thoughtbot/factory_bot/blob/893eb67bbbde9d7f482852cc5133b4ab57e34b97/lib/factory_bot/sequence.rb#L13
https://github.com/thoughtbot/factory_bot/blob/893eb67bbbde9d7f482852cc5133b4ab57e34b97/spec/acceptance/sequence_spec.rb#L48
https://guides.rubyonrails.org/configuring.html#custom-configuration
Related
I have the following factory:
FactoryGirl.define do
factory :foo do
sequence(:name) { |n| "Foo #{n}" }
trait :y do
sequence(:name) { |n| "Fooy #{n}" }
end
end
end
If I run
create :foo
create :foo
create :foo, :y
I get Foo 1, Foo 2, Fooy 1. But I want Foo1, Foo2, Fooy 3. How can I achieve this?
After a couple of hints from smile2day's answer and this answer, I came to the following solution:
FactoryGirl.define do
sequence :base_name do |n|
" #{n}"
end
factory :foo do
name { "Foo " + generate(:base_name) }
trait :y do
name { "Fooy " + generate(:base_name) }
end
end
end
You defined two different sequence generators since they are not within the same scope.
I would not use :name for the generator. A name that implies a number seems more suitable.
sequence :seq_number
Include a transient attribute in the factory and assign the generated sequence nunber.
transient do
seq_no { generate(:seq_number) }
end
Use the transient attribute for the 'name' attribute. The same applied to the trait version of 'name'.
name { "Foo #{seq_no}" }
trait :y do
name { "Fooy #{seq_no}" }
end
Cheers,
Eugen
I have a class that looks like this:
class Killmail::Parser
def initialize(body)
#body = body
end
end
I also have a factory for it:
FactoryGirl.define do
factory :parser, class: Killmail::Parser do
skip_create
body '2013.12.02 19:24 bla bla'
initialize_with { new(attributes) }
end
end
It all works fine as long as I don't try to change default attributes. However when I try to use it like this
FactoryGirl.create(:parser, body: 'some different body')
It returns this:
=> #<Killmail::Parser:0x007fb2ff116548 #body={:body=>"some different body"}>
What am I doing wrong? Can't really google anything useful on this case.
Have you tried calling new(body) instead of new(attributes)? The problem could lie with how you're initializing your Parser class.
attributes creates a hash of all the attributes and passes that...
initialize_with { new(attributes) }
# roughly translates to
Parser.new({ body: '2013.12.02 19:24 bla bla' })
While calling #new with just body passes just that value as the first param...
initialize_with { new(body) }
# roughly translates to
Parser.new('2013.12.02 19:24 bla bla')
And you can add as many values to #new as you want in the same fashion...
# in your FactoryGirl.define
new_var_1 'new_string_1'
new_var_2 2
initialize_with { new(body, new_var_1, new_var_2) }
# roughly translates to
Parser.new('2013.12.02 19:24 bla bla', 'new_string_1', 2)
This will work, although it's a bit fiddly:
Change
initialize_with { new(attributes) }
To
initialize_with { new(attributes[:body]) }
I don't think factory girl was designed for this sort of thing TBH
Basically:
I have a Structure model that has many Subjects.
Each subject has a parent and it can be 2-levels deep.
A Structure has to have one Subject at_depth 0 and one Subject at_depth 2.
The problem:
I can't figure out how to build my Subject Factory and how to make the association in the Structure Factory.
I'm on Rails 4, factory_girl_rails 4.2.1 and Ruby 2.0.0
Here is what I tried for the subject factory:
factory :subject_grand_parent do |f|
name Forgery(:name).company_name
factory :subject_parent do |s|
f.parent { Factory.create(:subject_grand_parent) }
factory :subject do |s|
f.parent { Factory.create(:subject_parent) }
end
end
end
But I can't define parent two times.
And in the Structure factory I'm not sure how to define multiple subjects for my association. Here
what I have now:
factory :structure do
subjects {|structure| [structure.association(:subject)] }
...
end
Thanks in advance
Alright, this seems to work:
Subject Factory:
factory :subject do
name Forgery(:name).company_name
factory :subject_children do
name Forgery(:name).company_name + ' child'
after :build do |subject|
subject_grand_parent = Subject.create(name: Forgery(:name).company_name)
subject_parent = subject_grand_parent.children.create(name: Forgery(:name).company_name)
subject.parent = subject_parent
subject.ancestry_depth = 2
end
end
end
Structure Factory:
after(:build) do |structure|
structure.subjects << FactoryGirl.build(:subject)
structure.subjects << FactoryGirl.build(:subject_children)
end
Have you considered using after(:build) blocks ?
I'm new to TDD, RSpec and factories, and trying to understand how to test that each User's phone number attribute is unique. To do so, I'm trying to use a sequence in my User factory. I'm not having much luck with the following:
FactoryGirl.define do
factory :user do
number = 123456789
sequence(:phone_number) {|n| (number + n).to_s }
end
end
Any thoughts on the best way to accomplish this? Also, what kind of test would make sense for something like this where ultimately I would want to add the following validation to the user model to make such a test pass?
validates :phone_number, :uniqueness => true
Thanks!
Try using a lambda with a random 10 digit number:
phone_number { rand(10**9..10**10) }
Try this:
FactoryGirl.define do
sequence :phone_number do |n|
"123456789#{n}"
end
factory :user do
phone_number
end
end
and in order to test your validation use this in your user_spec
it { should validate_uniqueness_of(:phone_number) }
To complete #westonplatter answer, in order to start at 0 000 000 000, you can use String#rjust:
FactoryGirl.define do
factory :user do
sequence(:phone_number) {|n| n.to_s.rjust(10, '0') }
end
end
Example:
> 10.times { |n| puts n.to_s.rjust(10, '0') }
0000000000
0000000001
0000000002
0000000003
0000000004
0000000005
0000000006
0000000007
0000000008
0000000009
While the random solution works, you have a small chance of not getting a unique number. I think you should leverage the FactoryGirl sequence.
We can start at, 1,000,000,000 (100-000-000) and increment up. Note: This only gives you 98,999,999,999 unqiue phone numbers, which should be sufficient. If not, you have other issues.
FactoryGirl.define do
sequence :phone_number do |n|
num = 1*(10**8) + n
num.to_s
end
factory :user do
phone_number
end
end
There are (at least?) two ways to use a sequence in factory girl:
Factory.sequence :my_id do |n|
"#{n}"
end
Factory.define :my_object do |mo|
mo.id Factory.next :my_id
end
and simply doing it inline:
Factory.define :my_object do |mo|
mo.sequence(:id) { |n| "#{n}" }
end
My question is this. If I use the inline version in two different factories, will there be two different sequences that both start at 1 and increment in tandem...meaning that if I create one of each type of factory object they will both have id 1?
If I use the externally defined sequence in two different factories am I guaranteed to get unique ids across the two objects? Meaning will the ids of each object be different?
I am trying to confirm if the behavior above is accurate because I'm working with a completely goofy data model trying to get rspec & factory girl to play nice with it. The designer of the database set things up so that different objects have to have ids generated that are unique across a set of unrelated objects. Changing the data model at this point is not a feasible solution though I'd really love to drag this stuff back onto the Rails.
When using externally defined sequences in two different factories you will see incrementing ids across the factories. However, when using inline sequences each factory will have their own sequence.
I created the example rake task below to illustrate this. It displays the following results:
*** External FactoryGirl Sequence Test Results ***
User Name: Name 1
User Name: Name 2
User Name: Name 3
User Name: Name 4
Role: Name 5
Role: Name 6
Role: Name 7
Role: Name 8
*** Internal FactoryGirl Sequence Test Results ***
User Name: Name 1
User Name: Name 2
User Name: Name 3
User Name: Name 4
Role: Role 1
Role: Role 2
Role: Role 3
Role: Role 4
As you can see, when using external sequences the number continues to increase as you move from the user to the role. However when using an inline sequence the increments are independent of each other.
The following schema files were used for this example:
create_table "users", :force => true do |t|
t.string "name"
t.string "email"
end
create_table "roles", :force => true do |t|
t.string "name"
end
The example rake task is:
require 'factory_girl_rails'
namespace :sequencetests do
Rake::Task[:environment].invoke
task :external do
FactoryGirl.factories.clear
desc "Factory Girl Sequence Test using an externally defined sequence"
puts "*** External FactoryGirl Sequence Test Results ***"
FactoryGirl.define do
sequence :name do |n|
"Name #{n}"
end
factory :user do |u|
name
end
factory :role do |r|
name
end
end
users = buildit(:user)
roles = buildit(:role)
puts( showit(users, "User Name: "))
puts( showit(roles, "Role: "))
end
task :inline do
FactoryGirl.factories.clear
puts "*** Internal FactoryGirl Sequence Test Results ***"
desc "Factory Girl Sequence Test using an inline sequence"
FactoryGirl.define do
factory :user do |u|
u.sequence(:name) {|n| "Name #{n}" }
end
factory :role do |r|
r.sequence(:name) {|n| "Role #{n}" }
end
end
users = buildit(:user)
roles = buildit(:role)
puts( showit(users, "User Name: "))
puts( showit(roles, "Role: "))
end
end
task sequencetests: ['sequencetests:external', 'sequencetests:inline']
def buildit(what)
items = []
4.times do
items << FactoryGirl.build(what)
end
items
end
def showit(items, prefix = "Name: ")
results = ""
items.each do |item|
results += "#{prefix}#{item.name}\n"
end
results
end
I hope this helps explain the different possibilities when using sequences in FactoryGirl.
Yes, the inline versions will create 2 independent sequences, each starting at 1