factory_bot 4.11.1 → 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/GETTING_STARTED.md +105 -29
  3. data/NEWS +17 -0
  4. data/README.md +3 -14
  5. data/lib/factory_bot.rb +56 -56
  6. data/lib/factory_bot/aliases.rb +1 -1
  7. data/lib/factory_bot/attribute.rb +4 -39
  8. data/lib/factory_bot/attribute_assigner.rb +20 -5
  9. data/lib/factory_bot/attribute_list.rb +2 -1
  10. data/lib/factory_bot/callback.rb +1 -0
  11. data/lib/factory_bot/configuration.rb +6 -18
  12. data/lib/factory_bot/declaration.rb +4 -4
  13. data/lib/factory_bot/declaration/association.rb +3 -1
  14. data/lib/factory_bot/declaration/dynamic.rb +3 -1
  15. data/lib/factory_bot/declaration/implicit.rb +3 -1
  16. data/lib/factory_bot/declaration_list.rb +1 -1
  17. data/lib/factory_bot/decorator.rb +5 -1
  18. data/lib/factory_bot/decorator/attribute_hash.rb +1 -1
  19. data/lib/factory_bot/decorator/invocation_tracker.rb +1 -1
  20. data/lib/factory_bot/definition.rb +4 -3
  21. data/lib/factory_bot/definition_proxy.rb +53 -62
  22. data/lib/factory_bot/errors.rb +4 -4
  23. data/lib/factory_bot/evaluation.rb +1 -1
  24. data/lib/factory_bot/evaluator.rb +4 -4
  25. data/lib/factory_bot/factory.rb +7 -7
  26. data/lib/factory_bot/factory_runner.rb +3 -3
  27. data/lib/factory_bot/find_definitions.rb +1 -1
  28. data/lib/factory_bot/linter.rb +35 -18
  29. data/lib/factory_bot/null_factory.rb +3 -0
  30. data/lib/factory_bot/null_object.rb +2 -2
  31. data/lib/factory_bot/registry.rb +15 -6
  32. data/lib/factory_bot/sequence.rb +0 -1
  33. data/lib/factory_bot/strategy/null.rb +2 -4
  34. data/lib/factory_bot/strategy/stub.rb +23 -29
  35. data/lib/factory_bot/strategy_syntax_method_registrar.rb +2 -2
  36. data/lib/factory_bot/syntax.rb +2 -2
  37. data/lib/factory_bot/syntax/default.rb +1 -1
  38. data/lib/factory_bot/trait.rb +2 -1
  39. data/lib/factory_bot/version.rb +1 -1
  40. metadata +68 -29
  41. data/lib/factory_bot/attribute/static.rb +0 -16
  42. data/lib/factory_bot/declaration/static.rb +0 -26
  43. data/lib/factory_bot/decorator/class_key_hash.rb +0 -28
@@ -11,12 +11,12 @@ module FactoryBot
11
11
  # Raised when attempting to register a sequence from a dynamic attribute block
12
12
  class SequenceAbuseError < RuntimeError; end
13
13
 
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
14
+ # Raised when defining an attribute twice in the same factory
18
15
  class AttributeDefinitionError < RuntimeError; end
19
16
 
17
+ # Raised when attempting to pass a block to an association definition
18
+ class AssociationDefinitionError < RuntimeError; end
19
+
20
20
  # Raised when a method is defined in a factory or trait with arguments
21
21
  class MethodDefinitionError < RuntimeError; end
22
22
 
@@ -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
@@ -37,7 +37,7 @@ module FactoryBot
37
37
  @instance = object_instance
38
38
  end
39
39
 
40
- def method_missing(method_name, *args, &block)
40
+ def method_missing(method_name, *args, &block) # rubocop:disable Style/MethodMissing
41
41
  if @instance.respond_to?(method_name)
42
42
  @instance.send(method_name, *args, &block)
43
43
  else
@@ -45,7 +45,7 @@ module FactoryBot
45
45
  end
