factory_bot 5.1.0 → 6.0.1

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +52 -13
  3. data/GETTING_STARTED.md +426 -68
  4. data/NEWS.md +22 -1
  5. data/README.md +4 -4
  6. data/lib/factory_bot.rb +21 -55
  7. data/lib/factory_bot/aliases.rb +3 -3
  8. data/lib/factory_bot/attribute/association.rb +2 -2
  9. data/lib/factory_bot/attribute/dynamic.rb +2 -1
  10. data/lib/factory_bot/attribute_assigner.rb +9 -10
  11. data/lib/factory_bot/attribute_list.rb +1 -1
  12. data/lib/factory_bot/callback.rb +2 -10
  13. data/lib/factory_bot/configuration.rb +6 -6
  14. data/lib/factory_bot/declaration.rb +1 -1
  15. data/lib/factory_bot/declaration_list.rb +2 -2
  16. data/lib/factory_bot/decorator.rb +1 -1
  17. data/lib/factory_bot/decorator/invocation_tracker.rb +1 -1
  18. data/lib/factory_bot/definition.rb +45 -17
  19. data/lib/factory_bot/definition_hierarchy.rb +1 -11
  20. data/lib/factory_bot/definition_proxy.rb +64 -6
  21. data/lib/factory_bot/enum.rb +27 -0
  22. data/lib/factory_bot/evaluator.rb +5 -7
  23. data/lib/factory_bot/evaluator_class_definer.rb +1 -1
  24. data/lib/factory_bot/factory.rb +12 -12
  25. data/lib/factory_bot/factory_runner.rb +3 -3
  26. data/lib/factory_bot/find_definitions.rb +1 -1
  27. data/lib/factory_bot/internal.rb +18 -29
  28. data/lib/factory_bot/linter.rb +9 -13
  29. data/lib/factory_bot/null_factory.rb +10 -4
  30. data/lib/factory_bot/null_object.rb +2 -6
  31. data/lib/factory_bot/registry.rb +4 -4
  32. data/lib/factory_bot/reload.rb +0 -1
  33. data/lib/factory_bot/sequence.rb +5 -5
  34. data/lib/factory_bot/strategy/null.rb +4 -2
  35. data/lib/factory_bot/strategy/stub.rb +6 -2
  36. data/lib/factory_bot/strategy_syntax_method_registrar.rb +12 -1
  37. data/lib/factory_bot/syntax/default.rb +7 -19
  38. data/lib/factory_bot/trait.rb +1 -1
  39. data/lib/factory_bot/version.rb +1 -1
  40. metadata +9 -22
@@ -1,16 +1,6 @@
1
1
  module FactoryBot
2
2
  class DefinitionHierarchy
3
- def callbacks
4
- FactoryBot.callbacks
5
- end
6
-
7
- def constructor
8
- FactoryBot.constructor
9
- end
10
-
11
- def to_create
12
- FactoryBot.to_create
13
- end
3
+ delegate :callbacks, :constructor, :to_create, to: Internal
14
4
 
15
5
  def self.build_from_definition(definition)
16
6
  build_to_create(&definition.to_create)
@@ -1,6 +1,6 @@
1
1
  module FactoryBot
2
2
  class DefinitionProxy
3
- UNPROXIED_METHODS = %w(
3
+ UNPROXIED_METHODS = %w[
4
4
  __send__
5
5
  __id__
6
6
  nil?
@@ -13,7 +13,7 @@ module FactoryBot
13
13
  raise
14
14
  caller
15
15
  method
16
- ).freeze
16
+ ].freeze
17
17
 
18
18
  (instance_methods + private_instance_methods).each do |method|
19
19
  undef_method(method) unless UNPROXIED_METHODS.include?(method.to_s)
@@ -24,8 +24,8 @@ module FactoryBot
24
24
  attr_reader :child_factories
25
25
 
26
26
  def initialize(definition, ignore = false)
27
- @definition = definition
28
- @ignore = ignore
27
+ @definition = definition
28
+ @ignore = ignore
29
29
  @child_factories = []
30
30
  end
31
31
 
@@ -88,7 +88,7 @@ module FactoryBot
88
88
  # end
89
89
  #
90
90
  # are equivalent.
91
- def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissing
91
+ def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing, Style/MethodMissingSuper
92
92
  association_options = args.first
93
93
 
94
94
  if association_options.nil?
@@ -152,7 +152,7 @@ module FactoryBot
152
152
  if block_given?
153
153
  raise AssociationDefinitionError.new(
154
154
  "Unexpected block passed to '#{name}' association "\
155
- "in '#{@definition.name}' factory",
155
+ "in '#{@definition.name}' factory"
156
156
  )
157
157
  else
158
158
  declaration = Declaration::Association.new(name, *options)
