factory_bot 4.10.0 → 6.0.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 +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
@@ -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?(*args)
21
- false
22
- end
23
19
  end
24
20
  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
@@ -5,8 +7,8 @@ module FactoryBot
5
7
  attr_reader :name
6
8
 
7
9
  def initialize(name)
8
- @name = name
9
- @items = Decorator::ClassKeyHash.new({})
10
+ @name = name
11
+ @items = ActiveSupport::HashWithIndifferentAccess.new
10
12
  end
11
13
 
12
14
  def clear
@@ -18,14 +20,12 @@ 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 => 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
@@ -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,8 +1,7 @@
1
1
  module FactoryBot
2
2
  def self.reload
3
- reset_configuration
4
- register_default_strategies
5
- register_default_callbacks
3
+ Internal.reset_configuration
4
+ Internal.register_default_strategies
6
5
  find_definitions
7
6
  end
8
7
  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
@@ -7,14 +6,14 @@ module FactoryBot
7
6
  attr_reader :name
8
7
 
9
8
  def initialize(name, *args, &proc)
10
- @name = name
11
- @proc = proc
9
+ @name = name
10
+ @proc = proc
12
11
 
13
- options = args.extract_options!
14
- @value = args.first || 1
12
+ options = args.extract_options!
13
+ @value = args.first || 1
15
14
  @aliases = options.fetch(:aliases) { [] }
16
15
 
17
- if !@value.respond_to?(:peek)
16
+ unless @value.respond_to?(:peek)
18
17
  @value = EnumeratorAdapter.new(@value)
19
18
  end
20
19
  end
@@ -6,17 +6,14 @@ module FactoryBot
6
6
  DISABLED_PERSISTENCE_METHODS = [
7
7
  :connection,
8
8
  :decrement!,
9
- :decrement,
10
9
  :delete,
11
10
  :destroy!,
12
11
  :destroy,
13
12
  :increment!,
14
- :increment,
15
13
  :reload,
16
14
  :save!,
17
15
  :save,
18
16
  :toggle!,
19
- :toggle,
20
17
  :touch,
21
18
  :update!,
22
19
  :update,
@@ -24,9 +21,13 @@ module FactoryBot
24
21
  :update_attributes!,
25
22
  :update_attributes,
26
23
  :update_column,
27
- :update_columns,
24
+ :update_columns
28
25
  ].freeze
29
26
 
27
+ def self.next_id=(id)
28
+ @@next_id = id
29
+ end
30
+
30
31
  def association(runner)
31
32
  runner.run(:build_stubbed)
32
33
  end
@@ -34,7 +35,8 @@ module FactoryBot
34
35
  def result(evaluation)
35
36
  evaluation.object.tap do |instance|
36
37
  stub_database_interaction_on_result(instance)
37
- clear_changed_attributes_on_result(instance)
38
+ set_timestamps(instance)
39
+ clear_changes_information(instance)
38
40
  evaluation.notify(:after_stub, instance)
39
41
  end
40
42
  end
@@ -46,15 +48,17 @@ module FactoryBot
46
48
  end
47
49
 
48
50
  def stub_database_interaction_on_result(result_instance)
49
- result_instance.id ||= next_id
51
+ if has_settable_id?(result_instance)
52
+ result_instance.id ||= next_id
53
+ end
50
54
 
51
55
  result_instance.instance_eval do
52
56
  def persisted?
53
- !new_record?
57
+ true
54
58
  end
55
59
 
56
60
  def new_record?
57
- id.nil?
61
+ false
58
62
  end
59
63
 
60
64
  def destroyed?
@@ -63,48 +67,44 @@ module FactoryBot
63
67
 
64
68
  DISABLED_PERSISTENCE_METHODS.each do |write_method|
65
69
  define_singleton_method(write_method) do |*args|
66
- raise "stubbed models are not allowed to access the database - #{self.class}##{write_method}(#{args.join(",")})"
70
+ raise "stubbed models are not allowed to access the database - "\
71
+ "#{self.class}##{write_method}(#{args.join(",")})"
67
72
  end
68
73
  end
69
74
  end
75
+ end
70
76
 
71
- created_at_missing_default = result_instance.respond_to?(:created_at) && !result_instance.created_at
72
- result_instance_missing_created_at = !result_instance.respond_to?(:created_at)
77
+ def has_settable_id?(result_instance)
78
+ !result_instance.class.respond_to?(:primary_key) ||
79
+ result_instance.class.primary_key
80
+ end
73
81
 
74
- if created_at_missing_default || result_instance_missing_created_at
75
- result_instance.instance_eval do
76
- def created_at
77
- @created_at ||= Time.now.in_time_zone
78
- end
79
- end
82
+ def clear_changes_information(result_instance)
83
+ if result_instance.respond_to?(:clear_changes_information)
84
+ result_instance.clear_changes_information
80
85
  end
