factory_bot 5.1.2 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +52 -13
  3. data/GETTING_STARTED.md +441 -66
  4. data/NEWS.md +26 -1
  5. data/README.md +5 -11
  6. data/lib/factory_bot.rb +20 -54
  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 +1 -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 +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 +10 -3
  19. data/lib/factory_bot/definition.rb +46 -15
  20. data/lib/factory_bot/definition_hierarchy.rb +1 -11
  21. data/lib/factory_bot/definition_proxy.rb +64 -6
  22. data/lib/factory_bot/enum.rb +27 -0
  23. data/lib/factory_bot/evaluator.rb +19 -11
  24. data/lib/factory_bot/evaluator_class_definer.rb +1 -1
  25. data/lib/factory_bot/factory.rb +12 -12
  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/internal.rb +18 -29
  29. data/lib/factory_bot/linter.rb +8 -12
  30. data/lib/factory_bot/null_factory.rb +11 -5
  31. data/lib/factory_bot/null_object.rb +2 -6
  32. data/lib/factory_bot/registry.rb +2 -2
  33. data/lib/factory_bot/reload.rb +0 -1
  34. data/lib/factory_bot/sequence.rb +5 -5
  35. data/lib/factory_bot/strategy/null.rb +4 -2
  36. data/lib/factory_bot/strategy/stub.rb +6 -2
  37. data/lib/factory_bot/strategy_syntax_method_registrar.rb +12 -1
  38. data/lib/factory_bot/syntax/default.rb +7 -19
  39. data/lib/factory_bot/trait.rb +1 -1
  40. data/lib/factory_bot/version.rb +1 -1
  41. metadata +7 -34
data/NEWS.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # News
2
2
 
3
+ ## 6.1.0 (July 8, 2020)
4
+ * Added: public reader for the evaluation instance, helpful for building interrelated associations
5
+ * Changed: raise a more helpful error when passing an invalid argument to an association
6
+ * Fixed: Ruby 2.7 kwarg deprecation warnings
7
+
8
+ ## 6.0.2 (June 19, 2020)
9
+ * Fixed: bug causing traits to consume more memory each time they were used
10
+
11
+ ## 6.0.1 (June 19, 2020)
12
+ * Fixed: bug with constant resolution causing unexpected uninitialized constant errors
13
+
14
+ ## 6.0.0 (June 18, 2020)
15
+ * Added: automatic definition of traits for Active Record enum attributes, enabled by default
16
+ (Note that this required changing where factory_bot constantizes the build
17
+ class, which may affect applications that were using abstract factories for
18
+ inheritance. See issue #1409.)
19
+ * Added: `traits_for_enum` method to define traits for non-Active Record enums
20
+ * Added: `build_stubbed_starting_id=` option to define the starting id for `build_stubbed`
21
+ * Removed: deprecated methods on the top-level `FactoryBot` module meant only for internal use
22
+ * Removed: support for EOL versions of Ruby (2.3, 2.4) and Rails (4.2)
23
+
24
+ ## 5.2.0 (April 24, 2020)
25
+ * Added: Pass index to block for `*_list` methods
26
+ * 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`
27
+
3
28
  ## 5.1.2 (March 25, 2020)
4
29
  * Fixed: Ruby 2.7 keyword deprecation warning in FactoryBot.lint
5
30
 
@@ -13,7 +38,7 @@
13
38
  * Fixed: avoid undefining inherited evaluator methods
14
39
  * Fixed: avoid stubbing id for records without a primary key
15
40
  * Fixed: raise a helpful error for self-referencing traits to avoid a `SystemStackError`
16
- * 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`
41
+ * 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`
17
42
 
18
43
  ## 5.0.2 (February 22, 2019)
19
44
  * 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 [`.travis.yml`](https://github.com/thoughtbot/factory_bot/blob/master/.travis.yml)
53
47
 
54
48
  More Information
55
49
  ----------------
@@ -72,14 +66,14 @@ Contributing
72
66
 
73
67
  Please see [CONTRIBUTING.md](https://github.com/thoughtbot/factory_bot/blob/master/CONTRIBUTING.md).
74
68
 
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
69
+ factory_bot was originally written by Joe Ferris and is maintained by thoughtbot.
70
+ Many improvements and bugfixes were contributed by the [open source
77
71
  community](https://github.com/thoughtbot/factory_bot/graphs/contributors).
78
72
 
79
73
  License
80
74
  -------
81
75
 
82
- factory_bot is Copyright © 2008-2019 Joe Ferris and thoughtbot. It is free
76
+ factory_bot is Copyright © 2008-2020 Joe Ferris and thoughtbot. It is free
83
77
  software, and may be redistributed under the terms specified in the
84
78
  [LICENSE] file.
85
79
 
@@ -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
@@ -74,52 +70,22 @@ module FactoryBot
74
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,7 +14,7 @@ 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
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
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!)
@@ -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