factory_bot 5.2.0 → 6.2.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +58 -13
  3. data/GETTING_STARTED.md +684 -136
  4. data/NEWS.md +30 -2
  5. data/README.md +4 -10
  6. data/lib/factory_bot.rb +19 -53
  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 -2
  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 +3 -11
  13. data/lib/factory_bot/configuration.rb +7 -7
  14. data/lib/factory_bot/declaration.rb +1 -1
  15. data/lib/factory_bot/declaration/association.rb +23 -6
  16. data/lib/factory_bot/declaration_list.rb +2 -2
  17. data/lib/factory_bot/decorator.rb +18 -6
  18. data/lib/factory_bot/decorator/invocation_tracker.rb +1 -0
  19. data/lib/factory_bot/definition.rb +61 -16
  20. data/lib/factory_bot/definition_proxy.rb +63 -5
  21. data/lib/factory_bot/enum.rb +27 -0
  22. data/lib/factory_bot/evaluator.rb +6 -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 +12 -25
  28. data/lib/factory_bot/linter.rb +8 -12
  29. data/lib/factory_bot/null_factory.rb +11 -5
  30. data/lib/factory_bot/null_object.rb +2 -6
  31. data/lib/factory_bot/registry.rb +2 -2
  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/syntax/default.rb +7 -7
  37. data/lib/factory_bot/trait.rb +2 -2
  38. data/lib/factory_bot/version.rb +1 -1
  39. metadata +12 -40
data/NEWS.md CHANGED
@@ -1,8 +1,36 @@
1
1
  # News
2
2
 