86
+ end
81
87
 
82
- has_updated_at = result_instance.respond_to?(:updated_at)
83
- updated_at_no_default = has_updated_at && !result_instance.updated_at
84
- result_instance_missing_updated_at = !has_updated_at
85
-
86
- if updated_at_no_default || result_instance_missing_updated_at
87
- result_instance.instance_eval do
88
- def updated_at
89
- @updated_at ||= Time.current
90
- end
91
- end
88
+ def set_timestamps(result_instance)
89
+ if missing_created_at?(result_instance)
90
+ result_instance.created_at = Time.current
92
91
  end
93
- end
94
92
 
95
- def clear_changed_attributes_on_result(result_instance)
96
- unless result_instance.respond_to?(:clear_changes_information)
97
- result_instance.extend ActiveModelDirtyBackport
93
+ if missing_updated_at?(result_instance)
94
+ result_instance.updated_at = Time.current
98
95
  end
96
+ end
99
97
 
100
- result_instance.clear_changes_information
98
+ def missing_created_at?(result_instance)
99
+ result_instance.respond_to?(:created_at) &&
100
+ result_instance.respond_to?(:created_at=) &&
101
+ result_instance.created_at.blank?
101
102
  end
102
- end
103
103
 
104
- module ActiveModelDirtyBackport
105
- def clear_changes_information
106
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
107
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
104
+ def missing_updated_at?(result_instance)
105
+ result_instance.respond_to?(:updated_at) &&
106
+ result_instance.respond_to?(:updated_at=) &&
107
+ result_instance.updated_at.blank?
108
108
  end
109
109
  end
110
110
  end
