factory_bot 6.5.1 → 6.5.5
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/GETTING_STARTED.md +24 -2
- data/NEWS.md +421 -323
- data/README.md +2 -2
- data/lib/factory_bot/attribute_assigner.rb +25 -8
- data/lib/factory_bot/callbacks_observer.rb +19 -1
- data/lib/factory_bot/definition.rb +3 -2
- data/lib/factory_bot/definition_proxy.rb +19 -6
- data/lib/factory_bot/evaluator.rb +9 -3
- data/lib/factory_bot/factory.rb +8 -2
- data/lib/factory_bot/find_definitions.rb +2 -2
- data/lib/factory_bot/internal.rb +33 -0
- data/lib/factory_bot/registry.rb +1 -1
- data/lib/factory_bot/sequence.rb +137 -10
- data/lib/factory_bot/strategy/build.rb +2 -0
- data/lib/factory_bot/strategy/create.rb +2 -0
- data/lib/factory_bot/strategy/stub.rb +4 -2
- data/lib/factory_bot/syntax/methods.rb +59 -12
- data/lib/factory_bot/trait.rb +9 -7
- data/lib/factory_bot/uri_manager.rb +63 -0
- data/lib/factory_bot/version.rb +1 -1
- data/lib/factory_bot.rb +33 -3
- metadata +4 -6
data/README.md
CHANGED
@@ -10,7 +10,7 @@ _[Interested in the history of the project name?][NAME]_
|
|
10
10
|
|
11
11
|
### Transitioning from factory\_girl?
|
12
12
|
|
13
|
-
Check out the [guide](https://github.com/thoughtbot/factory_bot/blob/
|
13
|
+
Check out the [guide](https://github.com/thoughtbot/factory_bot/blob/v4.9.0/UPGRADE_FROM_FACTORY_GIRL.md).
|
14
14
|
|
15
15
|
|
16
16
|
Documentation
|
@@ -62,6 +62,7 @@ Useful Tools
|
|
62
62
|
------------
|
63
63
|
|
64
64
|
* [FactoryTrace](https://github.com/djezzzl/factory_trace) - helps to find unused factories and traits.
|
65
|
+
* [ruby-lsp-factory_bot](https://github.com/donny741/ruby-lsp-factory_bot) / [ruby-lsp-rails-factory-bot](https://github.com/johansenja/ruby-lsp-rails-factory-bot) - integration with [ruby-lsp](https://github.com/Shopify/ruby-lsp) to provide intellisense
|
65
66
|
|
66
67
|
Contributing
|
67
68
|
------------
|
@@ -96,7 +97,6 @@ We are [available for hire][hire].
|
|
96
97
|
[community]: https://thoughtbot.com/community?utm_source=github
|
97
98
|
[hire]: https://thoughtbot.com/hire-us?utm_source=github
|
98
99
|
|
99
|
-
|
100
100
|
<!-- END /templates/footer.md -->
|
101
101
|
|
102
102
|
[ci-image]: https://github.com/thoughtbot/factory_bot/actions/workflows/build.yml/badge.svg?branch=main
|
@@ -72,7 +72,7 @@ module FactoryBot
|
|
72
72
|
non_ignored_attribute_names +
|
73
73
|
override_names -
|
74
74
|
ignored_attribute_names -
|
75
|
-
|
75
|
+
aliased_attribute_names_to_ignore
|
76
76
|
end
|
77
77
|
|
78
78
|
def non_ignored_attribute_names
|
@@ -91,22 +91,39 @@ module FactoryBot
|
|
91
91
|
@evaluator.__override_names__
|
92
92
|
end
|
93
93
|
|
94
|
+
def attribute_names
|
95
|
+
@attribute_list.names
|
96
|
+
end
|
97
|
+
|
94
98
|
def hash_instance_methods_to_respond_to
|
95
|
-
|
99
|
+
attribute_names + override_names + @build_class.instance_methods
|
96
100
|
end
|
97
101
|
|
98
|
-
|
102
|
+
##
|
103
|
+
# Creat a list of attribute names that will be
|
104
|
+
# overridden by an alias, so any defaults can
|
105
|
+
# ignored.
|
106
|
+
#
|
107
|
+
def aliased_attribute_names_to_ignore
|
99
108
|
@attribute_list.non_ignored.flat_map { |attribute|
|
100
109
|
override_names.map do |override|
|
101
|
-
attribute.name if
|
110
|
+
attribute.name if aliased_attribute?(attribute, override)
|
102
111
|
end
|
103
112
|
}.compact
|
104
113
|
end
|
105
114
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
115
|
+
##
|
116
|
+
# Is the override an alias for the attribute and not the
|
117
|
+
# actual name of another attribute?
|
118
|
+
#
|
119
|
+
# Note: Checking against the names of all attributes, resolves any
|
120
|
+
# issues with having both <attribute> and <attribute>_id
|
121
|
+
# in the same factory.
|
122
|
+
#
|
123
|
+
def aliased_attribute?(attribute, override)
|
124
|
+
return false if attribute_names.include?(override)
|
125
|
+
|
126
|
+
attribute.alias_for?(override)
|
110
127
|
end
|
111
128
|
end
|
112
129
|
end
|
@@ -4,11 +4,15 @@ module FactoryBot
|
|
4
4
|
def initialize(callbacks, evaluator)
|
5
5
|
@callbacks = callbacks
|
6
6
|
@evaluator = evaluator
|
7
|
+
@completed = []
|
7
8
|
end
|
8
9
|
|
9
10
|
def update(name, result_instance)
|
10
11
|
callbacks_by_name(name).each do |callback|
|
11
|
-
|
12
|
+
if !completed?(result_instance, callback)
|
13
|
+
callback.run(result_instance, @evaluator)
|
14
|
+
record_completion!(result_instance, callback)
|
15
|
+
end
|
12
16
|
end
|
13
17
|
end
|
14
18
|
|
@@ -17,5 +21,19 @@ module FactoryBot
|
|
17
21
|
def callbacks_by_name(name)
|
18
22
|
@callbacks.select { |callback| callback.name == name }
|
19
23
|
end
|
24
|
+
|
25
|
+
def completed?(instance, callback)
|
26
|
+
key = completion_key_for(instance, callback)
|
27
|
+
@completed.include?(key)
|
28
|
+
end
|
29
|
+
|
30
|
+
def record_completion!(instance, callback)
|
31
|
+
key = completion_key_for(instance, callback)
|
32
|
+
@completed << key
|
33
|
+
end
|
34
|
+
|
35
|
+
def completion_key_for(instance, callback)
|
36
|
+
"#{instance.object_id}-#{callback.object_id}"
|
37
|
+
end
|
20
38
|
end
|
21
39
|
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module FactoryBot
|
2
2
|
# @api private
|
3
3
|
class Definition
|
4
|
-
attr_reader :defined_traits, :declarations, :name, :registered_enums
|
4
|
+
attr_reader :defined_traits, :declarations, :name, :registered_enums, :uri_manager
|
5
5
|
attr_accessor :klass
|
6
6
|
|
7
|
-
def initialize(name, base_traits = [])
|
7
|
+
def initialize(name, base_traits = [], **opts)
|
8
8
|
@name = name
|
9
|
+
@uri_manager = opts[:uri_manager]
|
9
10
|
@declarations = DeclarationList.new(name)
|
10
11
|
@callbacks = []
|
11
12
|
@defined_traits = Set.new
|
@@ -119,10 +119,14 @@ module FactoryBot
|
|
119
119
|
# end
|
120
120
|
#
|
121
121
|
# Except that no globally available sequence will be defined.
|
122
|
-
def sequence(name,
|
123
|
-
|
124
|
-
|
125
|
-
|
122
|
+
def sequence(name, *args, &block)
|
123
|
+
options = args.extract_options!
|
124
|
+
options[:uri_paths] = @definition.uri_manager.to_a
|
125
|
+
args << options
|
126
|
+
|
127
|
+
new_sequence = Sequence.new(name, *args, &block)
|
128
|
+
registered_sequence = __fetch_or_register_sequence(new_sequence)
|
129
|
+
add_attribute(name) { increment_sequence(registered_sequence) }
|
126
130
|
end
|
127
131
|
|
128
132
|
# Adds an attribute that builds an association. The associated instance will
|
@@ -169,11 +173,11 @@ module FactoryBot
|
|
169
173
|
end
|
170
174
|
|
171
175
|
def factory(name, options = {}, &block)
|
172
|
-
|
176
|
+
child_factories << [name, options, block]
|
173
177
|
end
|
174
178
|
|
175
179
|
def trait(name, &block)
|
176
|
-
@definition.define_trait(Trait.new(name, &block))
|
180
|
+
@definition.define_trait(Trait.new(name, uri_paths: @definition.uri_manager.to_a, &block))
|
177
181
|
end
|
178
182
|
|
179
183
|
# Creates traits for enumerable values.
|
@@ -252,5 +256,14 @@ module FactoryBot
|
|
252
256
|
def __valid_association_options?(options)
|
253
257
|
options.respond_to?(:has_key?) && options.has_key?(:factory)
|
254
258
|
end
|
259
|
+
|
260
|
+
##
|
261
|
+
# If the inline sequence has already been registered by a parent,
|
262
|
+
# return that one, otherwise register and return the given sequence
|
263
|
+
#
|
264
|
+
def __fetch_or_register_sequence(sequence)
|
265
|
+
FactoryBot::Sequence.find_by_uri(sequence.uri_manager.first) ||
|
266
|
+
FactoryBot::Internal.register_inline_sequence(sequence)
|
267
|
+
end
|
255
268
|
end
|
256
269
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require "active_support/core_ext/hash/except"
|
2
1
|
require "active_support/core_ext/class/attribute"
|
3
2
|
|
4
3
|
module FactoryBot
|
@@ -51,8 +50,15 @@ module FactoryBot
|
|
51
50
|
@overrides.keys
|
52
51
|
end
|
53
52
|
|
54
|
-
def increment_sequence(sequence)
|
55
|
-
sequence.next(
|
53
|
+
def increment_sequence(sequence, scope: self)
|
54
|
+
value = sequence.next(scope)
|
55
|
+
|
56
|
+
raise if value.respond_to?(:start_with?) && value.start_with?("#<FactoryBot::Declaration")
|
57
|
+
|
58
|
+
value
|
59
|
+
rescue
|
60
|
+
raise ArgumentError, "Sequence '#{sequence.uri_manager.first}' failed to " \
|
61
|
+
"return a value. Perhaps it needs a scope to operate? (scope: <object>)"
|
56
62
|
end
|
57
63
|
|
58
64
|
def self.attribute_list
|
data/lib/factory_bot/factory.rb
CHANGED
@@ -12,7 +12,8 @@ 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
|
|
@@ -32,6 +33,7 @@ module FactoryBot
|
|
32
33
|
|
33
34
|
def run(build_strategy, overrides, &block)
|
34
35
|
block ||= ->(result) { result }
|
36
|
+
|
35
37
|
compile
|
36
38
|
|
37
39
|
strategy = StrategyCalculator.new(build_strategy).strategy.new
|
@@ -43,7 +45,11 @@ module FactoryBot
|
|
43
45
|
evaluation =
|
44
46
|
Evaluation.new(evaluator, attribute_assigner, compiled_to_create, observer)
|
45
47
|
|
46
|
-
|
48
|
+
evaluation.notify(:before_all, nil)
|
49
|
+
instance = strategy.result(evaluation).tap(&block)
|
50
|
+
evaluation.notify(:after_all, instance)
|
51
|
+
|
52
|
+
instance
|
47
53
|
end
|
48
54
|
|
49
55
|
def human_names
|
@@ -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
|
@@ -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/registry.rb
CHANGED
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,22 +94,103 @@ 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
|
-
def initialize(
|
53
|
-
@
|
54
|
-
@value = value
|
153
|
+
def initialize(initial_value)
|
154
|
+
@initial_value = initial_value
|
55
155
|
end
|
56
156
|
|
57
157
|
def peek
|
58
|
-
|
158
|
+
value
|
59
159
|
end
|
60
160
|
|
61
161
|
def next
|
62
|
-
@value =
|
162
|
+
@value = value.next
|
63
163
|
end
|
64
164
|
|
65
165
|
def rewind
|
66
|
-
@value =
|
166
|
+
@value = first_value
|
167
|
+
end
|
168
|
+
|
169
|
+
def set_value(new_value)
|
170
|
+
if new_value >= first_value
|
171
|
+
@value = new_value
|
172
|
+
else
|
173
|
+
fail ArgumentError, "Value cannot be less than: #{@first_value}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def integer_value?
|
178
|
+
first_value.is_a?(Integer)
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def first_value
|
184
|
+
@first_value ||= initial_value
|
185
|
+
end
|
186
|
+
|
187
|
+
def value
|
188
|
+
@value ||= initial_value
|
189
|
+
end
|
190
|
+
|
191
|
+
def initial_value
|
192
|
+
@value = @initial_value.respond_to?(:call) ? @initial_value.call : @initial_value
|
193
|
+
@first_value = @value
|
67
194
|
end
|
68
195
|
end
|
69
196
|
end
|
@@ -102,12 +102,14 @@ module FactoryBot
|
|
102
102
|
end
|
103
103
|
|
104
104
|
def set_timestamps(result_instance)
|
105
|
+
timestamp = Time.current
|
106
|
+
|
105
107
|
if missing_created_at?(result_instance)
|
106
|
-
result_instance.created_at =
|
108
|
+
result_instance.created_at = timestamp
|
107
109
|
end
|
108
110
|
|
109
111
|
if missing_updated_at?(result_instance)
|
110
|
-
result_instance.updated_at =
|
112
|
+
result_instance.updated_at = timestamp
|
111
113
|
end
|
112
114
|
end
|
113
115
|
|
@@ -102,33 +102,80 @@ 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(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(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
|
+
# sequence:
|
165
|
+
# The sequence instance
|
166
|
+
# scope: (object)(optional)
|
167
|
+
# The object the sequence should be evaluated within
|
168
|
+
#
|
169
|
+
def increment_sequence(sequence, scope: nil)
|
170
|
+
value = sequence.next(scope)
|
171
|
+
|
172
|
+
raise if value.respond_to?(:start_with?) && value.start_with?("#<FactoryBot::Declaration")
|
173
|
+
|
174
|
+
value
|
175
|
+
rescue
|
176
|
+
raise ArgumentError, "Sequence '#{sequence.uri_manager.first}' failed to " \
|
177
|
+
"return a value. Perhaps it needs a scope to operate? (scope: <object>)"
|
178
|
+
end
|
132
179
|
end
|
133
180
|
end
|
134
181
|
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
|
@@ -15,12 +20,9 @@ module FactoryBot
|
|
15
20
|
end
|
16
21
|
|
17
22
|
def clone
|
18
|
-
Trait.new(name, &block)
|
23
|
+
Trait.new(name, uri_paths: definition.uri_manager.paths, &block)
|
19
24
|
end
|
20
25
|
|
21
|
-
delegate :add_callback, :declare_attribute, :to_create, :define_trait, :constructor,
|
22
|
-
:callbacks, :attributes, :klass, :klass=, to: :@definition
|
23
|
-
|
24
26
|
def names
|
25
27
|
[@name]
|
26
28
|
end
|