factory_bot 4.10.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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 +630 -182
  5. data/LICENSE +1 -1
  6. data/NEWS.md +372 -0
  7. data/README.md +29 -31
  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 +33 -3
  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 +20 -4
  23. data/lib/factory_bot/decorator/attribute_hash.rb +1 -1
  24. data/lib/factory_bot/decorator/invocation_tracker.rb +10 -3
  25. data/lib/factory_bot/definition.rb +54 -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 +23 -15
  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,19 +33,27 @@ 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_accessor :instance
39
37
 
40
- def method_missing(method_name, *args, &block)
41
- if @instance.respond_to?(method_name)
42
- @instance.send(method_name, *args, &block)
43
- else
44
- SyntaxRunner.new.send(method_name, *args, &block)
38
+ if ::Gem::Version.new(::RUBY_VERSION) >= ::Gem::Version.new("2.7")
39
+ def method_missing(method_name, *args, **kwargs, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
40
+ if @instance.respond_to?(method_name)
41
+ @instance.send(method_name, *args, **kwargs, &block)
42
+ else
43
+ SyntaxRunner.new.send(method_name, *args, **kwargs, &block)
44
+ end
45
+ end
46
+ else
47
+ def method_missing(method_name, *args, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
48
+ if @instance.respond_to?(method_name)
49
+ @instance.send(method_name, *args, &block)
50
+ else
51
+ SyntaxRunner.new.send(method_name, *args, &block)
52
+ end
45
53
  end
46
54
  end
47
55
 
48
- def respond_to_missing?(method_name, include_private = false)
56
+ def respond_to_missing?(method_name, _include_private = false)
49
57
  @instance.respond_to?(method_name) || SyntaxRunner.new.respond_to?(method_name)
50
58
  end
51
59
 
@@ -66,7 +74,7 @@ module FactoryBot
66
74
  end
67
75
 
68
76
  def self.define_attribute(name, &block)
69
- if method_defined?(name) || private_method_defined?(name)
77
+ if instance_methods(false).include?(name) || private_instance_methods(false).include?(name)
70
78
  undef_method(name)
71
79
  end
72
80
 
@@ -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