Allow inline sequences in traits to have same name

Fixes #1257

When sequence rewinding was first introduced in #1078 it only applied to
globally defined sequences. To get rewinding to work for inline
sequences as well we registered them "privately" in the global registry
in #1164. Unfortunately in #1164 we did not take inline sequences inside
traits into consideration. Since trait names are not unique, it is
possibly to get a `FactoryBot::DuplicateDefinitionError` when defining
two sequences that have the same name in two traits that have the same
name.

This PR abandons the idea of "privately" registering inline sequences,
and instead keeps a separate list of all the inline sequences just for
the purpose of rewinding them.
This commit is contained in:
Daniel Colson 2019-02-13 17:24:28 -05:00
parent d2a30d6fd2
commit 79331a3863
6 changed files with 59 additions and 9 deletions

View File

@ -115,6 +115,7 @@ module FactoryBot
def self.rewind_sequences def self.rewind_sequences
sequences.each(&:rewind) sequences.each(&:rewind)
Internal.rewind_inline_sequences
end end
def self.register_trait(trait) def self.register_trait(trait)

View File

@ -1,7 +1,14 @@
module FactoryBot module FactoryBot
# @api private # @api private
class Configuration class Configuration
attr_reader :factories, :sequences, :traits, :strategies, :callback_names attr_reader(
:callback_names,
:factories,
:inline_sequences,
:sequences,
:strategies,
:traits,
)
def initialize def initialize
@factories = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Factory")) @factories = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Factory"))
@ -10,6 +17,7 @@ module FactoryBot
@strategies = Registry.new("Strategy") @strategies = Registry.new("Strategy")
@callback_names = Set.new @callback_names = Set.new
@definition = Definition.new(:configuration) @definition = Definition.new(:configuration)
@inline_sequences = []
to_create(&:save!) to_create(&:save!)
initialize_with { new } initialize_with { new }

View File

@ -117,9 +117,8 @@ module FactoryBot
# #
# Except that no globally available sequence will be defined. # Except that no globally available sequence will be defined.
def sequence(name, *args, &block) def sequence(name, *args, &block)
sequence_name = "__#{@definition.name}_#{name}__" sequence = Sequence.new(name, *args, &block)
sequence = Sequence.new(sequence_name, *args, &block) FactoryBot::Internal.register_inline_sequence(sequence)
FactoryBot.register_sequence(sequence)
add_attribute(name) { increment_sequence(sequence) } add_attribute(name) { increment_sequence(sequence) }
end end

View File

@ -2,6 +2,8 @@ module FactoryBot
# @api private # @api private
module Internal module Internal
class << self class << self
delegate :inline_sequences, to: :configuration
def configuration def configuration
@configuration ||= Configuration.new @configuration ||= Configuration.new
end end
@ -9,6 +11,14 @@ module FactoryBot
def reset_configuration def reset_configuration
@configuration = nil @configuration = nil
end end
def register_inline_sequence(sequence)
inline_sequences.push(sequence)
end
def rewind_inline_sequences
inline_sequences.each(&:rewind)
end
end end
end end
end end

View File

@ -90,4 +90,34 @@ describe "FactoryBot.rewind_sequences" do
expect(user.email).to eq "local-somebody1@example.com" expect(user.email).to eq "local-somebody1@example.com"
expect(email).to eq "global-somebody1@example.com" expect(email).to eq "global-somebody1@example.com"
end end
it "allows setting sequences within identically named traits" do
define_class("User") { attr_accessor :email }
define_class("Person") { attr_accessor :email }
FactoryBot.define do
factory :user do
trait :with_email do
sequence(:email) { |n| "user#{n}@example.com" }
end
end
factory :person do
trait :with_email do
sequence(:email) { |n| "person#{n}@example.com" }
end
end
end
build_list(:user, 2, :with_email)
build_list(:person, 2, :with_email)
FactoryBot.rewind_sequences
user = build(:user, :with_email)
person = build(:person, :with_email)
expect(user.email).to eq "user1@example.com"
expect(person.email).to eq "person1@example.com"
end
end end

View File

@ -106,18 +106,20 @@ describe FactoryBot::DefinitionProxy, "#sequence" do
it "creates a new sequence starting at 1" do it "creates a new sequence starting at 1" do
proxy = build_proxy(:factory) proxy = build_proxy(:factory)
proxy.sequence(:sequence) proxy.sequence(:sequence)
expect(FactoryBot::Sequence).to have_received(:new). expect(FactoryBot::Sequence).to have_received(:new).with(:sequence)
with("__factory_sequence__")
end end
it "creates a new sequence with an overridden starting vaue" do it "creates a new sequence with an overridden starting vaue" do
proxy = build_proxy(:factory) proxy = build_proxy(:factory)
proxy.sequence(:sequence, "C") override = "override"
proxy.sequence(:sequence, override)
expect(FactoryBot::Sequence).to have_received(:new). expect(FactoryBot::Sequence).to have_received(:new).
with("__factory_sequence__", "C") with(:sequence, override)
end end
it "creates a new sequence with a block" do it "creates a new sequence with a block" do
@ -126,7 +128,7 @@ describe FactoryBot::DefinitionProxy, "#sequence" do
proxy.sequence(:sequence, 1, &sequence_block) proxy.sequence(:sequence, 1, &sequence_block)
expect(FactoryBot::Sequence).to have_received(:new). expect(FactoryBot::Sequence).to have_received(:new).
with("__factory_sequence__", 1, &sequence_block) with(:sequence, 1, &sequence_block)
end end
end end