@@ -176,6 +176,64 @@ module FactoryBot
176
176
  @definition.define_trait(Trait.new(name, &block))
177
177
  end
178
178
 
179
+ # Creates traits for enumerable values.
180
+ #
181
+ # Example:
182
+ # factory :task do
183
+ # traits_for_enum :status, [:started, :finished]
184
+ # end
185
+ #
186
+ # Equivalent to:
187
+ # factory :task do
188
+ # trait :started do
189
+ # status { :started }
190
+ # end
191
+ #
192
+ # trait :finished do
193
+ # status { :finished }
194
+ # end
195
+ # end
196
+ #
197
+ # Example:
198
+ # factory :task do
199
+ # traits_for_enum :status, {started: 1, finished: 2}
200
+ # end
201
+ #
202
+ # Example:
203
+ # class Task
204
+ # def statuses
205
+ # {started: 1, finished: 2}
206
+ # end
207
+ # end
208
+ #
209
+ # factory :task do
210
+ # traits_for_enum :status
211
+ # end
212
+ #
213
+ # Both equivalent to:
214
+ # factory :task do
215
+ # trait :started do
216
+ # status { 1 }
217
+ # end
218
+ #
219
+ # trait :finished do
220
+ # status { 2 }
221
+ # end
222
+ # end
223
+ #
224
+ #
225
+ # Arguments:
226
+ # attribute_name: +Symbol+ or +String+
227
+ # the name of the attribute these traits will set the value of
228
+ # values: +Array+, +Hash+, or other +Enumerable+
229
+ # An array of trait names, or a mapping of trait names to values for
230
+ # those traits. When this argument is not provided, factory_bot will
231
+ # attempt to get the values by calling the pluralized `attribute_name`
232
+ # class method.
233
+ def traits_for_enum(attribute_name, values = nil)
234
+ @definition.register_enum(Enum.new(attribute_name, values))
235
+ end
236
+
179
237
  def initialize_with(&block)
180
238
  @definition.define_constructor(&block)
181
239
  end
@@ -0,0 +1,27 @@
1
+ module FactoryBot
2
+ # @api private
3
+ class Enum
4
+ def initialize(attribute_name, values = nil)
5
+ @attribute_name = attribute_name
6
+ @values = values
7
+ end
8
+
9
+ def build_traits(klass)
10
+ enum_values(klass).map do |trait_name, value|
11
+ build_trait(trait_name, @attribute_name, value || trait_name)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def enum_values(klass)
18
+ @values || klass.send(@attribute_name.to_s.pluralize)
19
+ end
20
+
21
+ def build_trait(trait_name, attribute_name, value)
22
+ Trait.new(trait_name) do
23
+ add_attribute(attribute_name) { value }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -7,7 +7,7 @@ module FactoryBot
7
7
  class_attribute :attribute_lists
8
8
 
9
9
  private_instance_methods.each do |method|
10
- undef_method(method) unless method =~ /^__|initialize/
10
+ undef_method(method) unless method.match?(/^__|initialize/)
11
11
  end
12
12
 
13
13
  def initialize(build_strategy, overrides = {})
@@ -23,9 +23,9 @@ module FactoryBot
23
23
 
24
24
  def association(factory_name, *traits_and_overrides)
25
25
  overrides = traits_and_overrides.extract_options!
26
- strategy_override = overrides.fetch(:strategy) do
26
+ strategy_override = overrides.fetch(:strategy) {
27
27
  FactoryBot.use_parent_strategy ? @build_strategy.class : :create
28
- end
28
+ }
29
29
 
30
30
  traits_and_overrides += [overrides.except(:strategy)]
31
31
 
@@ -33,11 +33,9 @@ module FactoryBot
33
33
  @build_strategy.association(runner)
34
34
  end
35
35
 
36
- def instance=(object_instance)
37
- @instance = object_instance
38
- end
36
+ attr_writer :instance
39
37
 
40
- def method_missing(method_name, *args, &block) # rubocop:disable Style/MethodMissing
38
+ def method_missing(method_name, *args, &block) # rubocop:disable Style/MethodMissingSuper
41
39
  if @instance.respond_to?(method_name)
42
40
  @instance.send(method_name, *args, &block)
43
41
  else
@@ -3,7 +3,7 @@ module FactoryBot
3
3
  class EvaluatorClassDefiner
4
4
  def initialize(attributes, parent_class)
5
5
  @parent_class = parent_class
6
- @attributes = attributes
6
+ @attributes = attributes
7
7
 
8
8
  attributes.each do |attribute|
9
9
  evaluator_class.define_attribute(attribute.name, &attribute.to_proc)
@@ -8,23 +8,23 @@ module FactoryBot
8
8
 
9
9
  def initialize(name, options = {})
10
10
  assert_valid_options(options)
