factory_bot 6.2.1 → 6.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +2 -2
- data/GETTING_STARTED.md +94 -8
- data/NEWS.md +113 -1
- data/README.md +28 -23
- data/lib/factory_bot/attribute/dynamic.rb +2 -2
- data/lib/factory_bot/attribute_assigner.rb +1 -1
- data/lib/factory_bot/decorator.rb +5 -17
- data/lib/factory_bot/definition.rb +37 -11
- data/lib/factory_bot/definition_proxy.rb +19 -6
- data/lib/factory_bot/evaluation.rb +3 -7
- data/lib/factory_bot/evaluator.rb +3 -4
- data/lib/factory_bot/factory.rb +16 -5
- data/lib/factory_bot/find_definitions.rb +2 -2
- data/lib/factory_bot/internal.rb +35 -2
- data/lib/factory_bot/linter.rb +13 -2
- data/lib/factory_bot/registry.rb +15 -3
- data/lib/factory_bot/sequence.rb +117 -4
- data/lib/factory_bot/strategy/stub.rb +23 -9
- data/lib/factory_bot/syntax/default.rb +2 -2
- data/lib/factory_bot/syntax/methods.rb +61 -12
- data/lib/factory_bot/trait.rb +11 -5
- data/lib/factory_bot/uri_manager.rb +63 -0
- data/lib/factory_bot/version.rb +1 -1
- data/lib/factory_bot.rb +34 -3
- metadata +23 -10
@@ -1,13 +1,10 @@
|
|
1
|
-
require "observer"
|
2
|
-
|
3
1
|
module FactoryBot
|
4
2
|
class Evaluation
|
5
|
-
|
6
|
-
|
7
|
-
def initialize(evaluator, attribute_assigner, to_create)
|
3
|
+
def initialize(evaluator, attribute_assigner, to_create, observer)
|
8
4
|
@evaluator = evaluator
|
9
5
|
@attribute_assigner = attribute_assigner
|
10
6
|
@to_create = to_create
|
7
|
+
@observer = observer
|
11
8
|
end
|
12
9
|
|
13
10
|
delegate :object, :hash, to: :@attribute_assigner
|
@@ -20,8 +17,7 @@ module FactoryBot
|
|
20
17
|
end
|
21
18
|
|
22
19
|
def notify(name, result_instance)
|
23
|
-
|
24
|
-
notify_observers(name, result_instance)
|
20
|
+
@observer.update(name, result_instance)
|
25
21
|
end
|
26
22
|
end
|
27
23
|
end
|
@@ -35,14 +35,13 @@ module FactoryBot
|
|
35
35
|
|
36
36
|
attr_accessor :instance
|
37
37
|
|
38
|
-
def method_missing(method_name,
|
38
|
+
def method_missing(method_name, ...)
|
39
39
|
if @instance.respond_to?(method_name)
|
40
|
-
@instance.send(method_name,
|
40
|
+
@instance.send(method_name, ...)
|
41
41
|
else
|
42
|
-
SyntaxRunner.new.send(method_name,
|
42
|
+
SyntaxRunner.new.send(method_name, ...)
|
43
43
|
end
|
44
44
|
end
|
45
|
-
ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
|
46
45
|
|
47
46
|
def respond_to_missing?(method_name, _include_private = false)
|
48
47
|
@instance.respond_to?(method_name) || SyntaxRunner.new.respond_to?(method_name)
|
data/lib/factory_bot/factory.rb
CHANGED
@@ -12,16 +12,20 @@ module FactoryBot
|
|
12
12
|
@parent = options[:parent]
|
13
13
|
@aliases = options[:aliases] || []
|
14
14
|
@class_name = options[:class]
|
15
|
-
@
|
15
|
+
@uri_manager = FactoryBot::UriManager.new(names)
|
16
|
+
@definition = Definition.new(@name, options[:traits] || [], uri_manager: @uri_manager)
|
16
17
|
@compiled = false
|
17
18
|
end
|
18
19
|
|
19
20
|
delegate :add_callback, :declare_attribute, :to_create, :define_trait, :constructor,
|
20
|
-
:defined_traits, :inherit_traits, :append_traits,
|
21
|
+
:defined_traits, :defined_traits_names, :inherit_traits, :append_traits,
|
22
|
+
to: :@definition
|
21
23
|
|
22
24
|
def build_class
|
23
25
|
@build_class ||= if class_name.is_a? Class
|
24
26
|
class_name
|
27
|
+
elsif class_name.to_s.safe_constantize
|
28
|
+
class_name.to_s.safe_constantize
|
25
29
|
else
|
26
30
|
class_name.to_s.camelize.constantize
|
27
31
|
end
|
@@ -36,9 +40,9 @@ module FactoryBot
|
|
36
40
|
evaluator = evaluator_class.new(strategy, overrides.symbolize_keys)
|
37
41
|
attribute_assigner = AttributeAssigner.new(evaluator, build_class, &compiled_constructor)
|
38
42
|
|
43
|
+
observer = CallbacksObserver.new(callbacks, evaluator)
|
39
44
|
evaluation =
|
40
|
-
Evaluation.new(evaluator, attribute_assigner, compiled_to_create)
|
41
|
-
evaluation.add_observer(CallbacksObserver.new(callbacks, evaluator))
|
45
|
+
Evaluation.new(evaluator, attribute_assigner, compiled_to_create, observer)
|
42
46
|
|
43
47
|
strategy.result(evaluation).tap(&block)
|
44
48
|
end
|
@@ -83,7 +87,7 @@ module FactoryBot
|
|
83
87
|
def compile
|
84
88
|
unless @compiled
|
85
89
|
parent.compile
|
86
|
-
|
90
|
+
inherit_parent_traits
|
87
91
|
@definition.compile(build_class)
|
88
92
|
build_hierarchy
|
89
93
|
@compiled = true
|
@@ -151,6 +155,13 @@ module FactoryBot
|
|
151
155
|
end
|
152
156
|
end
|
153
157
|
|
158
|
+
def inherit_parent_traits
|
159
|
+
parent.defined_traits.each do |trait|
|
160
|
+
next if defined_traits_names.include?(trait.name)
|
161
|
+
define_trait(trait.clone)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
154
165
|
def initialize_copy(source)
|
155
166
|
super
|
156
167
|
@definition = @definition.clone
|
@@ -2,8 +2,8 @@ module FactoryBot
|
|
2
2
|
class << self
|
3
3
|
# An Array of strings specifying locations that should be searched for
|
4
4
|
# factory definitions. By default, factory_bot will attempt to require
|
5
|
-
# "factories", "
|
6
|
-
#
|
5
|
+
# "factories.rb", "factories/**/*.rb", "test/factories.rb",
|
6
|
+
# "test/factories/**.rb", "spec/factories.rb", and "spec/factories/**.rb".
|
7
7
|
attr_accessor :definition_file_paths
|
8
8
|
end
|
9
9
|
|
data/lib/factory_bot/internal.rb
CHANGED
@@ -26,6 +26,7 @@ module FactoryBot
|
|
26
26
|
|
27
27
|
def register_inline_sequence(sequence)
|
28
28
|
inline_sequences.push(sequence)
|
29
|
+
sequence
|
29
30
|
end
|
30
31
|
|
31
32
|
def rewind_inline_sequences
|
@@ -39,8 +40,8 @@ module FactoryBot
|
|
39
40
|
trait
|
40
41
|
end
|
41
42
|
|
42
|
-
def trait_by_name(name)
|
43
|
-
traits.find(name)
|
43
|
+
def trait_by_name(name, klass)
|
44
|
+
traits.find(name).tap { |t| t.klass = klass }
|
44
45
|
end
|
45
46
|
|
46
47
|
def register_sequence(sequence)
|
@@ -59,6 +60,22 @@ module FactoryBot
|
|
59
60
|
rewind_inline_sequences
|
60
61
|
end
|
61
62
|
|
63
|
+
def rewind_sequence(*uri_parts)
|
64
|
+
fail_argument_count(0, "1+") if uri_parts.empty?
|
65
|
+
|
66
|
+
uri = build_uri(uri_parts)
|
67
|
+
sequence = Sequence.find_by_uri(uri) || fail_unregistered_sequence(uri)
|
68
|
+
|
69
|
+
sequence.rewind
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_sequence(*uri_parts, value)
|
73
|
+
uri = build_uri(uri_parts) || fail_argument_count(uri_parts.size, "2+")
|
74
|
+
sequence = Sequence.find(*uri) || fail_unregistered_sequence(uri)
|
75
|
+
|
76
|
+
sequence.set_value(value)
|
77
|
+
end
|
78
|
+
|
62
79
|
def register_factory(factory)
|
63
80
|
factory.names.each do |name|
|
64
81
|
factories.register(name, factory)
|
@@ -86,6 +103,22 @@ module FactoryBot
|
|
86
103
|
register_strategy(:build_stubbed, FactoryBot::Strategy::Stub)
|
87
104
|
register_strategy(:null, FactoryBot::Strategy::Null)
|
88
105
|
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def build_uri(...)
|
110
|
+
FactoryBot::UriManager.build_uri(...)
|
111
|
+
end
|
112
|
+
|
113
|
+
def fail_argument_count(received, expected)
|
114
|
+
fail ArgumentError,
|
115
|
+
"wrong number of arguments (given #{received}, expected #{expected})"
|
116
|
+
end
|
117
|
+
|
118
|
+
def fail_unregistered_sequence(uri)
|
119
|
+
fail KeyError,
|
120
|
+
"Sequence not registered: '#{uri}'."
|
121
|
+
end
|
89
122
|
end
|
90
123
|
end
|
91
124
|
end
|
data/lib/factory_bot/linter.rb
CHANGED
@@ -70,7 +70,7 @@ module FactoryBot
|
|
70
70
|
def lint_factory(factory)
|
71
71
|
result = []
|
72
72
|
begin
|
73
|
-
FactoryBot.public_send(factory_strategy, factory.name)
|
73
|
+
in_transaction { FactoryBot.public_send(factory_strategy, factory.name) }
|
74
74
|
rescue => e
|
75
75
|
result |= [FactoryError.new(e, factory)]
|
76
76
|
end
|
@@ -80,7 +80,7 @@ module FactoryBot
|
|
80
80
|
def lint_traits(factory)
|
81
81
|
result = []
|
82
82
|
factory.definition.defined_traits.map(&:name).each do |trait_name|
|
83
|
-
FactoryBot.public_send(factory_strategy, factory.name, trait_name)
|
83
|
+
in_transaction { FactoryBot.public_send(factory_strategy, factory.name, trait_name) }
|
84
84
|
rescue => e
|
85
85
|
result |= [FactoryTraitError.new(e, factory, trait_name)]
|
86
86
|
end
|
@@ -106,5 +106,16 @@ module FactoryBot
|
|
106
106
|
:message
|
107
107
|
end
|
108
108
|
end
|
109
|
+
|
110
|
+
def in_transaction
|
111
|
+
if defined?(ActiveRecord::Base)
|
112
|
+
ActiveRecord::Base.transaction do
|
113
|
+
yield
|
114
|
+
raise ActiveRecord::Rollback
|
115
|
+
end
|
116
|
+
else
|
117
|
+
yield
|
118
|
+
end
|
119
|
+
end
|
109
120
|
end
|
110
121
|
end
|
data/lib/factory_bot/registry.rb
CHANGED
@@ -39,9 +39,21 @@ module FactoryBot
|
|
39
39
|
|
40
40
|
def key_error_with_custom_message(key_error)
|
41
41
|
message = key_error.message.sub("key not found", "#{@name} not registered")
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
new_key_error(message, key_error).tap do |error|
|
43
|
+
error.set_backtrace(key_error.backtrace)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# detailed_message introduced in Ruby 3.2 for cleaner integration with
|
48
|
+
# did_you_mean. See https://bugs.ruby-lang.org/issues/18564
|
49
|
+
if KeyError.method_defined?(:detailed_message)
|
50
|
+
def new_key_error(message, key_error)
|
51
|
+
KeyError.new(message, key: key_error.key, receiver: key_error.receiver)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
def new_key_error(message, _)
|
55
|
+
KeyError.new(message)
|
56
|
+
end
|
45
57
|
end
|
46
58
|
end
|
47
59
|
end
|
data/lib/factory_bot/sequence.rb
CHANGED
@@ -1,17 +1,33 @@
|
|
1
|
+
require "timeout"
|
2
|
+
|
1
3
|
module FactoryBot
|
2
4
|
# Sequences are defined using sequence within a FactoryBot.define block.
|
3
5
|
# Sequence values are generated using next.
|
4
6
|
# @api private
|
5
7
|
class Sequence
|
6
|
-
attr_reader :name
|
8
|
+
attr_reader :name, :uri_manager, :aliases
|
9
|
+
|
10
|
+
def self.find(*uri_parts)
|
11
|
+
if uri_parts.empty?
|
12
|
+
fail ArgumentError, "wrong number of arguments, expected 1+)"
|
13
|
+
else
|
14
|
+
find_by_uri FactoryBot::UriManager.build_uri(*uri_parts)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find_by_uri(uri)
|
19
|
+
uri = uri.to_sym
|
20
|
+
(FactoryBot::Internal.sequences.to_a.find { |seq| seq.has_uri?(uri) }) ||
|
21
|
+
(FactoryBot::Internal.inline_sequences.find { |seq| seq.has_uri?(uri) })
|
22
|
+
end
|
7
23
|
|
8
24
|
def initialize(name, *args, &proc)
|
25
|
+
options = args.extract_options!
|
9
26
|
@name = name
|
10
27
|
@proc = proc
|
11
|
-
|
12
|
-
|
28
|
+
@aliases = options.fetch(:aliases, []).map(&:to_sym)
|
29
|
+
@uri_manager = FactoryBot::UriManager.new(names, paths: options[:uri_paths])
|
13
30
|
@value = args.first || 1
|
14
|
-
@aliases = options.fetch(:aliases) { [] }
|
15
31
|
|
16
32
|
unless @value.respond_to?(:peek)
|
17
33
|
@value = EnumeratorAdapter.new(@value)
|
@@ -34,10 +50,40 @@ module FactoryBot
|
|
34
50
|
[@name] + @aliases
|
35
51
|
end
|
36
52
|
|
53
|
+
def has_name?(test_name)
|
54
|
+
names.include?(test_name.to_sym)
|
55
|
+
end
|
56
|
+
|
57
|
+
def has_uri?(uri)
|
58
|
+
uri_manager.include?(uri)
|
59
|
+
end
|
60
|
+
|
61
|
+
def for_factory?(test_factory_name)
|
62
|
+
FactoryBot::Internal.factory_by_name(factory_name).names.include?(test_factory_name.to_sym)
|
63
|
+
end
|
64
|
+
|
37
65
|
def rewind
|
38
66
|
@value.rewind
|
39
67
|
end
|
40
68
|
|
69
|
+
##
|
70
|
+
# If it's an Integer based sequence, set the new value directly,
|
71
|
+
# else rewind and seek from the beginning until a match is found.
|
72
|
+
#
|
73
|
+
def set_value(new_value)
|
74
|
+
if can_set_value_directly?(new_value)
|
75
|
+
@value.set_value(new_value)
|
76
|
+
elsif can_set_value_by_index?
|
77
|
+
set_value_by_index(new_value)
|
78
|
+
else
|
79
|
+
seek_value(new_value)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
attr_reader :proc
|
86
|
+
|
41
87
|
private
|
42
88
|
|
43
89
|
def value
|
@@ -48,6 +94,61 @@ module FactoryBot
|
|
48
94
|
@value.next
|
49
95
|
end
|
50
96
|
|
97
|
+
def can_set_value_by_index?
|
98
|
+
@value.respond_to?(:find_index)
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Set to the given value, or fail if not found
|
103
|
+
#
|
104
|
+
def set_value_by_index(value)
|
105
|
+
index = @value.find_index(value) || fail_value_not_found(value)
|
106
|
+
@value.rewind
|
107
|
+
index.times { @value.next }
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Rewind index and seek until the value is found or the max attempts
|
112
|
+
# have been tried. If not found, the sequence is rewound to its original value
|
113
|
+
#
|
114
|
+
def seek_value(value)
|
115
|
+
original_value = @value.peek
|
116
|
+
|
117
|
+
# rewind and search for the new value
|
118
|
+
@value.rewind
|
119
|
+
Timeout.timeout(FactoryBot.sequence_setting_timeout) do
|
120
|
+
loop do
|
121
|
+
return if @value.peek == value
|
122
|
+
increment_value
|
123
|
+
end
|
124
|
+
|
125
|
+
# loop auto-recues a StopIteration error, so if we
|
126
|
+
# reached this point, re-raise it now
|
127
|
+
fail StopIteration
|
128
|
+
end
|
129
|
+
rescue Timeout::Error, StopIteration
|
130
|
+
reset_original_value(original_value)
|
131
|
+
fail_value_not_found(value)
|
132
|
+
end
|
133
|
+
|
134
|
+
def reset_original_value(original_value)
|
135
|
+
@value.rewind
|
136
|
+
|
137
|
+
until @value.peek == original_value
|
138
|
+
increment_value
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def can_set_value_directly?(value)
|
143
|
+
return false unless value.is_a?(Integer)
|
144
|
+
return false unless @value.is_a?(EnumeratorAdapter)
|
145
|
+
@value.integer_value?
|
146
|
+
end
|
147
|
+
|
148
|
+
def fail_value_not_found(value)
|
149
|
+
fail ArgumentError, "Unable to find '#{value}' in the sequence."
|
150
|
+
end
|
151
|
+
|
51
152
|
class EnumeratorAdapter
|
52
153
|
def initialize(value)
|
53
154
|
@first_value = value
|
@@ -65,6 +166,18 @@ module FactoryBot
|
|
65
166
|
def rewind
|
66
167
|
@value = @first_value
|
67
168
|
end
|
169
|
+
|
170
|
+
def set_value(new_value)
|
171
|
+
if new_value >= @first_value
|
172
|
+
@value = new_value
|
173
|
+
else
|
174
|
+
fail ArgumentError, "Value cannot be less than: #{@first_value}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def integer_value?
|
179
|
+
@first_value.is_a?(Integer)
|
180
|
+
end
|
68
181
|
end
|
69
182
|
end
|
70
183
|
end
|
@@ -47,13 +47,17 @@ module FactoryBot
|
|
47
47
|
|
48
48
|
private
|
49
49
|
|
50
|
-
def next_id
|
51
|
-
|
50
|
+
def next_id(result_instance)
|
51
|
+
if uuid_primary_key?(result_instance)
|
52
|
+
SecureRandom.uuid
|
53
|
+
else
|
54
|
+
@@next_id += 1
|
55
|
+
end
|
52
56
|
end
|
53
57
|
|
54
58
|
def stub_database_interaction_on_result(result_instance)
|
55
59
|
if has_settable_id?(result_instance)
|
56
|
-
result_instance.id ||= next_id
|
60
|
+
result_instance.id ||= next_id(result_instance)
|
57
61
|
end
|
58
62
|
|
59
63
|
result_instance.instance_eval do
|
@@ -66,12 +70,12 @@ module FactoryBot
|
|
66
70
|
end
|
67
71
|
|
68
72
|
def destroyed?
|
69
|
-
|
73
|
+
false
|
70
74
|
end
|
71
75
|
|
72
76
|
DISABLED_PERSISTENCE_METHODS.each do |write_method|
|
73
77
|
define_singleton_method(write_method) do |*args|
|
74
|
-
raise "stubbed models are not allowed to access the database - "\
|
78
|
+
raise "stubbed models are not allowed to access the database - " \
|
75
79
|
"#{self.class}##{write_method}(#{args.join(",")})"
|
76
80
|
end
|
77
81
|
end
|
@@ -79,8 +83,16 @@ module FactoryBot
|
|
79
83
|
end
|
80
84
|
|
81
85
|
def has_settable_id?(result_instance)
|
82
|
-
|
83
|
-
result_instance.class.primary_key
|
86
|
+
result_instance.respond_to?(:id=) &&
|
87
|
+
(!result_instance.class.respond_to?(:primary_key) ||
|
88
|
+
result_instance.class.primary_key)
|
89
|
+
end
|
90
|
+
|
91
|
+
def uuid_primary_key?(result_instance)
|
92
|
+
result_instance.respond_to?(:column_for_attribute) &&
|
93
|
+
(column = result_instance.column_for_attribute(result_instance.class.primary_key)) &&
|
94
|
+
column.respond_to?(:sql_type) &&
|
95
|
+
column.sql_type == "uuid"
|
84
96
|
end
|
85
97
|
|
86
98
|
def clear_changes_information(result_instance)
|
@@ -90,12 +102,14 @@ module FactoryBot
|
|
90
102
|
end
|
91
103
|
|
92
104
|
def set_timestamps(result_instance)
|
105
|
+
timestamp = Time.current
|
106
|
+
|
93
107
|
if missing_created_at?(result_instance)
|
94
|
-
result_instance.created_at =
|
108
|
+
result_instance.created_at = timestamp
|
95
109
|
end
|
96
110
|
|
97
111
|
if missing_updated_at?(result_instance)
|
98
|
-
result_instance.updated_at =
|
112
|
+
result_instance.updated_at = timestamp
|
99
113
|
end
|
100
114
|
end
|
101
115
|
|
@@ -102,33 +102,82 @@ module FactoryBot
|
|
102
102
|
# @param [Array<Symbol, Symbol, Hash>] traits_and_overrides splat args traits and a hash of overrides
|
103
103
|
# @param [Proc] block block to be executed
|
104
104
|
|
105
|
-
# Generates and returns the next value in a sequence.
|
105
|
+
# Generates and returns the next value in a global or factory sequence.
|
106
106
|
#
|
107
107
|
# Arguments:
|
108
|
-
#
|
109
|
-
# The
|
108
|
+
# context: (Array of Symbols)
|
109
|
+
# The definition context of the sequence, with the sequence name
|
110
|
+
# as the final entry
|
111
|
+
# scope: (object)(optional)
|
112
|
+
# The object the sequence should be evaluated within
|
110
113
|
#
|
111
114
|
# Returns:
|
112
115
|
# The next value in the sequence. (Object)
|
113
|
-
|
114
|
-
|
116
|
+
#
|
117
|
+
# Example:
|
118
|
+
# generate(:my_factory, :my_trair, :my_sequence)
|
119
|
+
#
|
120
|
+
def generate(*uri_parts, scope: nil)
|
121
|
+
uri = FactoryBot::UriManager.build_uri(uri_parts)
|
122
|
+
sequence = Sequence.find_by_uri(uri) ||
|
123
|
+
raise(KeyError,
|
124
|
+
"Sequence not registered: #{FactoryBot::UriManager.build_uri(uri_parts)}")
|
125
|
+
|
126
|
+
increment_sequence(uri, sequence, scope: scope)
|
115
127
|
end
|
116
128
|
|
117
|
-
# Generates and returns the list of values in a sequence.
|
129
|
+
# Generates and returns the list of values in a global or factory sequence.
|
118
130
|
#
|
119
131
|
# Arguments:
|
120
|
-
#
|
121
|
-
# The
|
122
|
-
#
|
123
|
-
#
|
132
|
+
# uri_parts: (Array of Symbols)
|
133
|
+
# The definition context of the sequence, with the sequence name
|
134
|
+
# as the final entry
|
135
|
+
# scope: (object)(optional)
|
136
|
+
# The object the sequence should be evaluated within
|
124
137
|
#
|
125
138
|
# Returns:
|
126
139
|
# The next value in the sequence. (Object)
|
127
|
-
|
140
|
+
#
|
141
|
+
# Example:
|
142
|
+
# generate_list(:my_factory, :my_trair, :my_sequence, 5)
|
143
|
+
#
|
144
|
+
def generate_list(*uri_parts, count, scope: nil)
|
145
|
+
uri = FactoryBot::UriManager.build_uri(uri_parts)
|
146
|
+
sequence = Sequence.find_by_uri(uri) ||
|
147
|
+
raise(KeyError, "Sequence not registered: '#{uri}'")
|
148
|
+
|
128
149
|
(1..count).map do
|
129
|
-
|
150
|
+
increment_sequence(uri, sequence, scope: scope)
|
130
151
|
end
|
131
152
|
end
|
153
|
+
|
154
|
+
# ======================================================================
|
155
|
+
# = PRIVATE
|
156
|
+
# ======================================================================
|
157
|
+
#
|
158
|
+
private
|
159
|
+
|
160
|
+
##
|
161
|
+
# Increments the given sequence and returns the value.
|
162
|
+
#
|
163
|
+
# Arguments:
|
164
|
+
# uri: (Symbol)
|
165
|
+
# The URI for the sequence
|
166
|
+
# sequence:
|
167
|
+
# The sequence instance
|
168
|
+
# scope: (object)(optional)
|
169
|
+
# The object the sequence should be evaluated within
|
170
|
+
#
|
171
|
+
def increment_sequence(uri, sequence, scope: nil)
|
172
|
+
value = sequence.next(scope)
|
173
|
+
|
174
|
+
raise if value.respond_to?(:start_with?) && value.start_with?("#<FactoryBot::Declaration")
|
175
|
+
|
176
|
+
value
|
177
|
+
rescue
|
178
|
+
raise ArgumentError, "Sequence '#{uri}' failed to " \
|
179
|
+
"return a value. Perhaps it needs a scope to operate? (scope: <object>)"
|
180
|
+
end
|
132
181
|
end
|
133
182
|
end
|
134
183
|
end
|
data/lib/factory_bot/trait.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
module FactoryBot
|
2
2
|
# @api private
|
3
3
|
class Trait
|
4
|
-
attr_reader :name, :definition
|
4
|
+
attr_reader :name, :uid, :definition
|
5
5
|
|
6
|
-
|
6
|
+
delegate :add_callback, :declare_attribute, :to_create, :define_trait, :constructor,
|
7
|
+
:callbacks, :attributes, :klass, :klass=, to: :@definition
|
8
|
+
|
9
|
+
def initialize(name, **options, &block)
|
7
10
|
@name = name.to_s
|
8
11
|
@block = block
|
9
|
-
@
|
12
|
+
@uri_manager = FactoryBot::UriManager.new(names, paths: options[:uri_paths])
|
13
|
+
|
14
|
+
@definition = Definition.new(@name, uri_manager: @uri_manager)
|
10
15
|
proxy = FactoryBot::DefinitionProxy.new(@definition)
|
11
16
|
|
12
17
|
if block
|
@@ -14,8 +19,9 @@ module FactoryBot
|
|
14
19
|
end
|
15
20
|
end
|
16
21
|
|
17
|
-
|
18
|
-
|
22
|
+
def clone
|
23
|
+
Trait.new(name, uri_paths: definition.uri_manager.paths, &block)
|
24
|
+
end
|
19
25
|
|
20
26
|
def names
|
21
27
|
[@name]
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module FactoryBot
|
2
|
+
# @api private
|
3
|
+
class UriManager
|
4
|
+
attr_reader :endpoints, :paths, :uri_list
|
5
|
+
|
6
|
+
delegate :size, :any?, :empty?, :each?, :include?, :first, to: :@uri_list
|
7
|
+
delegate :build_uri, to: :class
|
8
|
+
|
9
|
+
# Concatenate the parts, sripping leading/following slashes
|
10
|
+
# and returning a Symbolized String or nil.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
# build_uri(:my_factory, :my_trait, :my_sequence)
|
14
|
+
# #=> :"myfactory/my_trait/my_sequence"
|
15
|
+
#
|
16
|
+
def self.build_uri(*parts)
|
17
|
+
return nil if parts.empty?
|
18
|
+
|
19
|
+
parts.join("/")
|
20
|
+
.sub(/\A\/+/, "")
|
21
|
+
.sub(/\/+\z/, "")
|
22
|
+
.tr(" ", "_")
|
23
|
+
.to_sym
|
24
|
+
end
|
25
|
+
|
26
|
+
# Configures the new UriManager
|
27
|
+
#
|
28
|
+
# Arguments:
|
29
|
+
# endpoints: (Array of Strings or Symbols)
|
30
|
+
# the objects endpoints.
|
31
|
+
#
|
32
|
+
# paths: (Array of Strings or Symbols)
|
33
|
+
# the parent URIs to prepend to each endpoint
|
34
|
+
#
|
35
|
+
def initialize(*endpoints, paths: [])
|
36
|
+
if endpoints.empty?
|
37
|
+
fail ArgumentError, "wrong number of arguments (given 0, expected 1+)"
|
38
|
+
end
|
39
|
+
|
40
|
+
@uri_list = []
|
41
|
+
@endpoints = endpoints.flatten
|
42
|
+
@paths = Array(paths).flatten
|
43
|
+
|
44
|
+
build_uri_list
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_a
|
48
|
+
@uri_list.dup
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def build_uri_list
|
54
|
+
@endpoints.each do |endpoint|
|
55
|
+
if @paths.any?
|
56
|
+
@paths.each { |path| @uri_list << build_uri(path, endpoint) }
|
57
|
+
else
|
58
|
+
@uri_list << build_uri(endpoint)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/factory_bot/version.rb
CHANGED