Skip to content

Commit 4ec7283

Browse files
composerinteraliaDamian Le Nouailledgalarza
committed
Register inline sequence to allow for rewinding
This was originally opened as #1078, but this addresses the review comments on that PR. By registering the inline sequences, we allow them to get rewound with `FactoryBot.rewind_sequences`. We register them with `__#{factory_name}_#{sequence_name}__` to avoid conflicting with any reasonably named global sequences, and to hint that we should not be generating values from these sequences directly. Co-authored-by: Damian Le Nouaille <[email protected]> Co-authored-by: Damian Galarza <[email protected]>
1 parent a8e63c7 commit 4ec7283

File tree

6 files changed

+171
-67
lines changed

6 files changed

+171
-67
lines changed

lib/factory_bot/configuration.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def initialize
1111
@traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new('Trait'))
1212
@strategies = Registry.new('Strategy')
1313
@callback_names = Set.new
14-
@definition = Definition.new
14+
@definition = Definition.new(:configuration)
1515

1616
@allow_class_lookup = true
1717

lib/factory_bot/definition.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
module FactoryBot
22
# @api private
33
class Definition
4-
attr_reader :defined_traits, :declarations
4+
attr_reader :defined_traits, :declarations, :name
55

6-
def initialize(name = nil, base_traits = [])
6+
def initialize(name, base_traits = [])
7+
@name = name
78
@declarations = DeclarationList.new(name)
89
@callbacks = []
910
@defined_traits = Set.new

lib/factory_bot/definition_proxy.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ def method_missing(name, *args, &block)
121121
#
122122
# Except that no globally available sequence will be defined.
123123
def sequence(name, *args, &block)
124-
sequence = Sequence.new(name, *args, &block)
124+
sequence_name = "__#{@definition.name}_#{name}__"
125+
sequence = Sequence.new(sequence_name, *args, &block)
126+
FactoryBot.register_sequence(sequence)
125127
add_attribute(name) { increment_sequence(sequence) }
126128
end
127129

spec/acceptance/sequence_resetting_spec.rb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,80 @@
2020
expect(email).to eq "[email protected]"
2121
expect(name).to eq "Joe"
2222
end
23+
24+
it "resets inline sequences back to their starting value" do
25+
class User
26+
attr_accessor :email
27+
end
28+
29+
FactoryBot.define do
30+
factory :user do
31+
sequence(:email) { |n| "somebody#{n}@example.com" }
32+
end
33+
end
34+
35+
build_list(:user, 2)
36+
37+
FactoryBot.rewind_sequences
38+
39+
user = build(:user)
40+
41+
expect(user.email).to eq "[email protected]"
42+
end
43+
44+
it "does not collide with globally registered factories" do
45+
class User
46+
attr_accessor :email
47+
end
48+
49+
FactoryBot.define do
50+
sequence(:email) { |n| "global-somebody#{n}@example.com" }
51+
52+
factory :user do
53+
sequence(:email) { |n| "local-somebody#{n}@example.com" }
54+
end
55+
end
56+
57+
2.times do
58+
generate(:email)
59+
end
60+
61+
build_list(:user, 2)
62+
63+
FactoryBot.rewind_sequences
64+
65+
user = build(:user)
66+
email = generate(:email)
67+
68+
expect(user.email).to eq "[email protected]"
69+
expect(email).to eq "[email protected]"
70+
end
71+
72+
it "still allows global sequences prefixed with a factory name" do
73+
class User
74+
attr_accessor :email
75+
end
76+
77+
FactoryBot.define do
78+
sequence(:user_email) { |n| "global-somebody#{n}@example.com" }
79+
80+
factory :user do
81+
sequence(:email) { |n| "local-somebody#{n}@example.com" }
82+
end
83+
end
84+
85+
2.times do
86+
generate(:user_email)
87+
end
88+
89+
build_list(:user, 2)
90+
91+
FactoryBot.rewind_sequences
92+
93+
user = build(:user)
94+
email = generate(:user_email)
95+
96+
expect(user.email).to eq "[email protected]"
97+
expect(email).to eq "[email protected]"
98+
end
2399
end

spec/factory_bot/definition_proxy_spec.rb

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
describe FactoryBot::DefinitionProxy, "#add_attribute" do
2-
subject { FactoryBot::Definition.new }
2+
subject { FactoryBot::Definition.new(:name) }
33
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
44

55
it "raises if both a block and value are given" do
@@ -21,7 +21,7 @@
2121
end
2222

