factory_bot 4.10.0 → 6.0.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/CONTRIBUTING.md +52 -13
  4. data/GETTING_STARTED.md +613 -182
  5. data/LICENSE +1 -1
  6. data/NEWS.md +358 -0
  7. data/README.md +30 -26
  8. data/lib/factory_bot.rb +71 -140
  9. data/lib/factory_bot/aliases.rb +2 -2
  10. data/lib/factory_bot/attribute.rb +4 -39
  11. data/lib/factory_bot/attribute/association.rb +2 -2
  12. data/lib/factory_bot/attribute/dynamic.rb +2 -1
  13. data/lib/factory_bot/attribute_assigner.rb +24 -10
  14. data/lib/factory_bot/attribute_list.rb +3 -2
  15. data/lib/factory_bot/callback.rb +3 -10
  16. data/lib/factory_bot/configuration.rb +15 -19
  17. data/lib/factory_bot/declaration.rb +5 -5
  18. data/lib/factory_bot/declaration/association.rb +14 -1
  19. data/lib/factory_bot/declaration/dynamic.rb +3 -1
  20. data/lib/factory_bot/declaration/implicit.rb +7 -2
  21. data/lib/factory_bot/declaration_list.rb +3 -3
  22. data/lib/factory_bot/decorator.rb +5 -5
  23. data/lib/factory_bot/decorator/attribute_hash.rb +1 -1
  24. data/lib/factory_bot/decorator/invocation_tracker.rb +1 -1
  25. data/lib/factory_bot/definition.rb +49 -20
  26. data/lib/factory_bot/definition_hierarchy.rb +1 -11
  27. data/lib/factory_bot/definition_proxy.rb +125 -43
  28. data/lib/factory_bot/enum.rb +27 -0
  29. data/lib/factory_bot/errors.rb +7 -4
  30. data/lib/factory_bot/evaluation.rb +1 -1
  31. data/lib/factory_bot/evaluator.rb +9 -11
  32. data/lib/factory_bot/evaluator_class_definer.rb +1 -1
  33. data/lib/factory_bot/factory.rb +12 -12
  34. data/lib/factory_bot/factory_runner.rb +4 -4
  35. data/lib/factory_bot/find_definitions.rb +2 -2
  36. data/lib/factory_bot/internal.rb +91 -0
  37. data/lib/factory_bot/linter.rb +41 -28
  38. data/lib/factory_bot/null_factory.rb +13 -4
  39. data/lib/factory_bot/null_object.rb +2 -6
  40. data/lib/factory_bot/registry.rb +17 -8
  41. data/lib/factory_bot/reload.rb +2 -3
  42. data/lib/factory_bot/sequence.rb +5 -6
  43. data/lib/factory_bot/strategy/stub.rb +37 -37
  44. data/lib/factory_bot/strategy_calculator.rb +1 -1
  45. data/lib/factory_bot/strategy_syntax_method_registrar.rb +13 -2
  46. data/lib/factory_bot/syntax.rb +2 -2
  47. data/lib/factory_bot/syntax/default.rb +12 -24
  48. data/lib/factory_bot/syntax/methods.rb +32 -9
  49. data/lib/factory_bot/trait.rb +7 -4
  50. data/lib/factory_bot/version.rb +1 -1
  51. metadata +50 -65
  52. data/.autotest +0 -9
  53. data/.gitignore +0 -10
  54. data/.rspec +0 -3
  55. data/.simplecov +0 -4
  56. data/.travis.yml +0 -58
  57. data/Appraisals +0 -23
  58. data/Gemfile +0 -8
  59. data/Gemfile.lock +0 -103
  60. data/NEWS +0 -298
  61. data/Rakefile +0 -36
  62. data/cucumber.yml +0 -1
  63. data/factory_bot.gemspec +0 -36
  64. data/factory_girl.gemspec +0 -40
  65. data/gemfiles/3.2.gemfile +0 -10
  66. data/gemfiles/3.2.gemfile.lock +0 -107
  67. data/gemfiles/4.0.gemfile +0 -10
  68. data/gemfiles/4.0.gemfile.lock +0 -107
  69. data/gemfiles/4.1.gemfile +0 -10
  70. data/gemfiles/4.1.gemfile.lock +0 -106
  71. data/gemfiles/4.2.gemfile +0 -10
  72. data/gemfiles/4.2.gemfile.lock +0 -106
  73. data/gemfiles/5.0.gemfile +0 -10
  74. data/gemfiles/5.0.gemfile.lock +0 -104
  75. data/gemfiles/5.1.gemfile +0 -10
  76. data/gemfiles/5.1.gemfile.lock +0 -104
  77. data/lib/factory_bot/attribute/static.rb +0 -16
  78. data/lib/factory_bot/declaration/static.rb +0 -26
  79. data/lib/factory_bot/decorator/class_key_hash.rb +0 -28
  80. data/lib/factory_girl.rb +0 -5
