factory_bot 5.0.0 → 5.1.2

Sign up to get free protection for your applications and to get access to all the features.
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?
@@ -59,9 +59,13 @@ More Information
59
59
  * [Issues](https://github.com/thoughtbot/factory_bot/issues)
60
60
  * [GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS](https://robots.thoughtbot.com/)
61
61
 
62
- 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
63
64
 
64
- [GETTING_STARTED]: https://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md
65
+ Useful Tools
66
+ ------------
67
+
68
+ * [FactoryTrace](https://github.com/djezzzl/factory_trace) - helps to find unused factories and traits.
65
69
 
66
70
  Contributing
67
71
  ------------
@@ -75,14 +79,17 @@ community](https://github.com/thoughtbot/factory_bot/graphs/contributors).
75
79
  License
76
80
  -------
77
81
 
78
- factory_bot is Copyright © 2008-2016 Joe Ferris and thoughtbot. It is free
82
+ factory_bot is Copyright © 2008-2019 Joe Ferris and thoughtbot. It is free
79
83
  software, and may be redistributed under the terms specified in the
80
- [LICENSE](/LICENSE) file.
84
+ [LICENSE] file.
85
+
86
+ [LICENSE]: https://github.com/thoughtbot/factory_bot/blob/master/LICENSE
87
+
81
88
 
82
89
  About thoughtbot
83
90
  ----------------
84
91
 
85
- ![thoughtbot](https://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg)
92
+ ![thoughtbot](https://thoughtbot.com/brand_assets/93:44.svg)
86
93
 
87
94
  factory_bot is maintained and funded by thoughtbot, inc.
88
95
  The names and logos for thoughtbot are trademarks of thoughtbot, inc.
@@ -45,16 +45,17 @@ require "factory_bot/decorator/invocation_tracker"
45
45
  require "factory_bot/decorator/new_constructor"
46
46
  require "factory_bot/linter"
47
47
  require "factory_bot/version"
48
+ require "factory_bot/internal"
48
49
 
49
50
  module FactoryBot
50
- DEPRECATOR = ActiveSupport::Deprecation.new("6.0", "factory_bot")
51
+ Deprecation = ActiveSupport::Deprecation.new("6.0", "factory_bot")
51
52
 
52
53
  def self.configuration
53
- @configuration ||= Configuration.new
54
+ Internal.configuration
54
55
  end
55
56
 
56
57
  def self.reset_configuration
57
- @configuration = nil
58
+ Internal.reset_configuration
58
59
  end
59
60
 
60
61
  mattr_accessor :use_parent_strategy, instance_accessor: false
@@ -70,92 +71,55 @@ module FactoryBot
70
71
  def self.lint(*args)
71
72
  options = args.extract_options!
72
73
  factories_to_lint = args[0] || FactoryBot.factories
73
- Linter.new(factories_to_lint, options).lint!
74
+ Linter.new(factories_to_lint, **options).lint!
74
75
  end
75
76
 
76
77
  class << self
77
- delegate :factories,
78
+ delegate :callbacks,
79
+ :callback_names,
80
+ :constructor,
81
+ :factories,
82
+ :initialize_with,
78
83
  :sequences,
79
- :traits,
80
- :callbacks,
84
+ :skip_create,
81
85
  :strategies,
82
- :callback_names,
83
86
  :to_create,
84
- :skip_create,
85
- :initialize_with,
86
- :constructor,
87
+ :traits,
87
88
  to: :configuration
88
89
 
89
- attr_accessor :allow_class_lookup
90
- deprecate :allow_class_lookup, :allow_class_lookup=, deprecator: DEPRECATOR
91
- end
92
-
93
- def self.register_factory(factory)
94
- factory.names.each do |name|
95
- factories.register(name, factory)
96
- end
97
- factory
98
- end
99
-
100
- def self.factory_by_name(name)
101
- factories.find(name)
102
- end
103
-
104
- def self.register_sequence(sequence)
105
- sequence.names.each do |name|
106
- sequences.register(name, sequence)
107
- end
108
- sequence
109
- end
110
-
111
- def self.sequence_by_name(name)
112
- sequences.find(name)
113
- end
114
-
115
- def self.rewind_sequences
116
- sequences.each(&:rewind)
117
- end
118
-
119
- def self.register_trait(trait)
120
- trait.names.each do |name|
121
- traits.register(name, trait)
122
- end
123
- trait
124
- end
125
-
126
- def self.trait_by_name(name)
127
- traits.find(name)
128
- end
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
129
103
 
130
- def self.register_strategy(strategy_name, strategy_class)
131
- strategies.register(strategy_name, strategy_class)
132
- StrategySyntaxMethodRegistrar.new(strategy_name).define_strategy_methods
133
- end
134
-
135
- def self.strategy_by_name(name)
136
- strategies.find(name)
137
- end
138
-
139
- def self.register_default_strategies
140
- register_strategy(:build, FactoryBot::Strategy::Build)
141
- register_strategy(:create, FactoryBot::Strategy::Create)
142
- register_strategy(:attributes_for, FactoryBot::Strategy::AttributesFor)
143
- register_strategy(:build_stubbed, FactoryBot::Strategy::Stub)
144
- register_strategy(:null, FactoryBot::Strategy::Null)
145
- end
146
-
147
- def self.register_default_callbacks
148
- register_callback(:after_build)
149
- register_callback(:after_create)
150
- register_callback(:after_stub)
151
- register_callback(:before_create)
152
- end
104
+ attr_accessor :allow_class_lookup
153
105
 
154
- def self.register_callback(name)
155
- name = name.to_sym
156
- callback_names << name
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
157
121
  end
158
122
  end
159
123
 
160
- FactoryBot.register_default_strategies
161
- FactoryBot.register_default_callbacks
124
+ FactoryBot::Internal.register_default_strategies
125
+ FactoryBot::Internal.register_default_callbacks
@@ -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
@@ -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
@@ -28,9 +28,9 @@ module FactoryBot
28
28
  private
29
29
 
30
30
  def ensure_valid_callback_name!
31
- unless FactoryBot.callback_names.include?(name)
31
+ unless FactoryBot::Internal.callback_names.include?(name)
32
32
  raise InvalidCallbackNameError, "#{name} is not a valid callback name. " +
33
- "Valid callback names are #{FactoryBot.callback_names.inspect}"
33
+ "Valid callback names are #{FactoryBot::Internal.callback_names.inspect}"
34
34
  end
35
35
  end
36
36
 
@@ -1,7 +1,14 @@
1
1
  module FactoryBot
2
2
  # @api private
3
3
  class Configuration
4
- attr_reader :factories, :sequences, :traits, :strategies, :callback_names
4
+ attr_reader(
5
+ :callback_names,
6
+ :factories,
7
+ :inline_sequences,
8
+ :sequences,
9
+ :strategies,
10
+ :traits,
11
+ )
5
12
 
6
13
  def initialize
7
14
  @factories = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Factory"))
@@ -10,13 +17,14 @@ module FactoryBot
10
17
  @strategies = Registry.new("Strategy")
11
18
  @callback_names = Set.new
12
19
  @definition = Definition.new(:configuration)
20
+ @inline_sequences = []
13
21
 
14
22
  to_create(&:save!)
15
23
  initialize_with { new }
16
24
  end
17
25
 
18
26
  delegate :to_create, :skip_create, :constructor, :before, :after,
19
- :callback, :callbacks, to: :@definition
27
+ :callback, :callbacks, to: :@definition
20
28
 
21
29
  def initialize_with(&block)
22
30
  @definition.define_constructor(&block)
@@ -22,9 +22,20 @@ module FactoryBot
22
22
  private
23
23
 
24
24
  def build
25
+ ensure_factory_is_not_a_declaration!
26
+
25
27
  factory_name = @overrides[:factory] || name
26
28
  [Attribute::Association.new(name, factory_name, [@traits, @overrides.except(:factory)].flatten)]
27
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
28
39
  end
29
40
  end
30
41
  end
@@ -23,8 +23,11 @@ module FactoryBot
23
23
  def build
24
24
  if FactoryBot.factories.registered?(name)
25
25
  [Attribute::Association.new(name, name, {})]
26
- elsif FactoryBot.sequences.registered?(name)
26
+ elsif FactoryBot::Internal.sequences.registered?(name)
27
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
28
31
  else
29
32
  @factory.inherit_traits([name])
30
33
  []
@@ -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
@@ -49,7 +49,7 @@ module FactoryBot
49
49
 
50
50
  defined_traits.each do |defined_trait|
51
51
  base_traits.each { |bt| bt.define_trait defined_trait }
52
- additional_traits.each { |bt| bt.define_trait defined_trait }
52
+ additional_traits.each { |at| at.define_trait defined_trait }
53
53
  end
54
54
 
55
55
  @compiled = true
@@ -95,7 +95,7 @@ module FactoryBot
95
95
 
96
96
  def callback(*names, &block)
97
97
  names.each do |name|
98
- FactoryBot.register_callback(name)
98
+ FactoryBot::Internal.register_callback(name)
99
99
  add_callback(Callback.new(name, block))
100
100
  end
101
101
  end
@@ -111,17 +111,19 @@ module FactoryBot
111
111
  end
112
112
 
113
113
  def trait_by_name(name)
114
- trait_for(name) || FactoryBot.trait_by_name(name)
114
+ trait_for(name) || Internal.trait_by_name(name)
115
115
  end
116
116
 
117
117
  def trait_for(name)
118
- 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]
119
120
  end
120
121
 
121
122
  def initialize_copy(source)
122
123
  super
123
124
  @attributes = nil
124
125
  @compiled = false
126
+ @defined_traits_by_name = nil
125
127
  end
126
128
 
127
129
  def aggregate_from_traits_and_self(method_name, &block)
@@ -88,15 +88,18 @@ module FactoryBot
88
88
  # end
89
89
  #
90
90
  # are equivalent.
91
- def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissing
92
- if args.empty?
91
+ def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing, Style/MethodMissingSuper, Metrics/LineLength
92
+ association_options = args.first
93
+
94
+ if association_options.nil?
93
95
  __declare_attribute__(name, block)
94
- elsif args.first.respond_to?(:has_key?) && args.first.has_key?(:factory)
95
- association(name, *args)
96
+ elsif __valid_association_options?(association_options)
97
+ association(name, association_options)
96
98
  else
97
- raise NoMethodError.new(
98
- "undefined method '#{name}' in '#{@definition.name}' factory",
99
- )
99
+ raise NoMethodError.new(<<~MSG)
100
+ undefined method '#{name}' in '#{@definition.name}' factory
101
+ Did you mean? '#{name} { #{association_options.inspect} }'
102
+ MSG
100
103
  end
101
104
  end
102
105
 
@@ -117,9 +120,8 @@ module FactoryBot
117
120
  #
118
121
  # Except that no globally available sequence will be defined.
119
122
  def sequence(name, *args, &block)
120
- sequence_name = "__#{@definition.name}_#{name}__"
121
- sequence = Sequence.new(sequence_name, *args, &block)
122
- FactoryBot.register_sequence(sequence)
123
+ sequence = Sequence.new(name, *args, &block)
124
+ FactoryBot::Internal.register_inline_sequence(sequence)
123
125
  add_attribute(name) { increment_sequence(sequence) }
124
126
  end
125
127
 
@@ -188,5 +190,9 @@ module FactoryBot
188
190
  add_attribute(name, &block)
189
191
  end
190
192
  end
193
+
194
+ def __valid_association_options?(options)
195
+ options.respond_to?(:has_key?) && options.has_key?(:factory)
196
+ end
191
197
  end
192
198
  end
@@ -2,6 +2,9 @@ module FactoryBot
2
2
  # Raised when a factory is defined that attempts to instantiate itself.
3
3
  class AssociationDefinitionError < RuntimeError; end
4
4
 
5
+ # Raised when a trait is defined that references itself.
6
+ class TraitDefinitionError < RuntimeError; end
7
+
5
8
  # Raised when a callback is defined that has an invalid name
6
9
  class InvalidCallbackNameError < RuntimeError; end
7
10
 
@@ -37,7 +37,7 @@ module FactoryBot
37
37
  @instance = object_instance
38
38
  end
39
39
 
40
- def method_missing(method_name, *args, &block) # rubocop:disable Style/MethodMissing
40
+ def method_missing(method_name, *args, &block) # rubocop:disable Style/MethodMissingSuper
41
41
  if @instance.respond_to?(method_name)
42
42
  @instance.send(method_name, *args, &block)
43
43
  else
@@ -66,7 +66,7 @@ module FactoryBot
66
66
  end
67
67
 
68
68
  def self.define_attribute(name, &block)
69
- if method_defined?(name) || private_method_defined?(name)
69
+ if instance_methods(false).include?(name) || private_instance_methods(false).include?(name)
70
70
  undef_method(name)
71
71
  end
72
72
 
@@ -145,7 +145,7 @@ module FactoryBot
145
145
 
146
146
  def parent
147
147
  if @parent
148
- FactoryBot.factory_by_name(@parent)
148
+ FactoryBot::Internal.factory_by_name(@parent)
149
149
  else
150
150
  NullFactory.new
151
151
  end
@@ -5,11 +5,11 @@ module FactoryBot
5
5
  @strategy = strategy
6
6
 
7
7
  @overrides = traits_and_overrides.extract_options!
8
- @traits = traits_and_overrides.map(&:to_sym)
8
+ @traits = traits_and_overrides
9
9
  end
10
10
 
11
11
  def run(runner_strategy = @strategy, &block)
12
- factory = FactoryBot.factory_by_name(@name)
12
+ factory = FactoryBot::Internal.factory_by_name(@name)
13
13
 
14
14
  factory.compile
15
15