2323
describe FactoryBot::DefinitionProxy, "#add_attribute when the proxy ignores attributes" do
24-
subject { FactoryBot::Definition.new }
24+
subject { FactoryBot::Definition.new(:name) }
2525
let(:proxy) { FactoryBot::DefinitionProxy.new(subject, true) }
2626

2727
it "raises if both a block and value are given" do
@@ -43,7 +43,7 @@
4343
end
4444

4545
describe FactoryBot::DefinitionProxy, "#transient" do
46-
subject { FactoryBot::Definition.new }
46+
subject { FactoryBot::Definition.new(:name) }
4747
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
4848

4949
it "makes all attributes added ignored" do
@@ -56,7 +56,7 @@
5656
end
5757

5858
describe FactoryBot::DefinitionProxy, "#method_missing" do
59-
subject { FactoryBot::Definition.new }
59+
subject { FactoryBot::Definition.new(:name) }
6060
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
6161

6262
it "declares an implicit declaration without args or a block" do
@@ -82,30 +82,44 @@
8282
end
8383

8484
describe FactoryBot::DefinitionProxy, "#sequence" do
85-
subject { FactoryBot::Definition.new }
86-
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
8785

88-
before { allow(FactoryBot::Sequence).to receive(:new) }
86+
before do
87+
allow(FactoryBot::Sequence).to receive(:new).and_call_original
88+
end
89+
90+
def build_proxy(factory_name)
91+
definition = FactoryBot::Definition.new(factory_name)
92+
FactoryBot::DefinitionProxy.new(definition)
93+
end
8994

9095
it "creates a new sequence starting at 1" do
91-
proxy.sequence(:great)
92-
expect(FactoryBot::Sequence).to have_received(:new).with(:great)
96+
proxy = build_proxy(:factory)
97+
proxy.sequence(:sequence)
98+
99+
expect(FactoryBot::Sequence).to have_received(:new).
100+
with("__factory_sequence__")
93101
end
94102

95103
it "creates a new sequence with an overridden starting vaue" do
96-
proxy.sequence(:great, "C")
97-
expect(FactoryBot::Sequence).to have_received(:new).with(:great, "C")
104+
proxy = build_proxy(:factory)
105+
proxy.sequence(:sequence, "C")
106+
107+
expect(FactoryBot::Sequence).to have_received(:new).
108+
with("__factory_sequence__", "C")
98109
end
99110

100111
it "creates a new sequence with a block" do
101112
sequence_block = Proc.new { |n| "user+#{n}@example.com" }
102-
proxy.sequence(:great, 1, &sequence_block)
103-
expect(FactoryBot::Sequence).to have_received(:new).with(:great, 1, &sequence_block)
113+
proxy = build_proxy(:factory)
114+
proxy.sequence(:sequence, 1, &sequence_block)
115+
116+
expect(FactoryBot::Sequence).to have_received(:new).
117+
with("__factory_sequence__", 1, &sequence_block)
104118
end
105119
end
106120

107121
describe FactoryBot::DefinitionProxy, "#association" do
108-
subject { FactoryBot::Definition.new }
122+
subject { FactoryBot::Definition.new(:name) }
109123
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
110124

111125
it "declares an association" do
@@ -120,7 +134,7 @@
120134
end
121135

122136
describe FactoryBot::DefinitionProxy, "adding callbacks" do
123-
subject { FactoryBot::Definition.new }
137+
subject { FactoryBot::Definition.new(:name) }
124138
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
125139
let(:callback) { -> { "my awesome callback!" } }
126140

@@ -159,7 +173,7 @@
159173
end
160174

161175
describe FactoryBot::DefinitionProxy, "#to_create" do
162-
subject { FactoryBot::Definition.new }
176+
subject { FactoryBot::Definition.new(:name) }
163177
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
164178

165179
it "accepts a block to run in place of #save!" do
@@ -170,7 +184,7 @@
170184
end
171185

172186
describe FactoryBot::DefinitionProxy, "#factory" do
173-
subject { FactoryBot::Definition.new }
187+
subject { FactoryBot::Definition.new(:name) }
174188
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
175189

176190
it "without options" do
@@ -191,7 +205,7 @@
191205
end
192206

193207
describe FactoryBot::DefinitionProxy, "#trait" do
194-
subject { FactoryBot::Definition.new }
208+
subject { FactoryBot::Definition.new(:name) }
195209
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
196210

197211
it "declares a trait" do
@@ -202,7 +216,7 @@
202216
end
203217

204218
describe FactoryBot::DefinitionProxy, "#initialize_with" do
205-
subject { FactoryBot::Definition.new }
219+
subject { FactoryBot::Definition.new(:name) }
206220
let(:proxy) { FactoryBot::DefinitionProxy.new(subject) }
207221

