factory_bot 4.11.1 → 5.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|