factory_bot 5.0.2 → 6.1.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +52 -13
  3. data/GETTING_STARTED.md +453 -78
  4. data/NEWS.md +40 -0
  5. data/README.md +17 -16
  6. data/lib/factory_bot.rb +21 -93
  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 +8 -9
  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/association.rb +30 -2
  16. data/lib/factory_bot/declaration/implicit.rb +4 -1
  17. data/lib/factory_bot/declaration_list.rb +2 -2
  18. data/lib/factory_bot/decorator.rb +18 -6
  19. data/lib/factory_bot/decorator/invocation_tracker.rb +10 -3
  20. data/lib/factory_bot/definition.rb +51 -18
  21. data/lib/factory_bot/definition_hierarchy.rb +1 -11
  22. data/lib/factory_bot/definition_proxy.rb +77 -12
  23. data/lib/factory_bot/enum.rb +27 -0
  24. data/lib/factory_bot/errors.rb +3 -0
  25. data/lib/factory_bot/evaluator.rb +20 -12
  26. data/lib/factory_bot/evaluator_class_definer.rb +1 -1
  27. data/lib/factory_bot/factory.rb +13 -13
  28. data/lib/factory_bot/factory_runner.rb +4 -4
  29. data/lib/factory_bot/find_definitions.rb +1 -1
  30. data/lib/factory_bot/internal.rb +68 -1
  31. data/lib/factory_bot/linter.rb +9 -13
  32. data/lib/factory_bot/null_factory.rb +10 -4
  33. data/lib/factory_bot/null_object.rb +2 -6
  34. data/lib/factory_bot/registry.rb +4 -4
  35. data/lib/factory_bot/reload.rb +1 -2
  36. data/lib/factory_bot/sequence.rb +5 -5
  37. data/lib/factory_bot/strategy/null.rb +4 -2
  38. data/lib/factory_bot/strategy/stub.rb +16 -5
  39. data/lib/factory_bot/strategy_calculator.rb +1 -1
  40. data/lib/factory_bot/strategy_syntax_method_registrar.rb +12 -1
  41. data/lib/factory_bot/syntax/default.rb +11 -23
  42. data/lib/factory_bot/syntax/methods.rb +3 -3
  43. data/lib/factory_bot/trait.rb +5 -3
  44. data/lib/factory_bot/version.rb +1 -1
  45. metadata +9 -36
@@ -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
@@ -145,7 +145,7 @@ module FactoryBot
145
145
 
146
146
  def parent
147
147
  if @parent
148
- FactoryBot.factory_by_name(@parent)
148
+ FactoryBot::Internal.factory_by_name(@parent)
149
149
  else
150
150
  NullFactory.new
151
151
  end
@@ -1,15 +1,15 @@
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)
12
- factory = FactoryBot.factory_by_name(@name)
12
+ factory = FactoryBot::Internal.factory_by_name(@name)
13
13
 
14
14
  factory.compile
15
15
 
@@ -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) }
@@ -2,7 +2,19 @@ module FactoryBot
2
2
  # @api private
3
3
  module Internal
4
4
  class << self
5
- delegate :inline_sequences, 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
6
18
 
7
19
  def configuration
8
20
  @configuration ||= Configuration.new
@@ -19,6 +31,61 @@ module FactoryBot
19
31
  def rewind_inline_sequences
20
32
  inline_sequences.each(&:rewind)
21
33
  end
34
+
35
+ def register_trait(trait)
36
+ trait.names.each do |name|
37
+ traits.register(name, trait)
38
+ end
39
+ trait
40
+ end
41
+
42
+ def trait_by_name(name)
43
+ traits.find(name)
44
+ end
45
+
46
+ def register_sequence(sequence)
47
+ sequence.names.each do |name|
48
+ sequences.register(name, sequence)
49
+ end
50
+ sequence
51
+ end
52
+
53
+ def sequence_by_name(name)
54
+ sequences.find(name)
55
+ end
56
+
57
+ def rewind_sequences
58
+ sequences.each(&:rewind)
59
+ rewind_inline_sequences
60
+ end
61
+
62
+ def register_factory(factory)
63
+ factory.names.each do |name|
64
+ factories.register(name, factory)
65
+ end
66
+ factory
67
+ end
68
+
69
+ def factory_by_name(name)
70
+ factories.find(name)
71
+ end
72
+
73
+ def register_strategy(strategy_name, strategy_class)
74
+ strategies.register(strategy_name, strategy_class)
75
+ StrategySyntaxMethodRegistrar.new(strategy_name).define_strategy_methods
76
+ end
77
+
78
+ def strategy_by_name(name)
79
+ strategies.find(name)
80
+ end
81
+
82
+ def register_default_strategies
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)
88
+ end
22
89
  end