11
- @name = name.respond_to?(:to_sym) ? name.to_sym : name.to_s.underscore.to_sym
12
- @parent = options[:parent]
13
- @aliases = options[:aliases] || []
14
- @class_name = options[:class]
15
- @definition = Definition.new(@name, options[:traits] || [])
16
- @compiled = false
11
+ @name = name.respond_to?(:to_sym) ? name.to_sym : name.to_s.underscore.to_sym
12
+ @parent = options[:parent]
13
+ @aliases = options[:aliases] || []
14
+ @class_name = options[:class]
15
+ @definition = Definition.new(@name, options[:traits] || [])
16
+ @compiled = false
17
17
  end
18
18
 
19
19
  delegate :add_callback, :declare_attribute, :to_create, :define_trait, :constructor,
20
- :defined_traits, :inherit_traits, :append_traits, to: :@definition
20
+ :defined_traits, :inherit_traits, :append_traits, to: :@definition
21
21
 
22
22
  def build_class
23
23
  @build_class ||= if class_name.is_a? Class
24
- class_name
25
- else
26
- class_name.to_s.camelize.constantize
27
- end
24
+ class_name
25
+ else
26
+ class_name.to_s.camelize.constantize
27
+ end
28
28
  end
29
29
 
30
30
  def run(build_strategy, overrides, &block)
@@ -84,7 +84,7 @@ module FactoryBot
84
84
  unless @compiled
85
85
  parent.compile
86
86
  parent.defined_traits.each { |trait| define_trait(trait) }
87
- @definition.compile
87
+ @definition.compile(build_class)
88
88
  build_hierarchy
89
89
  @compiled = true
90
90
  end
@@ -1,11 +1,11 @@
1
1
  module FactoryBot
2
2
  class FactoryRunner
3
3
  def initialize(name, strategy, traits_and_overrides)
4
- @name = name
4
+ @name = name
5
5
  @strategy = strategy
6
6
 
7
7
  @overrides = traits_and_overrides.extract_options!
8
- @traits = traits_and_overrides
8
+ @traits = traits_and_overrides
9
9
  end
10
10
 
11
11
  def run(runner_strategy = @strategy, &block)
@@ -22,7 +22,7 @@ module FactoryBot
22
22
  strategy: runner_strategy,
23
23
  traits: @traits,
24
24
  overrides: @overrides,
25
- factory: factory,
25
+ factory: factory
26
26
  }
27
27
 
28
28
  ActiveSupport::Notifications.instrument("factory_bot.run_factory", instrumentation_payload) do
@@ -7,7 +7,7 @@ module FactoryBot
7
7
  attr_accessor :definition_file_paths
8
8
  end
9
9
 
10
- self.definition_file_paths = %w(factories test/factories spec/factories)
10
+ self.definition_file_paths = %w[factories test/factories spec/factories]
11
11
 
12
12
  def self.find_definitions
13
13
  absolute_definition_file_paths = definition_file_paths.map { |path| File.expand_path(path) }
@@ -1,26 +1,20 @@
1
1
  module FactoryBot
2
2
  # @api private
3
3
  module Internal
4
- DEFAULT_STRATEGIES = {
5
- build: FactoryBot::Strategy::Build,
6
- create: FactoryBot::Strategy::Create,
7
- attributes_for: FactoryBot::Strategy::AttributesFor,
8
- build_stubbed: FactoryBot::Strategy::Stub,
9
- null: FactoryBot::Strategy::Null,
10
- }.freeze
11
-
12
- DEFAULT_CALLBACKS = [
13
- :after_create, :after_build, :after_stub, :after_create
14
- ].freeze
15
-
16
4
  class << self
17
- delegate :callback_names,
18
- :factories,
19
- :inline_sequences,
20
- :sequences,
21
- :strategies,
22
- :traits,
23
- to: :configuration
5
+ delegate :after,
6
+ :before,
7
+ :callbacks,
8
+ :constructor,
9
+ :factories,
10
+ :initialize_with,
11
+ :inline_sequences,
12
+ :sequences,
13
+ :skip_create,
14
+ :strategies,
15
+ :to_create,
16
+ :traits,
17
+ to: :configuration
24
18
 
25
19
  def configuration
26
20
  @configuration ||= Configuration.new
@@ -86,16 +80,11 @@ module FactoryBot
86
80
  end
87
81
 
88
82
  def register_default_strategies
89
- DEFAULT_STRATEGIES.each(&method(:register_strategy))
90
- end
91
-
92
- def register_default_callbacks
93
- DEFAULT_CALLBACKS.each(&method(:register_callback))
94
- end
95
-
96
- def register_callback(name)
97
- name = name.to_sym
98
- callback_names << name
83
+ register_strategy(:build, FactoryBot::Strategy::Build)
84
+ register_strategy(:create, FactoryBot::Strategy::Create)
85
+ register_strategy(:attributes_for, FactoryBot::Strategy::AttributesFor)
86
+ register_strategy(:build_stubbed, FactoryBot::Strategy::Stub)
87
+ register_strategy(:null, FactoryBot::Strategy::Null)
99
88
  end