@@ -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
- amount.times.map { 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
 
@@ -37,7 +48,7 @@ module FactoryBot
37
48
  strategy_name = @strategy_name
38
49
 
39
50
  define_syntax_method("#{strategy_name}_pair") do |name, *traits_and_overrides, &block|
40
- 2.times.map { send(strategy_name, name, *traits_and_overrides, &block) }
51
+ Array.new(2) { send(strategy_name, name, *traits_and_overrides, &block) }
41
52
  end
42
53
  end
43
54
 
@@ -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
@@ -17,7 +17,7 @@ module FactoryBot
17
17
  proxy = FactoryBot::DefinitionProxy.new(factory.definition)
18
18
  proxy.instance_eval(&block) if block_given?
19
19
 
20
- FactoryBot.register_factory(factory)
20
+ Internal.register_factory(factory)
21
21
 
22
22
  proxy.child_factories.each do |(child_name, child_options, child_block)|
23
23
  parent_factory = child_options.delete(:parent) || name
@@ -26,41 +26,29 @@ module FactoryBot
26
26
  end
27
27
 
28
28
  def sequence(name, *args, &block)
29
- FactoryBot.register_sequence(Sequence.new(name, *args, &block))
29
+ Internal.register_sequence(Sequence.new(name, *args, &block))
30
30
  end
31
31
 
32
32
  def trait(name, &block)
33
- FactoryBot.register_trait(Trait.new(name, &block))
34
- end
35
-
36
- def to_create(&block)
37
- FactoryBot.to_create(&block)
38
- end
39
-
40
- def skip_create
41
- FactoryBot.skip_create
42
- end
43
-
44
- def initialize_with(&block)
45
- FactoryBot.initialize_with(&block)
33
+ Internal.register_trait(Trait.new(name, &block))
46
34
  end
47
35
 
48
36
  def self.run(block)
49
37
  new.instance_eval(&block)
50
38
  end
51
39
 
52
- delegate :before, :after, :callback, to: :configuration
53
-
54
- private
55
-
56
- def configuration
57
- FactoryBot.configuration
58
- end
40
+ delegate :after,
41
+ :before,
42
+ :callback,
43
+ :initialize_with,
44
+ :skip_create,
45
+ :to_create,
46
+ to: FactoryBot::Internal
59
47
  end
60
48
 
61
49
  class ModifyDSL
62
- def factory(name, options = {}, &block)
63
- factory = FactoryBot.factory_by_name(name)
50
+ def factory(name, _options = {}, &block)
51
+ factory = Internal.factory_by_name(name)
64
52
  proxy = FactoryBot::DefinitionProxy.new(factory.definition.overridable)
65
53
  proxy.instance_eval(&block)
66
54
  end
@@ -2,8 +2,8 @@ module FactoryBot
2
2
  module Syntax
3
3
  ## This module is a container for all strategy methods provided by
4
4
  ## FactoryBot. This includes all the default strategies provided ({Methods#build},
5
- ## {Methods#create}, {Methods#build_stubbed}, and {Methods#attributes_for}), as well as
6
- ## the complementary *_list methods.
5
+ ## {Methods#create}, {Methods#build_stubbed}, and {Methods#attributes_for}), as
6
+ ## well as the complementary *_list and *_pair methods.
7
7
  ## @example singular factory execution
8
8
  ## # basic use case
9
9
  ## build(:completed_order)
@@ -30,7 +30,7 @@ module FactoryBot
30
30
  ## # factory with traits and attribute override
31
31
  ## build_stubbed_list(:user, 15, :admin, :male, name: "John Doe")
32
32
  module Methods
33
- # @!parse FactoryBot.register_default_strategies
33
+ # @!parse FactoryBot::Internal.register_default_strategies
34
34
  # @!method build(name, *traits_and_overrides, &block)
35
35
  # (see #strategy_method)
36
36
  # Builds a registered factory by name.
@@ -51,22 +51,38 @@ module FactoryBot
51
51
  # Generates a hash of attributes for a registered factory by name.
52
52
  # @return [Hash] hash of attributes for the factory
53
53
 
54
- # @!method build_list(name, amount, *traits_and_overrides)
54
+ # @!method build_list(name, amount, *traits_and_overrides, &block)
55
55
  # (see #strategy_method_list)
56
56
  # @return [Array] array of built objects defined by the factory
57
57
 
58
- # @!method create_list(name, amount, *traits_and_overrides)
58
+ # @!method create_list(name, amount, *traits_and_overrides, &block)
59
59
  # (see #strategy_method_list)
60
60
  # @return [Array] array of created objects defined by the factory
61
61
 
62
- # @!method build_stubbed_list(name, amount, *traits_and_overrides)
62
+ # @!method build_stubbed_list(name, amount, *traits_and_overrides, &block)
63
63
  # (see #strategy_method_list)
64
64
  # @return [Array] array of stubbed objects defined by the factory
65
65
 
66
- # @!method attributes_for_list(name, amount, *traits_and_overrides)
66
+ # @!method attributes_for_list(name, amount, *traits_and_overrides, &block)
67
67
  # (see #strategy_method_list)
68
68
  # @return [Array<Hash>] array of attribute hashes for the factory
69
69
 
70
+ # @!method build_pair(name, *traits_and_overrides, &block)
71
+ # (see #strategy_method_pair)
72
+ # @return [Array] pair of built objects defined by the factory
73
+
74
+ # @!method create_pair(name, *traits_and_overrides, &block)
75
+ # (see #strategy_method_pair)
76
+ # @return [Array] pair of created objects defined by the factory
77
+
78
+ # @!method build_stubbed_pair(name, *traits_and_overrides, &block)
79
+ # (see #strategy_method_pair)
80
+ # @return [Array] pair of stubbed objects defined by the factory
81
+
82
+ # @!method attributes_for_pair(name, *traits_and_overrides, &block)
83
+ # (see #strategy_method_pair)
84
+ # @return [Array<Hash>] pair of attribute hashes for the factory
85
+
70
86
  # @!method strategy_method
71
87
  # @!visibility private
72
88
  # @param [Symbol] name the name of the factory to build
@@ -78,6 +94,13 @@ module FactoryBot
78
94
  # @param [Symbol] name the name of the factory to execute
79
95
  # @param [Integer] amount the number of instances to execute
80
96
  # @param [Array<Symbol, Symbol, Hash>] traits_and_overrides splat args traits and a hash of overrides
97
+ # @param [Proc] block block to be executed
98
+
99
+ # @!method strategy_method_pair
100
+ # @!visibility private
101
+ # @param [Symbol] name the name of the factory to execute
102
+ # @param [Array<Symbol, Symbol, Hash>] traits_and_overrides splat args traits and a hash of overrides
103
+ # @param [Proc] block block to be executed
81
104
 
82
105
  # Generates and returns the next value in a sequence.
83
106
  #
@@ -88,7 +111,7 @@ module FactoryBot
88
111
  # Returns:
89
112
  # The next value in the sequence. (Object)
90
113
  def generate(name)
91
- FactoryBot.sequence_by_name(name).next
114
+ Internal.sequence_by_name(name).next
92
115
  end
93
116
 
94
117
  # Generates and returns the list of values in a sequence.
@@ -103,7 +126,7 @@ module FactoryBot
103
126
  # The next value in the sequence. (Object)
104
127
  def generate_list(name, count)
105
128
  (1..count).map do
106
- FactoryBot.sequence_by_name(name).next
129
+ Internal.sequence_by_name(name).next
107
130
  end
108
131
  end
109
132
  end