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.
- checksums.yaml +4 -4
- data/GETTING_STARTED.md +121 -37
- data/LICENSE +1 -1
- data/NEWS.md +351 -0
- data/README.md +18 -22
- data/lib/factory_bot/aliases.rb +1 -1
- data/lib/factory_bot/attribute/dynamic.rb +1 -0
- data/lib/factory_bot/attribute.rb +4 -39
- data/lib/factory_bot/attribute_assigner.rb +21 -6
- data/lib/factory_bot/attribute_list.rb +2 -1
- data/lib/factory_bot/callback.rb +3 -2
- data/lib/factory_bot/configuration.rb +16 -20
- data/lib/factory_bot/declaration/association.rb +14 -1
- data/lib/factory_bot/declaration/dynamic.rb +3 -1
- data/lib/factory_bot/declaration/implicit.rb +7 -2
- data/lib/factory_bot/declaration.rb +4 -4
- data/lib/factory_bot/declaration_list.rb +1 -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/decorator.rb +5 -1
- data/lib/factory_bot/definition.rb +10 -7
- data/lib/factory_bot/definition_hierarchy.rb +1 -11
- data/lib/factory_bot/definition_proxy.rb +59 -62
- data/lib/factory_bot/errors.rb +7 -4
- data/lib/factory_bot/evaluation.rb +1 -1
- data/lib/factory_bot/evaluator.rb +5 -5
- data/lib/factory_bot/factory.rb +8 -8
- data/lib/factory_bot/factory_runner.rb +3 -3
- data/lib/factory_bot/find_definitions.rb +1 -1
- data/lib/factory_bot/internal.rb +104 -0
- data/lib/factory_bot/linter.rb +36 -19
- data/lib/factory_bot/null_factory.rb +4 -1
- data/lib/factory_bot/null_object.rb +2 -2
- data/lib/factory_bot/registry.rb +15 -6
- data/lib/factory_bot/reload.rb +3 -3
- data/lib/factory_bot/sequence.rb +0 -1
- data/lib/factory_bot/strategy/null.rb +2 -4
- data/lib/factory_bot/strategy/stub.rb +32 -31
- data/lib/factory_bot/strategy_calculator.rb +1 -1
- data/lib/factory_bot/strategy_syntax_method_registrar.rb +13 -2
- data/lib/factory_bot/syntax/default.rb +12 -24
- data/lib/factory_bot/syntax/methods.rb +32 -9
- data/lib/factory_bot/syntax.rb +2 -2
- data/lib/factory_bot/trait.rb +6 -3
- data/lib/factory_bot/version.rb +1 -1
- data/lib/factory_bot.rb +103 -138
- metadata +72 -32
- data/NEWS +0 -306
- 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/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?]
|
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-
|
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
|
-
|
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
|
-
[
|
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
|
83
|
-
|
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-
|
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]
|
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://
|
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.
|
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
|
@@ -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
|
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.
|
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
@@ -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
|
5
|
-
|
6
|
-
|
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(
|
10
|
-
@sequences = Decorator::DisallowsDuplicatesRegistry.new(Registry.new(
|
11
|
-
@traits = Decorator::DisallowsDuplicatesRegistry.new(Registry.new(
|
12
|
-
@strategies = Registry.new(
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
@@ -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
|
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 { |
|
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) ||
|
114
|
+
trait_for(name) || Internal.trait_by_name(name)
|
114
115
|
end
|
115
116
|
|
116
117
|
def trait_for(name)
|
117
|
-
defined_traits.
|
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
|
-
|
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)
|