factory_bot 4.11.1 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/GETTING_STARTED.md +121 -37
  3. data/LICENSE +1 -1
  4. data/NEWS.md +351 -0
  5. data/README.md +18 -22
  6. data/lib/factory_bot/aliases.rb +1 -1
  7. data/lib/factory_bot/attribute/dynamic.rb +1 -0
  8. data/lib/factory_bot/attribute.rb +4 -39
  9. data/lib/factory_bot/attribute_assigner.rb +21 -6
  10. data/lib/factory_bot/attribute_list.rb +2 -1
  11. data/lib/factory_bot/callback.rb +3 -2
  12. data/lib/factory_bot/configuration.rb +16 -20
  13. data/lib/factory_bot/declaration/association.rb +14 -1
  14. data/lib/factory_bot/declaration/dynamic.rb +3 -1
  15. data/lib/factory_bot/declaration/implicit.rb +7 -2
  16. data/lib/factory_bot/declaration.rb +4 -4
  17. data/lib/factory_bot/declaration_list.rb +1 -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/decorator.rb +5 -1
  21. data/lib/factory_bot/definition.rb +10 -7
  22. data/lib/factory_bot/definition_hierarchy.rb +1 -11
  23. data/lib/factory_bot/definition_proxy.rb +59 -62
  24. data/lib/factory_bot/errors.rb +7 -4
  25. data/lib/factory_bot/evaluation.rb +1 -1
  26. data/lib/factory_bot/evaluator.rb +5 -5
  27. data/lib/factory_bot/factory.rb +8 -8
  28. data/lib/factory_bot/factory_runner.rb +3 -3
  29. data/lib/factory_bot/find_definitions.rb +1 -1
  30. data/lib/factory_bot/internal.rb +104 -0
  31. data/lib/factory_bot/linter.rb +36 -19
  32. data/lib/factory_bot/null_factory.rb +4 -1
  33. data/lib/factory_bot/null_object.rb +2 -2
  34. data/lib/factory_bot/registry.rb +15 -6
  35. data/lib/factory_bot/reload.rb +3 -3
  36. data/lib/factory_bot/sequence.rb +0 -1
  37. data/lib/factory_bot/strategy/null.rb +2 -4
  38. data/lib/factory_bot/strategy/stub.rb +32 -31
  39. data/lib/factory_bot/strategy_calculator.rb +1 -1
  40. data/lib/factory_bot/strategy_syntax_method_registrar.rb +13 -2
  41. data/lib/factory_bot/syntax/default.rb +12 -24
  42. data/lib/factory_bot/syntax/methods.rb +32 -9
  43. data/lib/factory_bot/syntax.rb +2 -2
  44. data/lib/factory_bot/trait.rb +6 -3
  45. data/lib/factory_bot/version.rb +1 -1
  46. data/lib/factory_bot.rb +103 -138
  47. metadata +72 -32
  48. data/NEWS +0 -306
  49. data/lib/factory_bot/attribute/static.rb +0 -16
  50. data/lib/factory_bot/declaration/static.rb +0 -26
  51. data/lib/factory_bot/decorator/class_key_hash.rb +0 -28
data/README.md CHANGED
@@ -5,7 +5,7 @@ factory_bot is a fixtures replacement with a straightforward definition syntax,
5
5
  If you want to use factory_bot with Rails, see
