factory_bot 5.1.0 → 6.0.1

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +52 -13
  3. data/GETTING_STARTED.md +426 -68
  4. data/NEWS.md +22 -1
  5. data/README.md +4 -4
  6. data/lib/factory_bot.rb +21 -55
  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 +9 -10
  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_list.rb +2 -2
  16. data/lib/factory_bot/decorator.rb +1 -1
  17. data/lib/factory_bot/decorator/invocation_tracker.rb +1 -1
  18. data/lib/factory_bot/definition.rb +45 -17
  19. data/lib/factory_bot/definition_hierarchy.rb +1 -11
  20. data/lib/factory_bot/definition_proxy.rb +64 -6
  21. data/lib/factory_bot/enum.rb +27 -0
  22. data/lib/factory_bot/evaluator.rb +5 -7
  23. data/lib/factory_bot/evaluator_class_definer.rb +1 -1
  24. data/lib/factory_bot/factory.rb +12 -12
  25. data/lib/factory_bot/factory_runner.rb +3 -3
  26. data/lib/factory_bot/find_definitions.rb +1 -1
  27. data/lib/factory_bot/internal.rb +18 -29
  28. data/lib/factory_bot/linter.rb +9 -13
  29. data/lib/factory_bot/null_factory.rb +10 -4
  30. data/lib/factory_bot/null_object.rb +2 -6
  31. data/lib/factory_bot/registry.rb +4 -4
  32. data/lib/factory_bot/reload.rb +0 -1
  33. data/lib/factory_bot/sequence.rb +5 -5
  34. data/lib/factory_bot/strategy/null.rb +4 -2
  35. data/lib/factory_bot/strategy/stub.rb +6 -2
  36. data/lib/factory_bot/strategy_syntax_method_registrar.rb +12 -1
  37. data/lib/factory_bot/syntax/default.rb +7 -19
  38. data/lib/factory_bot/trait.rb +1 -1
  39. data/lib/factory_bot/version.rb +1 -1
  40. metadata +9 -22
data/NEWS.md CHANGED
@@ -1,12 +1,33 @@
1
1
  # News
2
2
 
3
+ ## 6.0.1 (June 19, 2020)
4
+ * Fixed: bug with constant resolution causing unexpected uninitialized constant errors
5
+
6
+ ## 6.0.0 (June 18, 2020)
7
+ * Added: automatic definition of traits for Active Record enum attributes, enabled by default
8
+ * Added: `traits_for_enum` method to define traits for non-Active Record enums
9
+ * Added: `build_stubbed_starting_id=` option to define the starting id for `build_stubbed`
10
+ * Removed: deprecated methods on the top-level `FactoryBot` module meant only for internal use
11
+ * Removed: support for EOL versions of Ruby (2.3, 2.4) and Rails (4.2)
12
+
13
+ ## 5.2.0 (April 24, 2020)
14
+ * Added: Pass index to block for `*_list` methods
15
+ * Deprecated: methods on the top-level `FactoryBot` module meant only for internal use: `callbacks`, `configuration`, `constructor`, `initialize_with`, `register_sequence`, `resent_configuration`, `skip_create`, `to_create`
16
+
17
+ ## 5.1.2 (March 25, 2020)
18
+ * Fixed: Ruby 2.7 keyword deprecation warning in FactoryBot.lint
19
+
20
+ ## 5.1.1 (October 2, 2019)
21
+ * Improved: performance of traits
22
+ * Fixed: registering strategies on JRuby
23
+
3
24
  ## 5.1.0 (September 21, 2019)
4
25
  * Added: "Did you mean?" style error message to help with typos in association declarations
5
26
  * Changed: `NoMethodError` for static attributes now offers a "Did you mean?" style message
6
27
  * Fixed: avoid undefining inherited evaluator methods
7
28
  * Fixed: avoid stubbing id for records without a primary key
8
29
  * Fixed: raise a helpful error for self-referencing traits to avoid a `SystemStackError`
9
- * Deprecated: top-level methods meant only for internal use: `allow_class_lookup`, `allow_class_lookup`=, `register_trait`, `trait_by_name`, `traits`, `sequence_by_name`, `sequences`, `factory_by_name`, `register_factory`, `callback_names`, `register_callback`, `register_default_callbacks`, `register_default_strategies`, `strategies`
30
+ * Deprecated: methods on the top-level `FactoryBot` module meant only for internal use: `allow_class_lookup`, `allow_class_lookup`=, `register_trait`, `trait_by_name`, `traits`, `sequence_by_name`, `sequences`, `factory_by_name`, `register_factory`, `callback_names`, `register_callback`, `register_default_callbacks`, `register_default_strategies`, `strategies`
10
31
 