46
46
  end
47
47
 
48
- def respond_to_missing?(method_name, include_private = false)
48
+ def respond_to_missing?(method_name, _include_private = false)
49
49
  @instance.respond_to?(method_name) || SyntaxRunner.new.respond_to?(method_name)
50
50
  end
51
51
 
@@ -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
@@ -21,10 +21,10 @@ module FactoryBot
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)
@@ -91,7 +91,7 @@ module FactoryBot
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
@@ -5,7 +5,7 @@ module FactoryBot
5
5
  @strategy = strategy
6
6
 
7
7
  @overrides = traits_and_overrides.extract_options!
8
- @traits = traits_and_overrides
8
+ @traits = traits_and_overrides.map(&:to_sym)
9
9
  end
10
10
 
11
11
  def run(runner_strategy = @strategy, &block)
@@ -22,10 +22,10 @@ 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
- 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
@@ -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
@@ -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
 
@@ -20,7 +20,7 @@ module FactoryBot
20
20
 
21
21
  def calculate_invalid_factories
22
22
  factories_to_lint.reduce(Hash.new([])) do |result, factory|
23
- errors = send(@linting_method, factory)
23
+ errors = lint(factory)
24
24
  result[factory] |= errors unless errors.empty?
25
25
  result
26
26
  end
@@ -37,6 +37,13 @@ module FactoryBot
37
37
  "* #{location} - #{message} (#{@wrapped_error.class.name})"
38
38
  end
39
39
 
40
+ def verbose_message
41
+ <<~MESSAGE
42
+ #{message}
43
+ #{@wrapped_error.backtrace.join("\n ")}
44
+ MESSAGE
45
+ end
46
+
40
47
  def location
41
48
  @factory.name
42
49
  end
@@ -53,11 +60,19 @@ module FactoryBot
53
60
  end
54
61
  end
55
62
 
63
+ def lint(factory)
64
+ if @traits
65
+ lint_factory(factory) + lint_traits(factory)
66
+ else
67
+ lint_factory(factory)
68
+ end
69
+ end
70
+
56
71
  def lint_factory(factory)
57
72
  result = []
58
73
  begin
59
74
  FactoryBot.public_send(factory_strategy, factory.name)
60
- rescue => error
75
+ rescue StandardError => error
61
76
  result |= [FactoryError.new(error, factory)]
62
77
  end
63
78
  result
@@ -68,30 +83,32 @@ module FactoryBot
68
83
  factory.definition.defined_traits.map(&:name).each do |trait_name|
69
84
  begin
70
85
  FactoryBot.public_send(factory_strategy, factory.name, trait_name)
71
- rescue => error
86
+ rescue StandardError => error
72
87
  result |=
73
- [FactoryTraitError.new(error, factory, trait_name)]
88
+ [FactoryTraitError.new(error, factory, trait_name)]
74
89
  end
75
90
  end
76
91
  result
77
92
  end
78
93
 
79
- def lint_factory_and_traits(factory)
80
- errors = lint_factory(factory)
81
- errors |= lint_traits(factory)
82
- errors
83
- end
84
-
85
94
  def error_message
86
95
  lines = invalid_factories.map do |_factory, exceptions|
87
- exceptions.map(&:message)
96
+ exceptions.map(&error_message_type)
88
97
  end.flatten
89
98
 
90
- <<-ERROR_MESSAGE.strip
91
- The following factories are invalid:
99
+ <<~ERROR_MESSAGE.strip
100
+ The following factories are invalid:
92
101
 
93
- #{lines.join("\n")}
102
+ #{lines.join("\n")}
94
103
  ERROR_MESSAGE
95
104
  end
105
+
106
+ def error_message_type
107
+ if @verbose
108
+ :verbose_message
109
+ else
110
+ :message
111
+ end
112
+ end
96
113
  end
97
114
  end
@@ -11,8 +11,11 @@ module FactoryBot
11
11
  :to_create, to: :definition