23
90
  end
24
91
  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
@@ -1,8 +1,7 @@
1
1
  module FactoryBot
2
2
  def self.reload
3
3
  Internal.reset_configuration
4
- register_default_strategies
5
- register_default_callbacks
4
+ Internal.register_default_strategies
6
5
  find_definitions
7
6
  end
8
7
  end
@@ -6,14 +6,14 @@ module FactoryBot
6
6
  attr_reader :name
7
7
 
8
8
  def initialize(name, *args, &proc)
9
- @name = name
10
- @proc = proc
9
+ @name = name
10
+ @proc = proc
11
11
 
12
- options = args.extract_options!
13
- @value = args.first || 1
12
+ options = args.extract_options!
13
+ @value = args.first || 1
14
14
  @aliases = options.fetch(:aliases) { [] }
15
15
 
16
- if !@value.respond_to?(:peek)
16
+ unless @value.respond_to?(:peek)
17
17
  @value = EnumeratorAdapter.new(@value)
18
18
  end
19
19
  end
@@ -1,9 +1,11 @@
1
1
  module FactoryBot
2
2
  module Strategy
3
3
  class Null
4
- def association(runner); end
4
+ def association(runner)
5
+ end
5
6
 
6
- def result(evaluation); end
7
+ def result(evaluation)
8
+ end
7
9
  end
8
10
  end
9
11
  end
@@ -21,9 +21,13 @@ module FactoryBot
21
21
  :update_attributes!,
22
22
  :update_attributes,
23
23
  :update_column,
24
- :update_columns,
24
+ :update_columns
25
25
  ].freeze
26
26
 
27
+ def self.next_id=(id)
28
+ @@next_id = id
29
+ end
30
+
27
31
  def association(runner)
28
32
  runner.run(:build_stubbed)
29
33
  end
@@ -44,15 +48,17 @@ module FactoryBot
44
48
  end
45
49
 
46
50
  def stub_database_interaction_on_result(result_instance)
47
- result_instance.id ||= next_id
51
+ if has_settable_id?(result_instance)
52
+ result_instance.id ||= next_id
53
+ end
48
54
 
49
55
  result_instance.instance_eval do
50
56
  def persisted?
51
- !new_record?
57
+ true
52
58
  end
53
59
 
54
60
  def new_record?
55
- id.nil?
61
+ false
56
62
  end
57
63
 
58
64
  def destroyed?
@@ -62,12 +68,17 @@ module FactoryBot
62
68
  DISABLED_PERSISTENCE_METHODS.each do |write_method|
63
69
  define_singleton_method(write_method) do |*args|
64
70
  raise "stubbed models are not allowed to access the database - "\
65
- "#{self.class}##{write_method}(#{args.join(',')})"
71
+ "#{self.class}##{write_method}(#{args.join(",")})"
66
72
  end
67
73
  end
68
74
  end
69
75
  end
70
76
 
77
+ def has_settable_id?(result_instance)
78
+ !result_instance.class.respond_to?(:primary_key) ||
79
+ result_instance.class.primary_key
80
+ end
81
+
71
82
  def clear_changes_information(result_instance)
72
83
  if result_instance.respond_to?(:clear_changes_information)
73
84
  result_instance.clear_changes_information
@@ -20,7 +20,7 @@ module FactoryBot
20
20
  end
21
21
 
22
22
  def strategy_name_to_object
23
- FactoryBot.strategy_by_name(@name_or_object)
23
+ FactoryBot::Internal.strategy_by_name(@name_or_object)
24
24
  end
25
25
  end
26
26
  end
@@ -11,6 +11,14 @@ module FactoryBot
11
11
  define_pair_strategy_method
12
12
  end
13
13
 
14
+ def self.with_index(block, index)
15
+ if block&.arity == 2
16
+ ->(instance) { block.call(instance, index) }
17
+ else
18
+ block
19
+ end
20
+ end
21
+
14
22
  private
15
23
 
16
24
  def define_singular_strategy_method
@@ -29,7 +37,10 @@ module FactoryBot
29
37
  raise ArgumentError, "count missing for #{strategy_name}_list"
30
38
  end
31
39
 
32
- Array.new(amount) { send(strategy_name, name, *traits_and_overrides, &block) }
40
+ Array.new(amount) do |i|
41
+ block_with_index = StrategySyntaxMethodRegistrar.with_index(block, i)
42
+ send(strategy_name, name, *traits_and_overrides, &block_with_index)
43
+ end
33
44
  end
34
45
  end
35
46