6
6
  [factory_bot_rails](https://github.com/thoughtbot/factory_bot_rails).
7
7
 
8
- _[Interested in the history of the project name?](NAME.md)_
8
+ _[Interested in the history of the project name?][NAME]_
9
9
 
10
10
 
11
11
  ### Transitioning from factory\_girl?
@@ -21,7 +21,7 @@ You should find the documentation for your version of factory_bot on [Rubygems](
21
21
  See [GETTING_STARTED] for information on defining and using factories. We also
22
22
  have [a detailed introductory video][], available for free on Upcase.
23
23
 
24
- [a detailed introductory video]: https://upcase.com/videos/factory-girl?utm_source=github&utm_medium=open-source&utm_campaign=factory-girl
24
+ [a detailed introductory video]: https://upcase.com/videos/factory-bot?utm_source=github&utm_medium=open-source&utm_campaign=factory-girl
25
25
 
26
26
  Install
27
27
  --------
@@ -40,22 +40,11 @@ To install the gem manually from your shell, run:
40
40
  gem install factory_bot
41
41
  ```
42
42
 
43
- **Caveat:** As of ActiveSupport 5.0 and above, Ruby 2.2.2+ is required. Because
44
- of Rubygems' dependency resolution when installing gems, you may see an error
45
- similar to:
46
-
47
- ```
48
- $ gem install factory_bot
49
- ERROR: Error installing factory_bot:
50
- activesupport requires Ruby version >= 2.2.2.
51
- ```
52
-
53
- To bypass this, install a pre-5.0 version of ActiveSupport before installing
54
- manually.
55
-
56
43
  Supported Ruby versions
57
44
  -----------------------
58
45
 
46
+ The factory_bot 5.x series supports MRI Ruby 2.3+.
47
+
59
48
  The factory_bot 3.x+ series supports MRI Ruby 1.9. Additionally, factory_bot
60
49
  3.6+ supports JRuby 1.6.7.2+ while running in 1.9 mode. See [GETTING_STARTED]
61
50
  for more information on configuring the JRuby environment.
@@ -70,30 +59,37 @@ More Information
70
59
  * [Issues](https://github.com/thoughtbot/factory_bot/issues)
71
60
  * [GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS](https://robots.thoughtbot.com/)
72
61
 
73
- You may also find useful information under the [factory_girl tag on Stack Overflow](https://stackoverflow.com/questions/tagged/factory-girl).
62
+ [GETTING_STARTED]: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md
63
+ [NAME]: https://github.com/thoughtbot/factory_bot/blob/master/NAME.md
64
+
65
+ Useful Tools
66
+ ------------
74
67
 
75
- [GETTING_STARTED]: https://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md
68
+ * [FactoryTrace](https://github.com/djezzzl/factory_trace) - helps to find unused factories and traits.
76
69
 
77
70
  Contributing
78
71
  ------------
79
72
 
80
73
  Please see [CONTRIBUTING.md](https://github.com/thoughtbot/factory_bot/blob/master/CONTRIBUTING.md).
81
74
 
82
- factory_bot was originally written by Joe Ferris and is now maintained by Josh
83
- 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
84
77
  community](https://github.com/thoughtbot/factory_bot/graphs/contributors).
85
78
 
86
79
  License
87
80
  -------
88
81
 
89
- factory_bot is Copyright © 2008-2016 Joe Ferris and thoughtbot. It is free
82
+ factory_bot is Copyright © 2008-2020 Joe Ferris and thoughtbot. It is free
90
83
  software, and may be redistributed under the terms specified in the
91
- [LICENSE](/LICENSE) file.
84
+ [LICENSE] file.
85
+
86
+ [LICENSE]: https://github.com/thoughtbot/factory_bot/blob/master/LICENSE
87
+
92
88
 
93
89
  About thoughtbot
94
90
  ----------------
95
91
 
96
- ![thoughtbot](https://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg)
92
+ ![thoughtbot](https://thoughtbot.com/brand_assets/93:44.svg)
97
93
 
98
94
  factory_bot is maintained and funded by thoughtbot, inc.
99
95
  The names and logos for thoughtbot are trademarks of thoughtbot, inc.
@@ -5,7 +5,7 @@ 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)
@@ -16,6 +16,7 @@ module FactoryBot
16
16
  else instance_exec(&block)
17
17
  end
18
18
  raise SequenceAbuseError if FactoryBot::Sequence === value
19
+
19
20
  value
20
21
  }
21
22
  end
@@ -1,7 +1,6 @@
1
- require 'factory_bot/attribute/static'
2
- require 'factory_bot/attribute/dynamic'
3
- require 'factory_bot/attribute/association'
4
- require 'factory_bot/attribute/sequence'
1
+ require "factory_bot/attribute/dynamic"
2
+ require "factory_bot/attribute/association"
3
+ require "factory_bot/attribute/sequence"
5
4
 
6
5
  module FactoryBot
7
6
  # @api private
@@ -11,11 +10,10 @@ module FactoryBot
11
10
  def initialize(name, ignored)
12
11
  @name = name.to_sym
13
12
  @ignored = ignored
14
- ensure_non_attribute_writer!
15
13
  end
16
14
 
17
15
  def to_proc
18
- -> { }
16
+ -> {}
19
17
  end
20
18
 
21
19
  def association?
@@ -25,38 +23,5 @@ module FactoryBot
25
23
  def alias_for?(attr)
26
24
  FactoryBot.aliases_for(attr).include?(name)
27
25
  end
28
-
29
- private
30
-
31
- def ensure_non_attribute_writer!
32
- NonAttributeWriterValidator.new(@name).validate!
33
- end
34
-
35
- class NonAttributeWriterValidator
36
- def initialize(method_name)
37
- @method_name = method_name.to_s
38
- @method_name_setter_match = @method_name.match(/(.*)=$/)
39
- end
40
-
41
- def validate!
42
- if method_is_writer?
43
- raise AttributeDefinitionError, error_message
44
- end
45
- end
46
-
47
- private
48
-
49
- def method_is_writer?
50
- !!@method_name_setter_match
51
- end
52
-
53
- def attribute_name
54
- @method_name_setter_match[1]
55
- end
56
-
57
- def error_message
58
- "factory_bot uses '#{attribute_name} value' syntax rather than '#{attribute_name} = value'"
59
- end
60
- end
61
26
  end
62
27
  end
@@ -2,7 +2,7 @@ module FactoryBot
2
2
  # @api private
3
3
  class AttributeAssigner
4
4
  def initialize(evaluator, build_class, &instance_builder)
5
- @build_class = build_class
5
+ @build_class = build_class
6
6
  @instance_builder = instance_builder
7
7
  @evaluator = evaluator
8
8
  @attribute_list = evaluator.class.attribute_list
@@ -22,7 +22,7 @@ module FactoryBot
22
22
  def hash
23
23
  @evaluator.instance = build_hash
24
24
 
25
- attributes_to_set_on_hash.inject({}) do |result, attribute|
25
+ attributes_to_set_on_hash.reduce({}) do |result, attribute|
26
26
  result[attribute] = get(attribute)
27
27
  result
28
28
  end
@@ -31,12 +31,15 @@ module FactoryBot
31
31
  private
32
32
 
33
33
  def method_tracking_evaluator
34
- @method_tracking_evaluator ||= Decorator::AttributeHash.new(decorated_evaluator, attribute_names_to_assign)
34
+ @method_tracking_evaluator ||= Decorator::AttributeHash.new(
35
+ decorated_evaluator,
36
+ attribute_names_to_assign,
37
+ )
35
38
  end
36
39
 
37
40
  def decorated_evaluator
38
41
  Decorator::InvocationTracker.new(
39
- Decorator::NewConstructor.new(@evaluator, @build_class)
42
+ Decorator::NewConstructor.new(@evaluator, @build_class),
40
43
  )
41
44
  end
42
45
 
@@ -65,7 +68,11 @@ module FactoryBot
65
68
  end
66
69
 
67
70
  def attribute_names_to_assign
68
- @attribute_names_to_assign ||= non_ignored_attribute_names + override_names - ignored_attribute_names - alias_names_to_ignore
71
+ @attribute_names_to_assign ||=
72
+ non_ignored_attribute_names +
73
+ override_names -
74
+ ignored_attribute_names -
75
+ alias_names_to_ignore
69
76
  end
70
77
 
71
78
  def non_ignored_attribute_names
@@ -90,8 +97,16 @@ module FactoryBot
90
97
 
91
98
  def alias_names_to_ignore
92
99
  @attribute_list.non_ignored.flat_map do |attribute|
93
- override_names.map { |override| attribute.name if attribute.alias_for?(override) && attribute.name != override && !ignored_attribute_names.include?(override) }
100
+ override_names.map do |override|
101
+ attribute.name if ignorable_alias?(attribute, override)
102
+ end
94
103
  end.compact
95
104
  end
105
+
106
+ def ignorable_alias?(attribute, override)
107
+ attribute.alias_for?(override) &&
108
+ attribute.name != override &&
109
+ !ignored_attribute_names.include?(override)
110
+ end
96
111
  end
97
112
  end
@@ -54,7 +54,8 @@ module FactoryBot
54
54
 
55
55
  def ensure_attribute_not_self_referencing!(attribute)
56
56
  if attribute.respond_to?(:factory) && attribute.factory == @name
57
- raise AssociationDefinitionError, "Self-referencing association '#{attribute.name}' in '#{attribute.factory}'"
57
+ message = "Self-referencing association '#{attribute.name}' in '#{attribute.factory}'"
58
+ raise AssociationDefinitionError, message
58
59
  end
59
60
  end
60
61
 
@@ -22,14 +22,15 @@ module FactoryBot
22
22
  end
23
23
 
24
24
  protected
25
+
25
26
  attr_reader :block
26
27
 
27
28
  private
28
29
 
29
30
  def ensure_valid_callback_name!
30
- unless FactoryBot.callback_names.include?(name)
31
+ unless FactoryBot::Internal.callback_names.include?(name)
31
32
  raise InvalidCallbackNameError, "#{name} is not a valid callback name. " +
32
- "Valid callback names are #{FactoryBot.callback_names.inspect}"
33
+ "Valid callback names are #{FactoryBot::Internal.callback_names.inspect}"
33
34
  end
34
35
  end
35
36
 
@@ -1,37 +1,33 @@
1
1
  module FactoryBot
2
2
  # @api private
3
3
  class Configuration
4
- attr_reader :factories, :sequences, :traits, :strategies, :callback_names
5
-
6
- attr_accessor :allow_class_lookup, :use_parent_strategy
4
+ attr_reader(
5
+ :callback_names,
6
+ :factories,
7
+ :inline_sequences,
8
+ :sequences,
9
+ :strategies,
10
+ :traits,
11
+ )
7
12
 
8
13
  def initialize
9
- @factories = Decorator::DisallowsDuplicatesRegistry.new(Registry.new('Factory'))
10
- @sequences = Decorator::DisallowsDuplicatesRegistry.new(Registry.new('Sequence'))
11
- @traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new('Trait'))
12
- @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")
13
18
  @callback_names = Set.new
14
- @definition = Definition.new
15
-
16
- @allow_class_lookup = true
19
+ @definition = Definition.new(:configuration)
20
+ @inline_sequences = []
17
21
 
18
- to_create { |instance| instance.save! }
22
+ to_create(&:save!)
19
23
  initialize_with { new }
20
24
  end
21
25
 
22
26
  delegate :to_create, :skip_create, :constructor, :before, :after,
23
- :callback, :callbacks, to: :@definition
27
+ :callback, :callbacks, to: :@definition
24
28
 
25
29
  def initialize_with(&block)
26
30
  @definition.define_constructor(&block)
27
31
  end
28
-
29
- def duplicate_attribute_assignment_from_initialize_with
30
- false
31
- end
32
-
33
- def duplicate_attribute_assignment_from_initialize_with=(value)
34
- ActiveSupport::Deprecation.warn 'Assignment of duplicate_attribute_assignment_from_initialize_with is unnecessary as this is now default behavior in FactoryBot 4.0; this line can be removed', caller
35
- end
36
32
  end
37
33
  end
@@ -10,19 +10,32 @@ module FactoryBot
10
10
  end
11
11
 
12
12
  def ==(other)
13
- name == other.name &&
13
+ self.class == other.class &&
14
+ name == other.name &&
14
15
  options == other.options
15
16
  end
16
17
 
17
18
  protected
19
+
18
20
  attr_reader :options
19
21
 
20
22
  private
21
23
 
22
24
  def build
25
+ ensure_factory_is_not_a_declaration!
26
+
23
27
  factory_name = @overrides[:factory] || name
24
28
  [Attribute::Association.new(name, factory_name, [@traits, @overrides.except(:factory)].flatten)]
25
29
  end
30
+
31
+ def ensure_factory_is_not_a_declaration!
32
+ if @overrides[:factory].is_a?(Declaration)
33
+ raise ArgumentError.new(<<~MSG)
34
+ Association '#{name}' received an invalid factory argument.
35
+ Did you mean? 'factory: :#{@overrides[:factory].name}'
36
+ MSG
37
+ end
38
+ end
26
39
  end
27
40
  end
28
41
  end
@@ -8,12 +8,14 @@ module FactoryBot
8
8
  end
9
9
 
10
10
  def ==(other)
11
- name == other.name &&
11
+ self.class == other.class &&
12
+ name == other.name &&
12
13
  ignored == other.ignored &&
13
14
  block == other.block
14
15
  end
15
16
 
16
17
  protected
18
+
17
19
  attr_reader :block
18
20
 
19
21
  private
@@ -8,12 +8,14 @@ module FactoryBot
8
8
  end
9
9
 
10
10
  def ==(other)
11
- name == other.name &&
11
+ self.class == other.class &&
12
+ name == other.name &&
12
13
  factory == other.factory &&
13
14
  ignored == other.ignored
14
15
  end
15
16
 
16
17
  protected
18
+
17
19
  attr_reader :factory
18
20
 
19
21
  private
@@ -21,8 +23,11 @@ module FactoryBot
21
23
  def build
22
24
  if FactoryBot.factories.registered?(name)
23
25
  [Attribute::Association.new(name, name, {})]
24
- elsif FactoryBot.sequences.registered?(name)
26
+ elsif FactoryBot::Internal.sequences.registered?(name)
25
27
  [Attribute::Sequence.new(name, name, @ignored)]
28
+ elsif @factory.name.to_s == name.to_s
29
+ message = "Self-referencing trait '#{@name}'"
30
+ raise TraitDefinitionError, message
26
31
  else
27
32
  @factory.inherit_traits([name])
28
33
  []
@@ -1,7 +1,6 @@
1
- require 'factory_bot/declaration/static'
2
- require 'factory_bot/declaration/dynamic'
3
- require 'factory_bot/declaration/association'
4
- require 'factory_bot/declaration/implicit'
1
+ require "factory_bot/declaration/dynamic"
2
+ require "factory_bot/declaration/association"
3
+ require "factory_bot/declaration/implicit"
5
4
 
6
5
  module FactoryBot
7
6
  # @api private
@@ -18,6 +17,7 @@ module FactoryBot
18
17
  end
19
18
 
20
19
  protected
20
+
21
21
  attr_reader :ignored
22
22
  end
23
23
  end
@@ -39,7 +39,7 @@ module FactoryBot
39
39
  end
40
40
 
41
41
  def to_attributes
42
- @declarations.inject([]) { |result, declaration| result += declaration.to_attributes }
42
+ @declarations.reduce([]) { |result, declaration| result + declaration.to_attributes }
43
43
  end
44
44
 
45
45
  def overridable?
@@ -8,7 +8,7 @@ module FactoryBot
8
8
 
9
9
  def attributes
10
10
  @attributes.each_with_object({}) do |attribute_name, result|
11
- result[attribute_name] = send(attribute_name)
11
+ result[attribute_name] = @component.send(attribute_name)
12
12
  end
13
13
  end
14
14
  end
@@ -6,7 +6,7 @@ module FactoryBot
6
6
  @invoked_methods = []
7
7
  end
8
8
 
9
- def method_missing(name, *args, &block)
9
+ def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing
10
10
  @invoked_methods << name
11
11
  super
12
12
  end
@@ -6,10 +6,14 @@ module FactoryBot
6
6
  @component = component
7
7
  end
8
8
 
9
- def method_missing(name, *args, &block)
9
+ def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissingSuper
10
10
  @component.send(name, *args, &block)
11
11
  end
12
12
 
13
+ def respond_to_missing?(name, include_private = false)
14
+ @component.respond_to?(name, true) || super
15
+ end
16
+
13
17
  def send(symbol, *args, &block)
14
18
  __send__(symbol, *args, &block)
15
19
  end
@@ -1,9 +1,10 @@
1
1
  module FactoryBot
2
2
  # @api private
3
3
  class Definition
4
- attr_reader :defined_traits, :declarations
4
+ attr_reader :defined_traits, :declarations, :name
5
5
 
6
- def initialize(name = nil, base_traits = [])
6
+ def initialize(name, base_traits = [])
7
+ @name = name
7
8
  @declarations = DeclarationList.new(name)
8
9
  @callbacks = []
9
10
  @defined_traits = Set.new
@@ -48,7 +49,7 @@ module FactoryBot
48
49
 
49
50
  defined_traits.each do |defined_trait|
50
51
  base_traits.each { |bt| bt.define_trait defined_trait }
51
- additional_traits.each { |bt| bt.define_trait defined_trait }
52
+ additional_traits.each { |at| at.define_trait defined_trait }
52
53
  end
53
54
 
54
55
  @compiled = true
@@ -73,7 +74,7 @@ module FactoryBot
73
74
  end
74
75
 
75
76
  def skip_create
76
- @to_create = ->(instance) { }
77
+ @to_create = ->(instance) {}
77
78
  end
78
79
 
79
80
  def define_trait(trait)
@@ -94,7 +95,7 @@ module FactoryBot
94
95
 
95
96
  def callback(*names, &block)
96
97
  names.each do |name|
97
- FactoryBot.register_callback(name)
98
+ FactoryBot::Internal.register_callback(name)
98
99
  add_callback(Callback.new(name, block))
99
100
  end
100
101
  end
@@ -110,17 +111,19 @@ module FactoryBot
110
111
  end
111
112
 
112
113
  def trait_by_name(name)
113
- trait_for(name) || FactoryBot.trait_by_name(name)
114
+ trait_for(name) || Internal.trait_by_name(name)
114
115
  end
115
116
 
116
117
  def trait_for(name)
117
- defined_traits.detect { |trait| trait.name == name }
118
+ @defined_traits_by_name ||= defined_traits.each_with_object({}) { |t, memo| memo[t.name] ||= t }
119
+ @defined_traits_by_name[name.to_s]
118
120
  end
119
121
 
120
122
  def initialize_copy(source)
121
123
  super
122
124
  @attributes = nil
123
125
  @compiled = false
126
+ @defined_traits_by_name = nil
124
127
  end
125
128
 
126
129
  def aggregate_from_traits_and_self(method_name, &block)
@@ -1,16 +1,6 @@
1
1
  module FactoryBot
2
2
  class DefinitionHierarchy
3
- def callbacks
4
- FactoryBot.callbacks
5
- end
6
-
7
- def constructor
8
- FactoryBot.constructor
9
- end
10
-
11
- def to_create
12
- FactoryBot.to_create
13
- end
3
+ delegate :callbacks, :constructor, :to_create, to: Internal
14
4
 
15
5
  def self.build_from_definition(definition)
16
6
  build_to_create(&definition.to_create)