3
+ ## 6.2.0 (May 7, 2021)
4
+ * Added: support for Ruby 3.0
5
+ * Changed: Include factory or trait name in error messages for missing traits. d05a9a3c
6
+ * Changed: Switched from Travis CI to GitHub Actions
7
+ * Fixed: More Ruby 2.7 kwarg deprecation warnings
8
+
9
+ ## 6.1.0 (July 8, 2020)
10
+ * Added: public reader for the evaluation instance, helpful for building interrelated associations
11
+ * Changed: raise a more helpful error when passing an invalid argument to an association
12
+ * Fixed: Ruby 2.7 kwarg deprecation warnings
13
+
14
+ ## 6.0.2 (June 19, 2020)
15
+ * Fixed: bug causing traits to consume more memory each time they were used
16
+
17
+ ## 6.0.1 (June 19, 2020)
18
+ * Fixed: bug with constant resolution causing unexpected uninitialized constant errors
19
+
20
+ ## 6.0.0 (June 18, 2020)
21
+ * Added: automatic definition of traits for Active Record enum attributes, enabled by default
22
+ (Note that this required changing where factory_bot constantizes the build
23
+ class, which may affect applications that were using abstract factories for
24
+ inheritance. See issue #1409.)
25
+ * Added: `traits_for_enum` method to define traits for non-Active Record enums
26
+ * Added: `build_stubbed_starting_id=` option to define the starting id for `build_stubbed`
27
+ * Removed: deprecated methods on the top-level `FactoryBot` module meant only for internal use
28
+ * Removed: support for EOL versions of Ruby (2.3, 2.4) and Rails (4.2)
29
+ * Removed: support for "abstract" factories with no associated class; use traits instead.
30
+
3
31
  ## 5.2.0 (April 24, 2020)
4
32
  * Added: Pass index to block for `*_list` methods
5
- * Deprecated: top-level methods meant only for internal use: `callbacks`, `configuration`, `constructor`, `initialize_with`, `register_sequence`, `resent_configuration`, `skip_create`, `to_create`
33
+ * 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`
6
34
 
7
35
  ## 5.1.2 (March 25, 2020)
8
36
  * Fixed: Ruby 2.7 keyword deprecation warning in FactoryBot.lint
@@ -17,7 +45,7 @@
17
45
  * Fixed: avoid undefining inherited evaluator methods
18
46
  * Fixed: avoid stubbing id for records without a primary key
19
47
  * Fixed: raise a helpful error for self-referencing traits to avoid a `SystemStackError`
20
- * 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`
48
+ * 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`
21
49
 
22
50
  ## 5.0.2 (February 22, 2019)
23
51
  * Bugfix: raise "Trait not registered" error when passing invalid trait arguments
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # factory_bot [![Build Status][ci-image]][ci] [![Code Climate][grade-image]][grade] [![Gem Version][version-image]][version] [![Reviewed by Hound][hound-badge-image]][hound]
1
+ # factory_bot [![Build Status][ci-image]][ci] [![Code Climate][grade-image]][grade] [![Gem Version][version-image]][version]
2
2
 
3
3
  factory_bot is a fixtures replacement with a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute hashes, and stubbed objects), and support for multiple factories for the same class (user, admin_user, and so on), including factory inheritance.
4
4
 
@@ -43,13 +43,7 @@ gem install factory_bot
43
43
  Supported Ruby versions
44
44
  -----------------------
45
45
 
46
- The factory_bot 5.x series supports MRI Ruby 2.3+.
47
-
48
- The factory_bot 3.x+ series supports MRI Ruby 1.9. Additionally, factory_bot
49
- 3.6+ supports JRuby 1.6.7.2+ while running in 1.9 mode. See [GETTING_STARTED]
50
- for more information on configuring the JRuby environment.
51
-
52
- For versions of Ruby prior to 1.9, please use factory_bot 2.x.
46
+ Supported Ruby versions are listed in [`.github/workflows/build.yml`](https://github.com/thoughtbot/factory_bot/blob/master/.github/workflows/build.yml)
53
47
 
54
48
  More Information
55
49
  ----------------
@@ -100,8 +94,8 @@ See [our other projects][community] or
100
94
 
101
95
  [community]: https://thoughtbot.com/community?utm_source=github
102
96
  [hire]: https://thoughtbot.com/hire-us?utm_source=github
103
- [ci-image]: https://travis-ci.org/thoughtbot/factory_bot.svg
104
- [ci]: https://travis-ci.org/thoughtbot/factory_bot?branch=master
97
+ [ci-image]: https://github.com/thoughtbot/factory_bot/actions/workflows/build.yml/badge.svg
98
+ [ci]: https://github.com/thoughtbot/factory_bot/actions?query=workflow%3A.github%2Fworkflows%2Fbuild.yml+branch%3Amaster++
105
99
  [grade-image]: https://codeclimate.com/github/thoughtbot/factory_bot/badges/gpa.svg
106
100
  [grade]: https://codeclimate.com/github/thoughtbot/factory_bot
107
101
  [version-image]: https://badge.fury.io/rb/factory_bot.svg
data/lib/factory_bot.rb CHANGED
@@ -32,6 +32,7 @@ require "factory_bot/declaration"
32
32
  require "factory_bot/sequence"
33
33
  require "factory_bot/attribute_list"
34
34
  require "factory_bot/trait"
35
+ require "factory_bot/enum"
35
36
  require "factory_bot/aliases"
36
37
  require "factory_bot/definition"
37
38
  require "factory_bot/definition_proxy"
@@ -48,11 +49,14 @@ require "factory_bot/linter"
48
49
  require "factory_bot/version"
49
50
 
50
51
  module FactoryBot
51
- Deprecation = ActiveSupport::Deprecation.new("6.0", "factory_bot")
52
+ Deprecation = ActiveSupport::Deprecation.new("7.0", "factory_bot")
52
53
 
53
54
  mattr_accessor :use_parent_strategy, instance_accessor: false
54
55
  self.use_parent_strategy = true
55
56
 
57
+ mattr_accessor :automatically_define_enum_traits, instance_accessor: false
58
+ self.automatically_define_enum_traits = true
59
+
56
60
  # Look for errors in factories and (optionally) their traits.
57
61
  # Parameters:
58
62
  # factories - which factories to lint; omit for all factories
@@ -66,60 +70,22 @@ module FactoryBot
66
70
  Linter.new(factories_to_lint, **options).lint!
67
71
  end
68
72
 
69
- class << self
70
- delegate :callback_names,
71
- :callbacks,
72
- :configuration,
73
- :constructor,
74
- :factories,
75
- :factory_by_name,
76
- :initialize_with,
77
- :register_callback,
78
- :register_default_callbacks,
79
- :register_default_strategies,
80
- :register_factory,
81
- :register_sequence,
82
- :register_strategy,
83
- :register_trait,
84
- :reset_configuration,
85
- :rewind_sequences,
86
- :sequence_by_name,
87
- :sequences,
88
- :skip_create,
89
- :strategies,
90
- :strategy_by_name,
91
- :to_create,
92
- :trait_by_name,
93
- :traits,
94
- to: Internal
95
-
96
- 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
97
81
 
98
- deprecate :allow_class_lookup,
99
- :allow_class_lookup=,
100
- :callback_names,
101
- :callbacks,
102
- :configuration,
103
- :constructor,
104
- :factory_by_name,
105
- :initialize_with,
106
- :register_callback,
107
- :register_default_callbacks,
108
- :register_default_strategies,
109
- :register_factory,
110
- :register_sequence,
111
- :register_trait,
112
- :reset_configuration,
113
- :sequence_by_name,
114
- :sequences,
115
- :skip_create,
116
- :strategies,
117
- :to_create,
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
@@ -12,9 +12,9 @@ module FactoryBot
12
12
 
13
13
  -> {
14
14
  value = case block.arity
15
- when 1, -1 then instance_exec(self, &block)
15
+ when 1, -1, -2 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
20
  value
@@ -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
- when 1, -1 then syntax_runner.instance_exec(instance, &block)
12
+ when 1, -1, -2 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!)
@@ -24,7 +24,7 @@ module FactoryBot
24
24
  end
25
25
 
26
26
  delegate :to_create, :skip_create, :constructor, :before, :after,
27
- :callback, :callbacks, to: :@definition
27
+ :callback, :callbacks, to: :@definition
28
28
 
29
29
  def initialize_with(&block)
30
30
  @definition.define_constructor(&block)
@@ -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
 
@@ -6,6 +6,7 @@ module FactoryBot
6
6
  super(name, false)
7
7
  @options = options.dup
8
8
  @overrides = options.extract_options!
9
+ @factory_name = @overrides.delete(:factory) || name
9
10
  @traits = options
10
11
  end
11
12
 
@@ -21,20 +22,36 @@ module FactoryBot
21
22
 
22
23
  private
23
24
 
25
+ attr_reader :factory_name, :overrides, :traits
26
+
24
27
  def build
25
- ensure_factory_is_not_a_declaration!
28
+ raise_if_arguments_are_declarations!
26
29
 
27
- factory_name = @overrides[:factory] || name
28
- [Attribute::Association.new(name, factory_name, [@traits, @overrides.except(:factory)].flatten)]
30
+ [
31
+ Attribute::Association.new(
32
+ name,
33
+ factory_name,
34
+ [traits, overrides].flatten
35
+ )
36
+ ]
29
37
  end
30
38
 
31
- def ensure_factory_is_not_a_declaration!
32
- if @overrides[:factory].is_a?(Declaration)
39
+ def raise_if_arguments_are_declarations!
40
+ if factory_name.is_a?(Declaration)
33
41
  raise ArgumentError.new(<<~MSG)
34
42
  Association '#{name}' received an invalid factory argument.
35
- Did you mean? 'factory: :#{@overrides[:factory].name}'
43
+ Did you mean? 'factory: :#{factory_name.name}'
36
44
  MSG
37
45
  end
46
+
47
+ overrides.each do |attribute, value|
48
+ if value.is_a?(Declaration)
49
+ raise ArgumentError.new(<<~MSG)
50
+ Association '#{name}' received an invalid attribute override.
51
+ Did you mean? '#{attribute}: :#{value.name}'
52
+ MSG
53
+ end
54
+ end
38
55
  end
39
56
  end
40
57
  end
@@ -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,18 +6,30 @@ module FactoryBot
6
6
  @component = component
7
7
  end
8
8
 
9
- def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissingSuper
10
- @component.send(name, *args, &block)
9
+ if ::Gem::Version.new(::RUBY_VERSION) >= ::Gem::Version.new("2.7")
10
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
11
+ def method_missing(...) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
12
+ @component.send(...)
13
+ end
14
+
15
+ def send(...)
16
+ __send__(...)
17
+ end
18
+ RUBY
19
+ else
20
+ def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
21
+ @component.send(name, *args, &block)
22
+ end
23
+
24
+ def send(symbol, *args, &block)
25
+ __send__(symbol, *args, &block)
26
+ end
11
27
  end
12
28
 
13
29
  def respond_to_missing?(name, include_private = false)
14
30
  @component.respond_to?(name, true) || super
15
31
  end
16
32
 
17
- def send(symbol, *args, &block)
18
- __send__(symbol, *args, &block)
19
- end
20
-
21
33
  def self.const_missing(name)
22
34
  ::Object.const_get(name)
23
35
  end