@@ -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
@@ -2,6 +2,9 @@ module FactoryBot
2
2
  # Raised when a factory is defined that attempts to instantiate itself.
3
3
  class AssociationDefinitionError < RuntimeError; end
4
4
 
5
+ # Raised when a trait is defined that references itself.
6
+ class TraitDefinitionError < RuntimeError; end
7
+
5
8
  # Raised when a callback is defined that has an invalid name
6
9
  class InvalidCallbackNameError < RuntimeError; end
7
10
 
@@ -11,12 +14,12 @@ module FactoryBot
11
14
  # Raised when attempting to register a sequence from a dynamic attribute block
12
15
  class SequenceAbuseError < RuntimeError; end
13
16
 
14
- # Raised when defining an invalid attribute:
15
- # * Defining an attribute which has a name ending in "="
16
- # * Defining an attribute with both a static and lazy value
17
- # * Defining an attribute twice in the same factory
17
+ # Raised when defining an attribute twice in the same factory
18
18
  class AttributeDefinitionError < RuntimeError; end
19
19
 
20
+ # Raised when attempting to pass a block to an association definition
21
+ class AssociationDefinitionError < RuntimeError; end
22
+
20
23
  # Raised when a method is defined in a factory or trait with arguments
21
24
  class MethodDefinitionError < RuntimeError; end
22
25
 
@@ -1,4 +1,4 @@
1
- require 'observer'
1
+ require "observer"
2
2
 
3
3
  module FactoryBot
4
4
  class Evaluation
@@ -1,5 +1,5 @@
1
- require 'active_support/core_ext/hash/except'
2
- require 'active_support/core_ext/class/attribute'
1
+ require "active_support/core_ext/hash/except"
2
+ require "active_support/core_ext/class/attribute"
3
3
 
4
4
  module FactoryBot
5
5
  # @api private
@@ -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)
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
@@ -45,7 +43,7 @@ module FactoryBot
45
43
  end
46
44
  end
47
45
 
48
- def respond_to_missing?(method_name, include_private = false)
46
+ def respond_to_missing?(method_name, _include_private = false)
49
47
  @instance.respond_to?(method_name) || SyntaxRunner.new.respond_to?(method_name)
50
48
  end
51
49
 
@@ -66,7 +64,7 @@ module FactoryBot
66
64
  end
67
65
 
68
66
  def self.define_attribute(name, &block)
69
- if method_defined?(name) || private_method_defined?(name)
67
+ if instance_methods(false).include?(name) || private_instance_methods(false).include?(name)
70
68
  undef_method(name)
71
69
  end
72
70
 
@@ -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)
@@ -1,5 +1,5 @@
1
- require 'active_support/core_ext/hash/keys'
2
- require 'active_support/inflector'
1
+ require "active_support/core_ext/hash/keys"
2
+ require "active_support/inflector"
3
3
 
4
4
  module FactoryBot
5
5
  # @api private
@@ -8,16 +8,16 @@ 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
@@ -84,14 +84,14 @@ 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
91
91
  end
92
92
 
93
93
  def with_traits(traits)
94
- self.clone.tap do |factory_with_traits|
94
+ clone.tap do |factory_with_traits|
95
95
  factory_with_traits.append_traits traits
96
96
  end
97
97
  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
 
@@ -25,7 +25,7 @@ module FactoryBot
25
25
  factory: factory
26
26
  }
27
27
 
28
- ActiveSupport::Notifications.instrument('factory_bot.run_factory', instrumentation_payload) do
28
+ ActiveSupport::Notifications.instrument("factory_bot.run_factory", instrumentation_payload) do
29
29
  factory.run(runner_strategy, @overrides, &block)
30
30
  end
31
31
  end
@@ -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) }
@@ -16,7 +16,7 @@ module FactoryBot
16
16
  load("#{path}.rb") if File.exist?("#{path}.rb")
17
17
 
18
18
  if File.directory? path
19
- Dir[File.join(path, '**', '*.rb')].sort.each do |file|
19
+ Dir[File.join(path, "**", "*.rb")].sort.each do |file|
20
20
  load file
21
21
  end
22
22
  end