11
32
  ## 5.0.2 (February 22, 2019)
12
33
  * Bugfix: raise "Trait not registered" error when passing invalid trait arguments
data/README.md CHANGED
@@ -72,14 +72,14 @@ Contributing
72
72
 
73
73
  Please see [CONTRIBUTING.md](https://github.com/thoughtbot/factory_bot/blob/master/CONTRIBUTING.md).
74
74
 
75
- factory_bot was originally written by Joe Ferris and is now maintained by Josh
76
- Clayton. Many improvements and bugfixes were contributed by the [open source
75
+ factory_bot was originally written by Joe Ferris and is maintained by thoughtbot.
76
+ Many improvements and bugfixes were contributed by the [open source
77
77
  community](https://github.com/thoughtbot/factory_bot/graphs/contributors).
78
78
 
79
79
  License
80
80
  -------
81
81
 
82
- factory_bot is Copyright © 2008-2019 Joe Ferris and thoughtbot. It is free
82
+ factory_bot is Copyright © 2008-2020 Joe Ferris and thoughtbot. It is free
83
83
  software, and may be redistributed under the terms specified in the
84
84
  [LICENSE] file.
85
85
 
@@ -89,7 +89,7 @@ software, and may be redistributed under the terms specified in the
89
89
  About thoughtbot
90
90
  ----------------
91
91
 
92
- ![thoughtbot](https://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg)
92
+ ![thoughtbot](https://thoughtbot.com/brand_assets/93:44.svg)
93
93
 
94
94
  factory_bot is maintained and funded by thoughtbot, inc.
95
95
  The names and logos for thoughtbot are trademarks of thoughtbot, inc.
@@ -4,6 +4,7 @@ require "active_support/core_ext/module/attribute_accessors"
4
4
  require "active_support/deprecation"
5
5
  require "active_support/notifications"
6
6
 
7
+ require "factory_bot/internal"
7
8
  require "factory_bot/definition_hierarchy"
8
9
  require "factory_bot/configuration"
9
10
  require "factory_bot/errors"
@@ -31,6 +32,7 @@ require "factory_bot/declaration"
31
32
  require "factory_bot/sequence"
32
33
  require "factory_bot/attribute_list"
33
34
  require "factory_bot/trait"
35
+ require "factory_bot/enum"
34
36
  require "factory_bot/aliases"
35
37
  require "factory_bot/definition"
36
38
  require "factory_bot/definition_proxy"
@@ -45,22 +47,16 @@ require "factory_bot/decorator/invocation_tracker"
45
47
  require "factory_bot/decorator/new_constructor"
46
48
  require "factory_bot/linter"
47
49
  require "factory_bot/version"
48
- require "factory_bot/internal"
49
50
 
50
51
  module FactoryBot
51
- Deprecation = ActiveSupport::Deprecation.new("6.0", "factory_bot")
52
-
53
- def self.configuration
54
- Internal.configuration
55
- end
56
-
57
- def self.reset_configuration
58
- Internal.reset_configuration
59
- end
52
+ Deprecation = ActiveSupport::Deprecation.new("7.0", "factory_bot")
60
53
 
61
54
  mattr_accessor :use_parent_strategy, instance_accessor: false
62
55
  self.use_parent_strategy = true
63
56
 
57
+ mattr_accessor :automatically_define_enum_traits, instance_accessor: false
58
+ self.automatically_define_enum_traits = true
59
+
64
60
  # Look for errors in factories and (optionally) their traits.
65
61
  # Parameters:
66
62
  # factories - which factories to lint; omit for all factories
@@ -71,55 +67,25 @@ module FactoryBot
71
67
  def self.lint(*args)
72
68
  options = args.extract_options!
73
69
  factories_to_lint = args[0] || FactoryBot.factories
74
- Linter.new(factories_to_lint, options).lint!
70
+ Linter.new(factories_to_lint, **options).lint!
75
71
  end
76
72
 
77
- class << self
78
- delegate :callbacks,
79
- :callback_names,
80
- :constructor,
81
- :factories,
82
- :initialize_with,
83
- :sequences,
84
- :skip_create,
85
- :strategies,
86
- :to_create,
87
- :traits,
88
- to: :configuration
89
-
90
- delegate :factory_by_name,
91
- :register_callback,
92
- :register_default_callbacks,
93
- :register_default_strategies,
94
- :register_factory,
95
- :register_sequence,
96
- :register_strategy,
97
- :register_trait,
98
- :rewind_sequences,
99
- :sequence_by_name,
100
- :strategy_by_name,
101
- :trait_by_name,
102
- to: Internal
103
-
104
- attr_accessor :allow_class_lookup
73
+ # Set the starting value for ids when using the build_stubbed strategy
74
+ #
75
+ # Arguments:
76
+ # * starting_id +Integer+
77
+ # The new starting id value.
78
+ def self.build_stubbed_starting_id=(starting_id)
79
+ Strategy::Stub.next_id = starting_id - 1
80
+ end
105
81
 
106
- deprecate :allow_class_lookup,
107
- :allow_class_lookup=,
108
- :callback_names,
109
- :factory_by_name,
110
- :register_callback,
111
- :register_default_callbacks,
112
- :register_default_strategies,
113
- :register_factory,
114
- :register_trait,
115
- :sequence_by_name,
116
- :sequences,
117
- :strategies,
118
- :trait_by_name,
119
- :traits,
120
- deprecator: Deprecation
82
+ class << self
83
+ delegate :factories,
84
+ :register_strategy,
85
+ :rewind_sequences,
86
+ :strategy_by_name,
87
+ to: Internal
121
88
  end
122
89
  end
123
90
 
124
91
  FactoryBot::Internal.register_default_strategies
125
- FactoryBot::Internal.register_default_callbacks
@@ -5,14 +5,14 @@ module FactoryBot
5
5
 
6
6
  self.aliases = [
7
7
  [/(.+)_id/, '\1'],
8
- [/(.*)/, '\1_id'],
8
+ [/(.*)/, '\1_id']
9
9
  ]
10
10
 
11
11
  def self.aliases_for(attribute)
12
- aliases.map do |(pattern, replace)|
12
+ aliases.map { |(pattern, replace)|
13
13
  if pattern.match(attribute.to_s)
14
14
  attribute.to_s.sub(pattern, replace).to_sym
15
15
  end
16
- end.compact << attribute
16
+ }.compact << attribute
17
17
  end
18
18
  end
@@ -6,12 +6,12 @@ module FactoryBot
6
6
 
7
7
  def initialize(name, factory, overrides)
8
8
  super(name, false)
9
- @factory = factory
9
+ @factory = factory
10
10
  @overrides = overrides
11
11
  end
12
12
 
13
13
  def to_proc
14
- factory = @factory
14
+ factory = @factory
15
15
  overrides = @overrides
16
16
  traits_and_overrides = [factory, overrides].flatten
17
17
  factory_name = traits_and_overrides.shift
@@ -14,8 +14,9 @@ module FactoryBot
14
14
  value = case block.arity
15
15
  when 1, -1 then instance_exec(self, &block)
16
16
  else instance_exec(&block)
17
- end
17
+ end
18
18
  raise SequenceAbuseError if FactoryBot::Sequence === value
19
+
19
20
  value
20
21
  }
21
22
  end
@@ -2,10 +2,10 @@ module FactoryBot
2
2
  # @api private
3
3
  class AttributeAssigner
4
4
  def initialize(evaluator, build_class, &instance_builder)
5
- @build_class = build_class
6
- @instance_builder = instance_builder
7
- @evaluator = evaluator
8
- @attribute_list = evaluator.class.attribute_list
5
+ @build_class = build_class
6
+ @instance_builder = instance_builder
7
+ @evaluator = evaluator
8
+ @attribute_list = evaluator.class.attribute_list
9
9
  @attribute_names_assigned = []
10
10
  end
11
11
 
@@ -22,9 +22,8 @@ module FactoryBot
22
22
  def hash
23
23
  @evaluator.instance = build_hash
24
24
 
25
- attributes_to_set_on_hash.reduce({}) do |result, attribute|
25
+ attributes_to_set_on_hash.each_with_object({}) do |attribute, result|
26
26
  result[attribute] = get(attribute)
27
- result
28
27
  end
29
28
  end
30
29
 
@@ -33,13 +32,13 @@ module FactoryBot
33
32
  def method_tracking_evaluator
34
33
  @method_tracking_evaluator ||= Decorator::AttributeHash.new(
35
34
  decorated_evaluator,
36
- attribute_names_to_assign,
35
+ attribute_names_to_assign
37
36
  )
38
37
  end
39
38
 
40
39
  def decorated_evaluator
41
40
  Decorator::InvocationTracker.new(
42
- Decorator::NewConstructor.new(@evaluator, @build_class),
41
+ Decorator::NewConstructor.new(@evaluator, @build_class)
43
42
  )
44
43
  end
45
44
 
@@ -96,11 +95,11 @@ module FactoryBot
96
95
  end
97
96
 
98
97
  def alias_names_to_ignore
99
- @attribute_list.non_ignored.flat_map do |attribute|
98
+ @attribute_list.non_ignored.flat_map { |attribute|
100
99
  override_names.map do |override|
101
100
  attribute.name if ignorable_alias?(attribute, override)
102
101
  end
103
- end.compact
102
+ }.compact
104
103
  end
105
104
 
106
105
  def ignorable_alias?(attribute, override)
@@ -4,7 +4,7 @@ module FactoryBot
4
4
  include Enumerable
5
5
 
6
6
  def initialize(name = nil, attributes = [])
7
- @name = name
7
+ @name = name
8
8
  @attributes = attributes
9
9
  end
10
10
 
@@ -3,16 +3,15 @@ module FactoryBot
3
3
  attr_reader :name
4
4
 
5
5
  def initialize(name, block)
6
- @name = name.to_sym
6
+ @name = name.to_sym
7
7
  @block = block
8
- ensure_valid_callback_name!
9
8
  end
10
9
 
11
10
  def run(instance, evaluator)
12
11
  case block.arity
13
12
  when 1, -1 then syntax_runner.instance_exec(instance, &block)
14
13
  when 2 then syntax_runner.instance_exec(instance, evaluator, &block)
15
- else syntax_runner.instance_exec(&block)
14
+ else syntax_runner.instance_exec(&block)
16
15
  end
17
16
  end
18
17
 
@@ -27,13 +26,6 @@ module FactoryBot
27
26
 
28
27
  private
29
28
 
30
- def ensure_valid_callback_name!
31
- unless FactoryBot::Internal.callback_names.include?(name)
32
- raise InvalidCallbackNameError, "#{name} is not a valid callback name. " +
33
- "Valid callback names are #{FactoryBot::Internal.callback_names.inspect}"
34
- end
35
- end
36
-
37
29
  def syntax_runner
38
30
  @syntax_runner ||= SyntaxRunner.new
39
31
  end
@@ -7,16 +7,16 @@ module FactoryBot
7
7
  :inline_sequences,
8
8
  :sequences,
9
9
  :strategies,
10
- :traits,
10
+ :traits
11
11
  )
12
12
 
13
13
  def initialize
14
- @factories = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Factory"))
15
- @sequences = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Sequence"))
16
- @traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Trait"))
17
- @strategies = Registry.new("Strategy")
14
+ @factories = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Factory"))
15
+ @sequences = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Sequence"))
16
+ @traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Trait"))
17
+ @strategies = Registry.new("Strategy")
18
18
  @callback_names = Set.new
19
- @definition = Definition.new(:configuration)
19
+ @definition = Definition.new(:configuration)
20
20
  @inline_sequences = []
21
21
 
22
22
  to_create(&:save!)
@@ -8,7 +8,7 @@ module FactoryBot
8
8
  attr_reader :name
9
9
 
10
10
  def initialize(name, ignored = false)
11
- @name = name
11
+ @name = name
12
12
  @ignored = ignored
13
13
  end
14
14
 
@@ -5,8 +5,8 @@ module FactoryBot
5
5
 
6
6
  def initialize(name = nil)
7
7
  @declarations = []
8
- @name = name
9
- @overridable = false
8
+ @name = name
9
+ @overridable = false
10
10
  end
11
11
 
12
12
  def declare_attribute(declaration)
@@ -6,7 +6,7 @@ module FactoryBot
6
6
  @component = component
7
7
  end
8
8
 
9
- def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissing
9
+ def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissingSuper
10
10
  @component.send(name, *args, &block)
11
11
  end
12
12
 
@@ -6,7 +6,7 @@ module FactoryBot
6
6
  @invoked_methods = []
7
7
  end
8
8
 
9
- def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissing
9
+ def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing
10
10
  @invoked_methods << name
11
11
  super
12
12
  end
@@ -1,19 +1,20 @@
1
1
  module FactoryBot
2
2
  # @api private
3
3
  class Definition
4
- attr_reader :defined_traits, :declarations, :name
4
+ attr_reader :defined_traits, :declarations, :name, :registered_enums
5
5
 
6
6
  def initialize(name, base_traits = [])
7
- @name = name
8
- @declarations = DeclarationList.new(name)
9
- @callbacks = []
10
- @defined_traits = Set.new
11
- @to_create = nil
12
- @base_traits = base_traits
7
+ @name = name
8
+ @declarations = DeclarationList.new(name)
9
+ @callbacks = []
10
+ @defined_traits = Set.new
11
+ @registered_enums = []
12
+ @to_create = nil
13
+ @base_traits = base_traits
13
14
  @additional_traits = []
14
- @constructor = nil
15
- @attributes = nil
16
- @compiled = false
15
+ @constructor = nil
16
+ @attributes = nil
17
+ @compiled = false
17
18
  end
18
19
 
19
20
  delegate :declare_attribute, to: :declarations
@@ -43,13 +44,15 @@ module FactoryBot
43
44
  aggregate_from_traits_and_self(:callbacks) { @callbacks }
44
45
  end
45
46
 
46
- def compile
47
+ def compile(klass = nil)
47
48
  unless @compiled
49
+ expand_enum_traits(klass) unless klass.nil?
50
+
48
51
  declarations.attributes
49
52
 
50
53
  defined_traits.each do |defined_trait|
51
- base_traits.each { |bt| bt.define_trait defined_trait }
52
- additional_traits.each { |bt| bt.define_trait defined_trait }
54
+ base_traits.each { |bt| bt.define_trait defined_trait }
55
+ additional_traits.each { |at| at.define_trait defined_trait }
53
56
  end
54
57
 
55
58
  @compiled = true
@@ -81,6 +84,10 @@ module FactoryBot
81
84
  @defined_traits.add(trait)
82
85
  end
83
86
 
87
+ def register_enum(enum)
88
+ @registered_enums << enum
89
+ end
90
+
84
91
  def define_constructor(&block)
85
92
  @constructor = block
86
93
  end
@@ -95,7 +102,6 @@ module FactoryBot
95
102
 
96
103
  def callback(*names, &block)
97
104
  names.each do |name|
98
- FactoryBot::Internal.register_callback(name)
99
105
  add_callback(Callback.new(name, block))
100
106
  end
101
107
  end
@@ -115,13 +121,15 @@ module FactoryBot
115
121
  end
116
122
 
117
123
  def trait_for(name)
118
- defined_traits.detect { |trait| trait.name == name.to_s }
124
+ @defined_traits_by_name ||= defined_traits.each_with_object({}) { |t, memo| memo[t.name] ||= t }
125
+ @defined_traits_by_name[name.to_s]
119
126
  end
120
127
 
121
128
  def initialize_copy(source)
122
129
  super
123
130
  @attributes = nil
124
- @compiled = false
131
+ @compiled = false
132
+ @defined_traits_by_name = nil
125
133
  end
126
134
 
127
135
  def aggregate_from_traits_and_self(method_name, &block)
@@ -130,8 +138,28 @@ module FactoryBot
130
138
  [
131
139
  base_traits.map(&method_name),
132
140
  instance_exec(&block),
133
- additional_traits.map(&method_name),
141
+ additional_traits.map(&method_name)
134
142
  ].flatten.compact
135
143
  end
144
+
145
+ def expand_enum_traits(klass)
146
+ if automatically_register_defined_enums?(klass)
147
+ automatically_register_defined_enums(klass)
148
+ end
149
+
150
+ registered_enums.each do |enum|
151
+ traits = enum.build_traits(klass)
152
+ traits.each { |trait| define_trait(trait) }
153
+ end
154
+ end
155
+
156
+ def automatically_register_defined_enums(klass)
157
+ klass.defined_enums.each_key { |name| register_enum(Enum.new(name)) }
158
+ end
159
+
160
+ def automatically_register_defined_enums?(klass)
161
+ FactoryBot.automatically_define_enum_traits &&
162
+ klass.respond_to?(:defined_enums)
163
+ end
136
164
  end
137
165
  end