factory_bot 6.4.4 → 6.6.0

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] [![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?
12
-
13
- Check out the [guide](https://github.com/thoughtbot/factory_bot/blob/4-9-0-stable/UPGRADE_FROM_FACTORY_GIRL.md).
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)
14
11
 
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).
15
13
 
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
  ------------
@@ -75,31 +68,31 @@ community](https://github.com/thoughtbot/factory_bot/graphs/contributors).
75
68
  License
76
69
  -------
77
70
 
78
- factory_bot is Copyright © 2008-2022 Joe Ferris and thoughtbot. It is free
71
+ factory_bot is Copyright © 2008 Joe Ferris and thoughtbot. It is free
79
72
  software, and may be redistributed under the terms specified in the
80
73
  [LICENSE] file.
81
74
 
82
75
  [LICENSE]: https://github.com/thoughtbot/factory_bot/blob/main/LICENSE
83
76
 
77
+ <!-- START /templates/footer.md -->
78
+ ## About thoughtbot
84
79
 
85
- About thoughtbot
86
- ----------------
87
-
88
- ![thoughtbot](https://thoughtbot.com/brand_assets/93:44.svg)
80
+ ![thoughtbot](https://thoughtbot.com/thoughtbot-logo-for-readmes.svg)
89
81
 
90
- factory_bot is maintained and funded by thoughtbot, inc.
82
+ This repo is maintained and funded by thoughtbot, inc.
91
83
  The names and logos for thoughtbot are trademarks of thoughtbot, inc.
92
84
 
93
85
  We love open source software!
94
- See [our other projects][community] or
95
- [hire us][hire] to design, develop, and grow your product.
86
+ See [our other projects][community].
87
+ We are [available for hire][hire].
96
88
 
97
89
  [community]: https://thoughtbot.com/community?utm_source=github
98
90
  [hire]: https://thoughtbot.com/hire-us?utm_source=github
91
+
92
+ <!-- END /templates/footer.md -->
93
+
99
94
  [ci-image]: https://github.com/thoughtbot/factory_bot/actions/workflows/build.yml/badge.svg?branch=main
100
95
  [ci]: https://github.com/thoughtbot/factory_bot/actions?query=workflow%3ABuild+branch%3Amain
101
- [grade-image]: https://codeclimate.com/github/thoughtbot/factory_bot/badges/gpa.svg
102
- [grade]: https://codeclimate.com/github/thoughtbot/factory_bot
103
96
  [version-image]: https://badge.fury.io/rb/factory_bot.svg
104
97
  [version]: https://badge.fury.io/rb/factory_bot
105
98
  [hound-badge-image]: https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg
@@ -9,16 +9,18 @@ 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|
15
16
  attributes_to_set_on_instance.each do |attribute|
16
- instance.public_send("#{attribute}=", get(attribute))
17
+ instance.public_send(:"#{attribute}=", get(attribute))
17
18
  @attribute_names_assigned << attribute
18
19
  end
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
@@ -6,24 +6,12 @@ module FactoryBot
6
6
  @component = component
7
7
  end
8
8
 
9
- if ::Gem::Version.new(::RUBY_VERSION) >= ::Gem::Version.new("2.7")
10
- class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
11
- def method_missing(...) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
12
- @component.send(...)
13
- end
14
-
15
- def send(...)
16
- __send__(...)
17
- end
18
- RUBY
19
- else
20
- def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing
21
- @component.send(name, *args, &block)
22
- end
9
+ def method_missing(...) # rubocop:disable Style/MethodMissingSuper
10
+ @component.send(...)
11
+ end
23
12
 
24
- def send(symbol, *args, &block)
25
- __send__(symbol, *args, &block)
26
- end
13
+ def send(...)
14
+ __send__(...)
27
15
  end
28
16
 
29
17
  def respond_to_missing?(name, include_private = false)
@@ -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
@@ -95,6 +96,10 @@ module FactoryBot
95
96
  @defined_traits.add(trait)
96
97
  end
97
98
 
99
+ def defined_traits_names
100
+ @defined_traits.map(&:name)
101
+ end
102
+
98
103
  def register_enum(enum)
99
104
  @registered_enums << enum
100
105
  end
@@ -94,7 +94,7 @@ module FactoryBot
94
94
  if association_options.nil?
95
95
  __declare_attribute__(name, block)
96
96
  elsif __valid_association_options?(association_options)
97
- association(name, association_options)
97
+ association(name, association_options, &block)
98
98
  else
99
99
  raise NoMethodError.new(<<~MSG)
100
100
  undefined method '#{name}' in '#{@definition.name}' factory
@@ -120,9 +120,13 @@ module FactoryBot
120
120
  #
121
121
  # Except that no globally available sequence will be defined.
122
122
  def sequence(name, *args, &block)
123
- sequence = Sequence.new(name, *args, &block)
124
- FactoryBot::Internal.register_inline_sequence(sequence)
125
- add_attribute(name) { increment_sequence(sequence) }
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
@@ -35,14 +34,13 @@ module FactoryBot
35
34
 
36
35
  attr_accessor :instance
37
36
 
38
- def method_missing(method_name, *args, &block)
37
+ def method_missing(method_name, ...)
39
38
  if @instance.respond_to?(method_name)
40
- @instance.send(method_name, *args, &block)
39
+ @instance.send(method_name, ...)
41
40
  else
42
- SyntaxRunner.new.send(method_name, *args, &block)
41
+ SyntaxRunner.new.send(method_name, ...)
43
42
  end
44
43
  end
45
- ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
46
44
 
47
45
  def respond_to_missing?(method_name, _include_private = false)
48
46
  @instance.respond_to?(method_name) || SyntaxRunner.new.respond_to?(method_name)
@@ -52,8 +50,15 @@ module FactoryBot
52
50
  @overrides.keys
53
51
  end
54
52
 
55
- def increment_sequence(sequence)
56
- 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>)"
57
62
  end
58
63
 
59
64
  def self.attribute_list
@@ -12,16 +12,20 @@ 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
 
19
20
  delegate :add_callback, :declare_attribute, :to_create, :define_trait, :constructor,
20
- :defined_traits, :inherit_traits, :append_traits, to: :@definition
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
@@ -29,18 +33,22 @@ module FactoryBot
29
33
 
30
34
  def run(build_strategy, overrides, &block)
31
35
  block ||= ->(result) { result }
36
+
32
37
  compile
33
38
 
34
- strategy = StrategyCalculator.new(build_strategy).strategy.new
39
+ strategy = Strategy.lookup_strategy(build_strategy).new
35
40
 
36
41
  evaluator = evaluator_class.new(strategy, overrides.symbolize_keys)
37
42
  attribute_assigner = AttributeAssigner.new(evaluator, build_class, &compiled_constructor)
38
43
 
39
44
  observer = CallbacksObserver.new(callbacks, evaluator)
40
- evaluation =
41
- 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)
42
50
 
43
- strategy.result(evaluation).tap(&block)
51
+ instance
44
52
  end
45
53
 
46
54
  def human_names
@@ -83,7 +91,7 @@ module FactoryBot
83
91
  def compile
84
92
  unless @compiled
85
93
  parent.compile
86
- parent.defined_traits.each { |trait| define_trait(trait) }
94
+ inherit_parent_traits
87
95
  @definition.compile(build_class)
88
96
  build_hierarchy
89
97
  @compiled = true
@@ -151,6 +159,13 @@ module FactoryBot
151
159
  end
152
160
  end
153
161
 
162
+ def inherit_parent_traits
163
+ parent.defined_traits.each do |trait|
164
+ next if defined_traits_names.include?(trait.name)
165
+ define_trait(trait.clone)
166
+ end
167
+ end
168
+
154
169
  def initialize_copy(source)
155
170
  super
156
171
  @definition = @definition.clone
@@ -25,6 +25,8 @@ module FactoryBot
25
25
  factory: factory
26
26
  }
27
27
 
28
+ ActiveSupport::Notifications.instrument("factory_bot.before_run_factory", instrumentation_payload)
29
+
28
30
  ActiveSupport::Notifications.instrument("factory_bot.run_factory", instrumentation_payload) do
29
31
  factory.run(runner_strategy, @overrides, &block)
30
32
  end
@@ -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
@@ -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(joinable: false) do
113
+ yield
114
+ raise ActiveRecord::Rollback
115
+ end
116
+ else
117
+ yield
118
+ end
119
+ end
109
120
  end
110
121
  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