@@ -0,0 +1,91 @@
1
+ module FactoryBot
2
+ # @api private
3
+ module Internal
4
+ class << self
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
18
+
19
+ def configuration
20
+ @configuration ||= Configuration.new
21
+ end
22
+
23
+ def reset_configuration
24
+ @configuration = nil
25
+ end
26
+
27
+ def register_inline_sequence(sequence)
28
+ inline_sequences.push(sequence)
29
+ end
30
+
31
+ def rewind_inline_sequences
32
+ inline_sequences.each(&:rewind)
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
89
+ end
90
+ end
91
+ end
@@ -1,10 +1,10 @@
1
1
  module FactoryBot
2
2
  class Linter
3
-
4
- def initialize(factories, linting_strategy, factory_strategy = :create)
3
+ def initialize(factories, strategy: :create, traits: false, verbose: false)
5
4
  @factories_to_lint = factories
6
- @linting_method = "lint_#{linting_strategy}"
7
- @factory_strategy = factory_strategy
5
+ @factory_strategy = strategy
6
+ @traits = traits
7
+ @verbose = verbose
8
8
  @invalid_factories = calculate_invalid_factories
9
9
  end
10
10
 
@@ -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|
23
- errors = send(@linting_method, factory)
22
+ factories_to_lint.each_with_object(Hash.new([])) do |factory, result|
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
@@ -37,6 +36,13 @@ module FactoryBot
37
36
  "* #{location} - #{message} (#{@wrapped_error.class.name})"
38
37
  end
39
38
 
39
+ def verbose_message
40
+ <<~MESSAGE
41
+ #{message}
42
+ #{@wrapped_error.backtrace.join("\n ")}
43
+ MESSAGE
44
+ end
45
+
40
46
  def location
41
47
  @factory.name
42
48
  end
@@ -53,12 +59,20 @@ module FactoryBot
53
59
  end
54
60
  end
55
61
 
62
+ def lint(factory)
63
+ if @traits
64
+ lint_factory(factory) + lint_traits(factory)
65
+ else
66
+ lint_factory(factory)
67
+ end
68
+ end
69
+
56
70
  def lint_factory(factory)
57
71
  result = []
58
72
  begin
59
73
  FactoryBot.public_send(factory_strategy, factory.name)
60
- rescue => error
61
- result |= [FactoryError.new(error, factory)]
74
+ rescue => e
75
+ result |= [FactoryError.new(e, factory)]
62
76
  end
63
77
  result
64
78
  end
@@ -66,32 +80,31 @@ module FactoryBot
66
80
  def lint_traits(factory)
67
81
  result = []
68
82
  factory.definition.defined_traits.map(&:name).each do |trait_name|
69
- begin
70
- FactoryBot.public_send(factory_strategy, factory.name, trait_name)
71
- rescue => error
72
- result |=
73
- [FactoryTraitError.new(error, factory, trait_name)]
74
- end
83
+ FactoryBot.public_send(factory_strategy, factory.name, trait_name)
84
+ rescue => e
85
+ result |= [FactoryTraitError.new(e, factory, trait_name)]
75
86
  end
76
87
  result
77
88
  end
78
89
 
79
- def lint_factory_and_traits(factory)
80
- errors = lint_factory(factory)
81
- errors |= lint_traits(factory)
82
- errors
83
- end
84
-
85
90
  def error_message
86
- lines = invalid_factories.map do |_factory, exceptions|
87
- exceptions.map(&:message)
88
- end.flatten
91
+ lines = invalid_factories.map { |_factory, exceptions|
92
+ exceptions.map(&error_message_type)
93
+ }.flatten
89
94
 
90
- <<-ERROR_MESSAGE.strip
91
- The following factories are invalid:
95
+ <<~ERROR_MESSAGE.strip
96
+ The following factories are invalid:
92
97
 
93
- #{lines.join("\n")}
98
+ #{lines.join("\n")}
94
99
  ERROR_MESSAGE
95
100
  end
101
+
102
+ def error_message_type
103
+ if @verbose
104
+ :verbose_message
105
+ else
106
+ :message
107
+ end
108
+ end
96
109
  end
97
110
  end
@@ -10,9 +10,18 @@ module FactoryBot
10
10
  delegate :defined_traits, :callbacks, :attributes, :constructor,
11
11
  :to_create, to: :definition
12
12
 
13
- def compile; end
14
- def class_name; end
15
- def evaluator_class; FactoryBot::Evaluator; end
16
- def hierarchy_class; FactoryBot::DefinitionHierarchy; end
13
+ def compile
14
+ end
15
+
16
+ def class_name
17
+ end
18
+
19
+ def evaluator_class
20
+ FactoryBot::Evaluator
21
+ end
22
+
23
+ def hierarchy_class
24
+ FactoryBot::DefinitionHierarchy
25
+ end
17
26
  end
18
27
  end