100
89
  end
101
90
  end
@@ -19,17 +19,16 @@ module FactoryBot
19
19
  attr_reader :factories_to_lint, :invalid_factories, :factory_strategy
20
20
 
21
21
  def calculate_invalid_factories
22
- factories_to_lint.reduce(Hash.new([])) do |result, factory|
22
+ factories_to_lint.each_with_object(Hash.new([])) do |factory, result|
23
23
  errors = lint(factory)
24
24
  result[factory] |= errors unless errors.empty?
25
- result
26
25
  end
27
26
  end
28
27
 
29
28
  class FactoryError
30
29
  def initialize(wrapped_error, factory)
31
30
  @wrapped_error = wrapped_error
32
- @factory = factory
31
+ @factory = factory
33
32
  end
34
33
 
35
34
  def message
@@ -72,8 +71,8 @@ module FactoryBot
72
71
  result = []
73
72
  begin
74
73
  FactoryBot.public_send(factory_strategy, factory.name)
75
- rescue StandardError => error
76
- result |= [FactoryError.new(error, factory)]
74
+ rescue => e
75
+ result |= [FactoryError.new(e, factory)]
77
76
  end
78
77
  result
79
78
  end
@@ -81,20 +80,17 @@ module FactoryBot
81
80
  def lint_traits(factory)
82
81
  result = []
83
82
  factory.definition.defined_traits.map(&:name).each do |trait_name|
84
- begin
85
- FactoryBot.public_send(factory_strategy, factory.name, trait_name)
86
- rescue StandardError => error
87
- result |=
88
- [FactoryTraitError.new(error, factory, trait_name)]
89
- end
83
+ FactoryBot.public_send(factory_strategy, factory.name, trait_name)
84
+ rescue => e
85
+ result |= [FactoryTraitError.new(e, factory, trait_name)]
90
86
  end
91
87
  result
92
88
  end
93
89
 
94
90
  def error_message
95
- lines = invalid_factories.map do |_factory, exceptions|
91
+ lines = invalid_factories.map { |_factory, exceptions|
96
92
  exceptions.map(&error_message_type)
97
- end.flatten
93
+ }.flatten
98
94
 
99
95
  <<~ERROR_MESSAGE.strip
100
96
  The following factories are invalid:
@@ -10,12 +10,18 @@ module FactoryBot
10
10
  delegate :defined_traits, :callbacks, :attributes, :constructor,
11
11
  :to_create, to: :definition
12
12
 
13
- def compile; end
13
+ def compile
14
+ end
14
15
 
15
- def class_name; end
16
+ def class_name
17
+ end
16
18
 
17
- def evaluator_class; FactoryBot::Evaluator; end
19
+ def evaluator_class
20
+ FactoryBot::Evaluator
21
+ end
18
22
 
19
- def hierarchy_class; FactoryBot::DefinitionHierarchy; end
23
+ def hierarchy_class
24
+ FactoryBot::DefinitionHierarchy
25
+ end
20
26
  end
21
27
  end
@@ -5,7 +5,7 @@ module FactoryBot
5
5
  @methods_to_respond_to = methods_to_respond_to.map(&:to_s)
6
6
  end
7
7
 
8
- def method_missing(name, *args, &block)
8
+ def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing
9
9
  if respond_to?(name)
10
10
  nil
11
11
  else
@@ -13,12 +13,8 @@ module FactoryBot
13
13
  end
14
14
  end
15
15
 
16
- def respond_to?(method, _include_private = false)
16
+ def respond_to?(method)
17
17
  @methods_to_respond_to.include? method.to_s
18
18
  end
19
-
20
- def respond_to_missing?(*)
21
- false
22
- end
23
19
  end
24
20
  end
@@ -7,7 +7,7 @@ module FactoryBot
7
7
  attr_reader :name
8
8
 
9
9
  def initialize(name)
10
- @name = name
10
+ @name = name
11
11
  @items = ActiveSupport::HashWithIndifferentAccess.new
12
12
  end
13
13
 
@@ -21,11 +21,11 @@ module FactoryBot
21
21
 
22
22
  def find(name)
23
23
  @items.fetch(name)
24
- rescue KeyError => key_error
25
- raise key_error_with_custom_message(key_error)
24
+ rescue KeyError => e
25
+ raise key_error_with_custom_message(e)
26
26
  end
27
27
 
28
- alias :[] :find
28
+ alias [] find
29
29
 
30
30
  def register(name, item)
31
31
  @items[name] = item