factory_bot 6.5.1 → 6.5.6

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.
data/README.md CHANGED
@@ -1,20 +1,17 @@
1
- # factory_bot [![Build Status][ci-image]][ci] [![Code Climate][grade-image]][grade] [![Gem Version][version-image]][version]
1
+ # factory_bot
2
+
3
+ [![Build Status][ci-image]][ci] [![Code Climate][grade-image]][grade] [![Gem Version][version-image]][version]
2
4
 
3
5
  factory_bot is a fixtures replacement with a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute hashes, and stubbed objects), and support for multiple factories for the same class (user, admin_user, and so on), including factory inheritance.
4
6
 
5
7
  If you want to use factory_bot with Rails, see
6
8
  [factory_bot_rails](https://github.com/thoughtbot/factory_bot_rails).
7
9
 
8
- _[Interested in the history of the project name?][NAME]_
9
-
10
-
11
- ### Transitioning from factory\_girl?
10
+ Interested in the history of the project name? You can find the history [here](https://github.com/thoughtbot/factory_bot/blob/main/NAME.md)
12
11
 
13
- Check out the [guide](https://github.com/thoughtbot/factory_bot/blob/4-9-0-stable/UPGRADE_FROM_FACTORY_GIRL.md).
12
+ Transitioning from factory\_girl? Check out the [upgrade guide](https://github.com/thoughtbot/factory_bot/blob/v4.9.0/UPGRADE_FROM_FACTORY_GIRL.md).
14
13
 
15
-
16
- Documentation
17
- -------------
14
+ ## Documentation
18
15
 
19
16
  See our extensive reference, guides, and cookbook in [the factory_bot book][].
20
17
 
@@ -27,8 +24,7 @@ Rails, see [the factory_bot wiki][].
27
24
  [the factory_bot book]: https://thoughtbot.github.io/factory_bot
28
25
  [the factory_bot wiki]: https://github.com/thoughtbot/factory_bot/wiki
29
26
 
30
- Install
31
- --------
27
+ ## Install
32
28
 
33
29
  Run:
34
30
 
@@ -42,13 +38,11 @@ To install the gem manually from your shell, run:
42
38
  gem install factory_bot
43
39
  ```
44
40
 
45
- Supported Ruby versions
46
- -----------------------
41
+ ## Supported Ruby versions
47
42
 
48
- Supported Ruby versions are listed in [`.github/workflows/build.yml`](https://github.com/thoughtbot/factory_bot/blob/main/.github/workflows/build.yml)
43
+ Supported Ruby versions are listed in `.github/workflows/build.yml` ([source](https://github.com/thoughtbot/factory_bot/blob/main/.github/workflows/build.yml))
49
44
 
50
- More Information
51
- ----------------
45
+ ## More Information
52
46
 
53
47
  * [Rubygems](https://rubygems.org/gems/factory_bot)
54
48
  * [Stack Overflow](https://stackoverflow.com/questions/tagged/factory-bot)
@@ -56,12 +50,11 @@ More Information
56
50
  * [GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS](https://robots.thoughtbot.com/)
57
51
 
58
52
  [GETTING_STARTED]: https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md
59
- [NAME]: https://github.com/thoughtbot/factory_bot/blob/main/NAME.md
60
53
 
61
- Useful Tools
62
- ------------
54
+ ## Useful Tools
63
55
 
64
56
  * [FactoryTrace](https://github.com/djezzzl/factory_trace) - helps to find unused factories and traits.
57
+ * [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
58
 
66
59
  Contributing
67
60
  ------------
@@ -96,7 +89,6 @@ We are [available for hire][hire].
96
89
  [community]: https://thoughtbot.com/community?utm_source=github
97
90
  [hire]: https://thoughtbot.com/hire-us?utm_source=github
98
91
 
99
-
100
92
  <!-- END /templates/footer.md -->
101
93
 
102
94
  [ci-image]: https://github.com/thoughtbot/factory_bot/actions/workflows/build.yml/badge.svg?branch=main
@@ -9,6 +9,7 @@ module FactoryBot
9
9
  @attribute_names_assigned = []
10
10
  end
11
11
 
12
+ # constructs an object-based factory product
12
13
  def object
13
14
  @evaluator.instance = build_class_instance
14
15
  build_class_instance.tap do |instance|
@@ -19,6 +20,7 @@ module FactoryBot
19
20
  end
20
21
  end
21
22
 
23
+ # constructs a Hash-based factory product
22
24
  def hash
23
25
  @evaluator.instance = build_hash
24
26
 
@@ -29,6 +31,8 @@ module FactoryBot
29
31
 
30
32
  private
31
33
 
34
+ # Track evaluation of methods on the evaluator to prevent the duplicate
35
+ # assignment of attributes accessed and via `initialize_with` syntax
32
36
  def method_tracking_evaluator
33
37
  @method_tracking_evaluator ||= Decorator::AttributeHash.new(
34
38
  decorated_evaluator,
@@ -67,12 +71,15 @@ module FactoryBot
67
71
  attribute_names_to_assign - association_names
68
72
  end
69
73
 
74
+ # Builds a list of attributes names that should be assigned to the factory product
70
75
  def attribute_names_to_assign
71
- @attribute_names_to_assign ||=
72
- non_ignored_attribute_names +
73
- override_names -
74
- ignored_attribute_names -
75
- alias_names_to_ignore
76
+ @attribute_names_to_assign ||= begin
77
+ # start a list of candidates containing non-transient attributes and overrides
78
+ assignment_candidates = non_ignored_attribute_names + override_names
79
+ # then remove any transient attributes (potentially reintroduced by the overrides),
80
+ # and remove ignorable aliased attributes from the candidate list
81
+ assignment_candidates - ignored_attribute_names - attribute_names_overriden_by_alias
82
+ end
76
83
  end
77
84
 
78
85
  def non_ignored_attribute_names
@@ -91,22 +98,71 @@ module FactoryBot
91
98
  @evaluator.__override_names__
92
99
  end
93
100
 
94
- def hash_instance_methods_to_respond_to
95
- @attribute_list.names + override_names + @build_class.instance_methods
96
- end
97
-
98
- def alias_names_to_ignore
99
- @attribute_list.non_ignored.flat_map { |attribute|
100
- override_names.map do |override|
101
- attribute.name if ignorable_alias?(attribute, override)
102
- end
103
- }.compact
101
+ def attribute_names
102
+ @attribute_list.names
104
103
  end
105
104
 
105
+ def hash_instance_methods_to_respond_to
106
+ attribute_names + override_names + @build_class.instance_methods
107
+ end
108
+
109
+ # Builds a list of attribute names which are slated to be interrupted by an override.
110
+ def attribute_names_overriden_by_alias
111
+ @attribute_list
112
+ .non_ignored
113
+ .flat_map { |attribute|
114
+ override_names.map do |override|
115
+ attribute.name if ignorable_alias?(attribute, override)
116
+ end
117
+ }
118
+ .compact
119
+ end
120
+
121
+ # Is the attribute an ignorable alias of the override?
122
+ # An attribute is ignorable when it is an alias of the override AND it is
123
+ # either interrupting an assocciation OR is not the name of another attribute
124
+ #
125
+ # @note An "alias" is currently an overloaded term for two distinct cases:
126
+ # (1) attributes which are aliases and reference the same value
127
+ # (2) a logical grouping of a foreign key and an associated object
106
128
  def ignorable_alias?(attribute, override)
107
- attribute.alias_for?(override) &&
108
- attribute.name != override &&
109
- !ignored_attribute_names.include?(override)
129
+ return false unless attribute.alias_for?(override)
130
+
131
+ # The attribute alias should be ignored when the override interrupts an association
132
+ return true if override_interrupts_association?(attribute, override)
133
+
134
+ # Remaining aliases should be ignored when the override does not match a declared attribute.
135
+ # An override which is an alias to a declared attribute should not interrupt the aliased
136
+ # attribute and interrupt only the attribute with a matching name. This workaround allows a
137
+ # factory to declare both <attribute> and <attribute>_id as separate and distinct attributes.
138
+ !override_matches_declared_attribute?(override)
139
+ end
140
+
141
+ # Does this override interrupt an association?
142
+ # When true, this indicates the aliased attribute is related to a declared association and the
143
+ # override does not match the attribute name.
144
+ #
145
+ # @note Association overrides should take precedence over a declared foreign key attribute.
146
+ #
147
+ # @note An override may interrupt an association by providing the associated object or
148
+ # by providing the foreign key.
149
+ #
150
+ # @param [FactoryBot::Attribute] aliased_attribute
151
+ # @param [Symbol] override name of an override which is an alias to the attribute name
152
+ def override_interrupts_association?(aliased_attribute, override)
153
+ (aliased_attribute.association? || association_names.include?(override)) &&
154
+ aliased_attribute.name != override
155
+ end
156
+
157
+ # Does this override match the name of any declared attribute?
158
+ #
159
+ # @note Checking against the names of all attributes, resolves any issues with having both
160
+ # <attribute> and <attribute>_id in the same factory. This also takes into account ignored
161
+ # attributes that should not be assigned (aka transient attributes)
162
+ #
163
+ # @param [Symbol] override the name of an override
164
+ def override_matches_declared_attribute?(override)
165
+ attribute_names.include?(override)
110
166
  end
111
167
  end
112
168
  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
- callback.run(result_instance, @evaluator)
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
- sequence = Sequence.new(name, ...)
124
- FactoryBot::Internal.register_inline_sequence(sequence)
125
- add_attribute(name) { increment_sequence(sequence) }
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
- @child_factories << [name, options, block]
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(self)
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
@@ -12,7 +12,8 @@ module FactoryBot
12
12
  @parent = options[:parent]
13
13
  @aliases = options[:aliases] || []
14
14
  @class_name = options[:class]
15
- @definition = Definition.new(@name, options[:traits] || [])
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,18 +33,22 @@ module FactoryBot
32
33
 
33
34
  def run(build_strategy, overrides, &block)
34
35
  block ||= ->(result) { result }
36
+
35
37
  compile
36
38
 
37
- strategy = StrategyCalculator.new(build_strategy).strategy.new
39
+ strategy = Strategy.lookup_strategy(build_strategy).new
38
40
 
39
41
  evaluator = evaluator_class.new(strategy, overrides.symbolize_keys)
40
42
  attribute_assigner = AttributeAssigner.new(evaluator, build_class, &compiled_constructor)
41
43
 
42
44
  observer = CallbacksObserver.new(callbacks, evaluator)
43
- evaluation =
44
- Evaluation.new(evaluator, attribute_assigner, compiled_to_create, observer)
45
+ evaluation = Evaluation.new(evaluator, attribute_assigner, compiled_to_create, observer)
46
+
47
+ evaluation.notify(:before_all, nil)
48
+ instance = strategy.result(evaluation).tap(&block)
49
+ evaluation.notify(:after_all, instance)
45
50
 
46
- strategy.result(evaluation).tap(&block)
51
+ instance
47
52
  end
48
53
 
49
54
  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", "test/factories" and "spec/factories". Only the first
6
- # existing file will be loaded.
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
 
@@ -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
@@ -1,4 +1,4 @@
1
- require "active_support/core_ext/hash/indifferent_access"
1
+ require "active_support/hash_with_indifferent_access"
2
2
 
3
3
  module FactoryBot
4
4
  class Registry
@@ -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
- options = args.extract_options!
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(value)
53
- @first_value = value
54
- @value = value
153
+ def initialize(initial_value)
154
+ @initial_value = initial_value
55
155
  end
56
156
 
57
157
  def peek
58
- @value
158
+ value
59
159
  end
60
160
 
61
161
  def next
62
- @value = @value.next
162
+ @value = value.next
63
163
  end
64
164
 
65
165
  def rewind
66
- @value = @first_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
@@ -6,6 +6,8 @@ module FactoryBot
6
6
  end
7
7
 
8
8
  def result(evaluation)
9
+ evaluation.notify(:before_build, nil)
10
+
9
11
  evaluation.object.tap do |instance|
10
12
  evaluation.notify(:after_build, instance)
11
13
  end
@@ -6,6 +6,8 @@ module FactoryBot
6
6
  end
7
7
 
8
8
  def result(evaluation)
9
+ evaluation.notify(:before_build, nil)
10
+
9
11
  evaluation.object.tap do |instance|
10
12
  evaluation.notify(:after_build, instance)
11
13
  evaluation.notify(:before_create, instance)
@@ -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 = Time.current
108
+ result_instance.created_at = timestamp
107
109
  end
108
110
 
109
111
  if missing_updated_at?(result_instance)
110
- result_instance.updated_at = Time.current
112
+ result_instance.updated_at = timestamp
111
113
  end
112
114
  end
113
115
 
@@ -0,0 +1,15 @@
1
+ require "factory_bot/strategy/build"
2
+ require "factory_bot/strategy/create"
3
+ require "factory_bot/strategy/attributes_for"
4
+ require "factory_bot/strategy/stub"
5
+ require "factory_bot/strategy/null"
6
+
7
+ module FactoryBot
8
+ module Strategy
9
+ def self.lookup_strategy(name_or_object)
10
+ return name_or_object if name_or_object.is_a?(Class)
11
+
12
+ FactoryBot::Internal.strategy_by_name(name_or_object)
13
+ end
14
+ end
15
+ end