12
12
 
13
13
  def compile; end
14
+
14
15
  def class_name; end
16
+
15
17
  def evaluator_class; FactoryBot::Evaluator; end
18
+
16
19
  def hierarchy_class; FactoryBot::DefinitionHierarchy; end
17
20
  end
18
21
  end
@@ -13,11 +13,11 @@ module FactoryBot
13
13
  end
14
14
  end
15
15
 
16
- def respond_to?(method, include_private=false)
16
+ def respond_to?(method, _include_private = false)
17
17
  @methods_to_respond_to.include? method.to_s
18
18
  end
19
19
 
20
- def respond_to_missing?(*args)
20
+ def respond_to_missing?(*)
21
21
  false
22
22
  end
23
23
  end
@@ -1,3 +1,5 @@
1
+ require "active_support/core_ext/hash/indifferent_access"
2
+
1
3
  module FactoryBot
2
4
  class Registry
3
5
  include Enumerable
@@ -6,7 +8,7 @@ module FactoryBot
6
8
 
7
9
  def initialize(name)
8
10
  @name = name
9
- @items = Decorator::ClassKeyHash.new({})
11
+ @items = ActiveSupport::HashWithIndifferentAccess.new
10
12
  end
11
13
 
12
14
  def clear
@@ -18,11 +20,9 @@ module FactoryBot
18
20
  end
19
21
 
20
22
  def find(name)
21
- if registered?(name)
22
- @items[name]
23
- else
24
- raise ArgumentError, "#{@name} not registered: #{name}"
25
- end
23
+ @items.fetch(name)
24
+ rescue KeyError => key_error
25
+ raise key_error_with_custom_message(key_error)
26
26
  end
27
27
 
28
28
  alias :[] :find
@@ -34,5 +34,14 @@ module FactoryBot
34
34
  def registered?(name)
35
35
  @items.key?(name)
36
36
  end
37
+
38
+ private
39
+
40
+ def key_error_with_custom_message(key_error)
41
+ message = key_error.message.sub("key not found", "#{@name} not registered")
42
+ error = KeyError.new(message)
43
+ error.set_backtrace(key_error.backtrace)
44
+ error
45
+ end
37
46
  end
38
47
  end
@@ -1,5 +1,4 @@
1
1
  module FactoryBot
2
-
3
2
  # Sequences are defined using sequence within a FactoryBot.define block.
4
3
  # Sequence values are generated using next.
5
4
  # @api private
@@ -1,11 +1,9 @@
1
1
  module FactoryBot
2
2
  module Strategy
3
3
  class Null
4
- def association(runner)
5
- end
4
+ def association(runner); end
6
5
 
7
- def result(evaluation)
8
- end
6
+ def result(evaluation); end
9
7
  end
10
8
  end
11
9
  end
@@ -31,7 +31,8 @@ module FactoryBot
31
31
  def result(evaluation)
32
32
  evaluation.object.tap do |instance|
33
33
  stub_database_interaction_on_result(instance)
34
- clear_changed_attributes_on_result(instance)
34
+ set_timestamps(instance)
35
+ clear_changes_information(instance)
35
36
  evaluation.notify(:after_stub, instance)
36
37
  end
37
38
  end
@@ -60,46 +61,39 @@ module FactoryBot
60
61
 
61
62
  DISABLED_PERSISTENCE_METHODS.each do |write_method|
62
63
  define_singleton_method(write_method) do |*args|
63
- raise "stubbed models are not allowed to access the database - #{self.class}##{write_method}(#{args.join(",")})"
64
+ raise "stubbed models are not allowed to access the database - "\
65
+ "#{self.class}##{write_method}(#{args.join(',')})"
64
66
  end
65
67
  end
66
68
  end
69
+ end
67
70
 
