factory_bot 4.11.1 → 5.0.0.rc1
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.
- checksums.yaml +4 -4
- data/GETTING_STARTED.md +105 -29
- data/NEWS +17 -0
- data/README.md +3 -14
- data/lib/factory_bot.rb +56 -56
- data/lib/factory_bot/aliases.rb +1 -1
- data/lib/factory_bot/attribute.rb +4 -39
- data/lib/factory_bot/attribute_assigner.rb +20 -5
- data/lib/factory_bot/attribute_list.rb +2 -1
- data/lib/factory_bot/callback.rb +1 -0
- data/lib/factory_bot/configuration.rb +6 -18
- data/lib/factory_bot/declaration.rb +4 -4
- data/lib/factory_bot/declaration/association.rb +3 -1
- data/lib/factory_bot/declaration/dynamic.rb +3 -1
- data/lib/factory_bot/declaration/implicit.rb +3 -1
- data/lib/factory_bot/declaration_list.rb +1 -1
- data/lib/factory_bot/decorator.rb +5 -1
- data/lib/factory_bot/decorator/attribute_hash.rb +1 -1
- data/lib/factory_bot/decorator/invocation_tracker.rb +1 -1
- data/lib/factory_bot/definition.rb +4 -3
- data/lib/factory_bot/definition_proxy.rb +53 -62
- data/lib/factory_bot/errors.rb +4 -4
- data/lib/factory_bot/evaluation.rb +1 -1
- data/lib/factory_bot/evaluator.rb +4 -4
- data/lib/factory_bot/factory.rb +7 -7
- data/lib/factory_bot/factory_runner.rb +3 -3
- data/lib/factory_bot/find_definitions.rb +1 -1
- data/lib/factory_bot/linter.rb +35 -18
- data/lib/factory_bot/null_factory.rb +3 -0
- data/lib/factory_bot/null_object.rb +2 -2
- data/lib/factory_bot/registry.rb +15 -6
- data/lib/factory_bot/sequence.rb +0 -1
- data/lib/factory_bot/strategy/null.rb +2 -4
- data/lib/factory_bot/strategy/stub.rb +23 -29
- data/lib/factory_bot/strategy_syntax_method_registrar.rb +2 -2
- data/lib/factory_bot/syntax.rb +2 -2
- data/lib/factory_bot/syntax/default.rb +1 -1
- data/lib/factory_bot/trait.rb +2 -1
- data/lib/factory_bot/version.rb +1 -1
- metadata +68 -29
- data/lib/factory_bot/attribute/static.rb +0 -16
- data/lib/factory_bot/declaration/static.rb +0 -26
- data/lib/factory_bot/decorator/class_key_hash.rb +0 -28
data/lib/factory_bot/aliases.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
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
|
@@ -22,7 +22,7 @@ module FactoryBot
|
|
22
22
|
def hash
|
23
23
|
@evaluator.instance = build_hash
|
24
24
|
|
25
|
-
attributes_to_set_on_hash.
|
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(
|
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 ||=
|
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
|
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
|
-
|
57
|
+
message = "Self-referencing association '#{attribute.name}' in '#{attribute.factory}'"
|
58
|
+
raise AssociationDefinitionError, message
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
data/lib/factory_bot/callback.rb
CHANGED
@@ -3,19 +3,15 @@ module FactoryBot
|
|
3
3
|
class Configuration
|
4
4
|
attr_reader :factories, :sequences, :traits, :strategies, :callback_names
|
5
5
|
|
6
|
-
attr_accessor :allow_class_lookup, :use_parent_strategy
|
7
|
-
|
8
6
|
def initialize
|
9
|
-
@factories = Decorator::DisallowsDuplicatesRegistry.new(Registry.new(
|
10
|
-
@sequences = Decorator::DisallowsDuplicatesRegistry.new(Registry.new(
|
11
|
-
@traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new(
|
12
|
-
@strategies = Registry.new(
|
7
|
+
@factories = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Factory"))
|
8
|
+
@sequences = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Sequence"))
|
9
|
+
@traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new("Trait"))
|
10
|
+
@strategies = Registry.new("Strategy")
|
13
11
|
@callback_names = Set.new
|
14
|
-
@definition = Definition.new
|
15
|
-
|
16
|
-
@allow_class_lookup = true
|
12
|
+
@definition = Definition.new(:configuration)
|
17
13
|
|
18
|
-
to_create
|
14
|
+
to_create(&:save!)
|
19
15
|
initialize_with { new }
|
20
16
|
end
|
21
17
|
|
@@ -25,13 +21,5 @@ module FactoryBot
|
|
25
21
|
def initialize_with(&block)
|
26
22
|
@definition.define_constructor(&block)
|
27
23
|
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
24
|
end
|
37
25
|
end
|
@@ -1,7 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
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
|
@@ -8,12 +8,14 @@ module FactoryBot
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def ==(other)
|
11
|
-
|
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
|
@@ -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/MethodMissing
|
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
|
6
|
+
def initialize(name, base_traits = [])
|
7
|
+
@name = name
|
7
8
|
@declarations = DeclarationList.new(name)
|
8
9
|
@callbacks = []
|
9
10
|
@defined_traits = Set.new
|
@@ -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)
|
@@ -1,6 +1,19 @@
|
|
1
1
|
module FactoryBot
|
2
2
|
class DefinitionProxy
|
3
|
-
UNPROXIED_METHODS = %w(
|
3
|
+
UNPROXIED_METHODS = %w(
|
4
|
+
__send__
|
5
|
+
__id__
|
6
|
+
nil?
|
7
|
+
send
|
8
|
+
object_id
|
9
|
+
extend
|
10
|
+
instance_eval
|
11
|
+
initialize
|
12
|
+
block_given?
|
13
|
+
raise
|
14
|
+
caller
|
15
|
+
method
|
16
|
+
).freeze
|
4
17
|
|
5
18
|
(instance_methods + private_instance_methods).each do |method|
|
6
19
|
undef_method(method) unless UNPROXIED_METHODS.include?(method.to_s)
|
@@ -21,43 +34,21 @@ module FactoryBot
|
|
21
34
|
raise FactoryBot::MethodDefinitionError, message
|
22
35
|
end
|
23
36
|
|
24
|
-
# Adds an attribute
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# called with a block, the attribute will be generated "lazily," whenever an
|
29
|
-
# instance is generated. Lazy attribute blocks will not be called if that
|
37
|
+
# Adds an attribute to the factory.
|
38
|
+
# The attribute value will be generated "lazily"
|
39
|
+
# by calling the block whenever an instance is generated.
|
40
|
+
# The block will not be called if the
|
30
41
|
# attribute is overridden for a specific instance.
|
31
42
|
#
|
32
|
-
# When defining lazy attributes, an instance of FactoryBot::Strategy will
|
33
|
-
# be yielded, allowing associations to be built using the correct build
|
34
|
-
# strategy.
|
35
|
-
#
|
36
43
|
# Arguments:
|
37
44
|
# * name: +Symbol+ or +String+
|
38
45
|
# The name of this attribute. This will be assigned using "name=" for
|
39
46
|
# generated instances.
|
40
|
-
|
41
|
-
|
42
|
-
def add_attribute(name, value = nil, &block)
|
43
|
-
raise AttributeDefinitionError, 'Both value and block given' if value && block_given?
|
44
|
-
|
45
|
-
declaration = if block_given?
|
46
|
-
Declaration::Dynamic.new(name, @ignore, block)
|
47
|
-
else
|
48
|
-
warn_static_attribute_deprecation(name, value)
|
49
|
-
Declaration::Static.new(name, value, @ignore)
|
50
|
-
end
|
51
|
-
|
47
|
+
def add_attribute(name, &block)
|
48
|
+
declaration = Declaration::Dynamic.new(name, @ignore, block)
|
52
49
|
@definition.declare_attribute(declaration)
|
53
50
|
end
|
54
51
|
|
55
|
-
def ignore(&block)
|
56
|
-
ActiveSupport::Deprecation.warn "`#ignore` is deprecated and will be "\
|
57
|
-
"removed in 5.0. Please use `#transient` instead."
|
58
|
-
transient(&block)
|
59
|
-
end
|
60
|
-
|
61
52
|
def transient(&block)
|
62
53
|
proxy = DefinitionProxy.new(@definition, true)
|
63
54
|
proxy.instance_eval(&block)
|
@@ -67,40 +58,45 @@ module FactoryBot
|
|
67
58
|
# attribute, so that:
|
68
59
|
#
|
69
60
|
# factory :user do
|
70
|
-
# name 'Billy Idol'
|
61
|
+
# name { 'Billy Idol' }
|
71
62
|
# end
|
72
63
|
#
|
73
64
|
# and:
|
74
65
|
#
|
75
66
|
# factory :user do
|
76
|
-
# add_attribute
|
67
|
+
# add_attribute(:name) { 'Billy Idol' }
|
77
68
|
# end
|
78
69
|
#
|
79
70
|
# are equivalent.
|
80
71
|
#
|
81
|
-
# If no argument or block is given, factory_bot will look for
|
82
|
-
#
|
72
|
+
# If no argument or block is given, factory_bot will first look for an
|
73
|
+
# association, then for a sequence, and finally for a trait with the same
|
74
|
+
# name. This means that given an "admin" trait, an "email" sequence, and an
|
75
|
+
# "account" factory:
|
83
76
|
#
|
84
|
-
# factory :user do
|
85
|
-
# email {
|
77
|
+
# factory :user, traits: [:admin] do
|
78
|
+
# email { generate(:email) }
|
86
79
|
# association :account
|
87
80
|
# end
|
88
81
|
#
|
89
82
|
# and:
|
90
83
|
#
|
91
84
|
# factory :user do
|
85
|
+
# admin
|
92
86
|
# email
|
93
87
|
# account
|
94
88
|
# end
|
95
89
|
#
|
96
90
|
# are equivalent.
|
97
|
-
def method_missing(name, *args, &block)
|
98
|
-
if args.empty?
|
99
|
-
|
91
|
+
def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissing
|
92
|
+
if args.empty?
|
93
|
+
__declare_attribute__(name, block)
|
100
94
|
elsif args.first.respond_to?(:has_key?) && args.first.has_key?(:factory)
|
101
95
|
association(name, *args)
|
102
96
|
else
|
103
|
-
|
97
|
+
raise NoMethodError.new(
|
98
|
+
"undefined method '#{name}' in '#{@definition.name}' factory",
|
99
|
+
)
|
104
100
|
end
|
105
101
|
end
|
106
102
|
|
@@ -121,7 +117,9 @@ module FactoryBot
|
|
121
117
|
#
|
122
118
|
# Except that no globally available sequence will be defined.
|
123
119
|
def sequence(name, *args, &block)
|
124
|
-
|
120
|
+
sequence_name = "__#{@definition.name}_#{name}__"
|
121
|
+
sequence = Sequence.new(sequence_name, *args, &block)
|
122
|
+
FactoryBot.register_sequence(sequence)
|
125
123
|
add_attribute(name) { increment_sequence(sequence) }
|
126
124
|
end
|
127
125
|
|
@@ -149,7 +147,15 @@ module FactoryBot
|
|
149
147
|
# name of the factory. For example, a "user" association will by
|
150
148
|
# default use the "user" factory.
|
151
149
|
def association(name, *options)
|
152
|
-
|
150
|
+
if block_given?
|
151
|
+
raise AssociationDefinitionError.new(
|
152
|
+
"Unexpected block passed to '#{name}' association "\
|
153
|
+
"in '#{@definition.name}' factory",
|
154
|
+
)
|
155
|
+
else
|
156
|
+
declaration = Declaration::Association.new(name, *options)
|
157
|
+
@definition.declare_attribute(declaration)
|
158
|
+
end
|
153
159
|
end
|
154
160
|
|
155
161
|
def to_create(&block)
|
@@ -174,28 +180,13 @@ module FactoryBot
|
|
174
180
|
|
175
181
|
private
|
176
182
|
|
177
|
-
def
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
183
|
+
def __declare_attribute__(name, block)
|
184
|
+
if block.nil?
|
185
|
+
declaration = Declaration::Implicit.new(name, @definition, @ignore)
|
186
|
+
@definition.declare_attribute(declaration)
|
187
|
+
else
|
188
|
+
add_attribute(name, &block)
|
182
189
|
end
|
183
|
-
|
184
|
-
ActiveSupport::Deprecation.warn(<<-MSG, attribute_caller)
|
185
|
-
Static attributes will be removed in FactoryBot 5.0. Please use dynamic
|
186
|
-
attributes instead by wrapping the attribute value in a block:
|
187
|
-
|
188
|
-
#{name} { #{value.inspect} }
|
189
|
-
|
190
|
-
To automatically update from static attributes to dynamic ones,
|
191
|
-
install rubocop-rspec and run:
|
192
|
-
|
193
|
-
rubocop \\
|
194
|
-
--require rubocop-rspec \\
|
195
|
-
--only FactoryBot/AttributeDefinedStatically \\
|
196
|
-
--auto-correct
|
197
|
-
|
198
|
-
MSG
|
199
190
|
end
|
200
191
|
end
|
201
192
|
end
|