208222
it "defines the constructor on the definition" do

spec/factory_bot/definition_spec.rb

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,75 @@
11
describe FactoryBot::Definition do
2+
subject { described_class.new(:name) }
3+
24
it { should delegate(:declare_attribute).to(:declarations) }
3-
end
45

5-
describe FactoryBot::Definition, "with a name" do
6-
let(:name) { :"great name" }
7-
subject { FactoryBot::Definition.new(name) }
6+
describe "with a name" do
7+
it "creates a new attribute list with the name passed" do
8+
name = "great name"
9+
allow(FactoryBot::DeclarationList).to receive(:new)
810

9-
it "creates a new attribute list with the name passed" do
10-
allow(FactoryBot::DeclarationList).to receive(:new)
11-
subject
12-
expect(FactoryBot::DeclarationList).to have_received(:new).with(name)
13-
end
14-
end
11+
FactoryBot::Definition.new(name)
1512

16-
describe FactoryBot::Definition, "#overridable" do
17-
let(:list) { double("declaration list", overridable: true) }
18-
before do
19-
allow(FactoryBot::DeclarationList).to receive(:new).and_return list
13+
expect(FactoryBot::DeclarationList).to have_received(:new).with(name)
14+
end
2015
end
2116

22-
it "sets the declaration list as overridable" do
23-
expect(subject.overridable).to eq subject
24-
expect(list).to have_received(:overridable).once
17+
describe "#name" do
18+
it "returns the name" do
19+
name = "factory name"
20+
definition = described_class.new(name)
21+
22+
expect(definition.name).to eq(name)
23+
end
2524
end
26-
end
2725

28-
describe FactoryBot::Definition, "defining traits" do
29-
let(:trait_1) { double("trait") }
30-
let(:trait_2) { double("trait") }
26+
describe "#overridable" do
27+
let(:list) { double("declaration list", overridable: true) }
28+
before do
29+
allow(FactoryBot::DeclarationList).to receive(:new).and_return list
30+
end
3131

32-
it "maintains a list of traits" do
33-
subject.define_trait(trait_1)
34-
subject.define_trait(trait_2)
35-
expect(subject.defined_traits).to include(trait_1, trait_2)
32+
it "sets the declaration list as overridable" do
33+
expect(subject.overridable).to eq subject
34+
expect(list).to have_received(:overridable).once
35+
end
3636
end
3737

38-
it "adds only unique traits" do
39-
subject.define_trait(trait_1)
40-
subject.define_trait(trait_1)
41-
expect(subject.defined_traits.size).to eq 1
38+
describe "defining traits" do
39+
let(:trait_1) { double("trait") }
40+
let(:trait_2) { double("trait") }
41+
42+
it "maintains a list of traits" do
43+
subject.define_trait(trait_1)
44+
subject.define_trait(trait_2)
45+
expect(subject.defined_traits).to include(trait_1, trait_2)
46+
end
47+
48+
it "adds only unique traits" do
49+
subject.define_trait(trait_1)
50+
subject.define_trait(trait_1)
51+
expect(subject.defined_traits.size).to eq 1
52+
end
4253
end
43-
end
4454

45-
describe FactoryBot::Definition, "adding callbacks" do
46-
let(:callback_1) { "callback1" }
47-
let(:callback_2) { "callback2" }
55+
describe "adding callbacks" do
56+
let(:callback_1) { "callback1" }
57+
let(:callback_2) { "callback2" }
4858

49-
it "maintains a list of callbacks" do
50-
subject.add_callback(callback_1)
51-
subject.add_callback(callback_2)
52-
expect(subject.callbacks).to eq [callback_1, callback_2]
59+
it "maintains a list of callbacks" do
60+
subject.add_callback(callback_1)
61+
subject.add_callback(callback_2)
62+
expect(subject.callbacks).to eq [callback_1, callback_2]
63+
end
5364
end
54-
end
5565

56-
describe FactoryBot::Definition, "#to_create" do
57-
its(:to_create) { should be_nil }
66+
describe "#to_create" do
67+
its(:to_create) { should be_nil }
5868

59-
it "returns the assigned value when given a block" do
60-
block = proc { nil }
61-
subject.to_create(&block)
62-
expect(subject.to_create).to eq block
69+
it "returns the assigned value when given a block" do
70+
block = proc { nil }
71+
subject.to_create(&block)
72+
expect(subject.to_create).to eq block
73+
end
6374
end
6475
end

0 commit comments

Comments
 (0)