68
- created_at_missing_default = result_instance.respond_to?(:created_at) && !result_instance.created_at
69
-
70
- if created_at_missing_default
71
- result_instance.instance_eval do
72
- def created_at
73
- @created_at ||= Time.now.in_time_zone
74
- end
75
- end
71
+ def clear_changes_information(result_instance)
72
+ if result_instance.respond_to?(:clear_changes_information)
73
+ result_instance.clear_changes_information
76
74
  end
75
+ end
77
76
 
78
- has_updated_at = result_instance.respond_to?(:updated_at)
79
- updated_at_no_default = has_updated_at && !result_instance.updated_at
80
-
81
- if updated_at_no_default
82
- result_instance.instance_eval do
83
- def updated_at
84
- @updated_at ||= Time.current
85
- end
86
- end
77
+ def set_timestamps(result_instance)
78
+ if missing_created_at?(result_instance)
79
+ result_instance.created_at = Time.current
87
80
  end
88
- end
89
81
 
90
- def clear_changed_attributes_on_result(result_instance)
91
- unless result_instance.respond_to?(:clear_changes_information)
92
- result_instance.extend ActiveModelDirtyBackport
82
+ if missing_updated_at?(result_instance)
83
+ result_instance.updated_at = Time.current
93
84
  end
85
+ end
94
86
 
95
- result_instance.clear_changes_information
87
+ def missing_created_at?(result_instance)
88
+ result_instance.respond_to?(:created_at) &&
89
+ result_instance.respond_to?(:created_at=) &&
90
+ result_instance.created_at.blank?
96
91
  end
97
- end
98
92
 
99
- module ActiveModelDirtyBackport
100
- def clear_changes_information
101
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
102
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
93
+ def missing_updated_at?(result_instance)
94
+ result_instance.respond_to?(:updated_at) &&
95
+ result_instance.respond_to?(:updated_at=) &&
96
+ result_instance.updated_at.blank?
103
97
  end
104
98
  end
105
99
  end
@@ -29,7 +29,7 @@ module FactoryBot
29
29
  raise ArgumentError, "count missing for #{strategy_name}_list"
30
30
  end
31
31
 
32
- amount.times.map { send(strategy_name, name, *traits_and_overrides, &block) }
32
+ Array.new(amount) { send(strategy_name, name, *traits_and_overrides, &block) }
33
33
  end
34
34
  end
35
35
 
@@ -37,7 +37,7 @@ module FactoryBot
37
37
  strategy_name = @strategy_name
38
38
 
39
39
  define_syntax_method("#{strategy_name}_pair") do |name, *traits_and_overrides, &block|
40
- 2.times.map { send(strategy_name, name, *traits_and_overrides, &block) }
40
+ Array.new(2) { send(strategy_name, name, *traits_and_overrides, &block) }
41
41
  end
42
42
  end
43
43
 
@@ -1,5 +1,5 @@
1
- require 'factory_bot/syntax/methods'
2
- require 'factory_bot/syntax/default'
1
+ require "factory_bot/syntax/methods"
2
+ require "factory_bot/syntax/default"
3
3
 
4
4
  module FactoryBot
5
5
  module Syntax
@@ -59,7 +59,7 @@ module FactoryBot
59
59
  end
60
60
 
61
61
  class ModifyDSL
62
- def factory(name, options = {}, &block)
62
+ def factory(name, _options = {}, &block)
63
63
  factory = FactoryBot.factory_by_name(name)
64
64
  proxy = FactoryBot::DefinitionProxy.new(factory.definition.overridable)
65
65
  proxy.instance_eval(&block)
@@ -4,7 +4,7 @@ module FactoryBot
4
4
  attr_reader :name, :definition
5
5
 
6
6
  def initialize(name, &block)
7
- @name = name
7
+ @name = name.to_sym
8
8
  @block = block
9
9
  @definition = Definition.new(@name)
10
10
 
@@ -25,6 +25,7 @@ module FactoryBot
25
25
  end
26
26
 
27
27
  protected
28
+
28
29
  attr_reader :block
29
30
  end
30
31
  end