factory_bot-with 0.5.0 → 0.6.0
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/.irbrc +8 -0
- data/CHANGELOG.md +5 -0
- data/README.md +91 -77
- data/lib/factory_bot/with/assoc_info.rb +3 -3
- data/lib/factory_bot/with/scoped.rb +39 -0
- data/lib/factory_bot/with/version.rb +1 -1
- data/lib/factory_bot/with.rb +40 -73
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab9e29f43a809164761618a28339cd98f4c26060183045f79dcd52cc107ee3aa
|
4
|
+
data.tar.gz: 2c4814ce52ed94f4120077410519424a19aa8b590a2af571429aadb059f59687
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25a743163e0a1f248d0106534086ee928ecd4b46257985588287dfdf681a03309ea5f4c6db40bf9c5591aad3c2ba78edb80b071752fa992d7935b458b25bbf40
|
7
|
+
data.tar.gz: d01e0dcecfa6b765a7510f581d6197f324cfd464bb0f6c939477f067fb1dd76675c46c898c7d7759b3923477162ef161994cc60f86ddad5f9ae6f2a6be6956a7
|
data/.irbrc
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.6.0] - 2025-04-11
|
4
|
+
|
5
|
+
- Added: Factory methods are now also provided as class methods of `FactoryBot::With`
|
6
|
+
- Fixed: Removed some internal methods from `FactoryBot::With`
|
7
|
+
|
3
8
|
## [0.5.0] - 2025-03-20
|
4
9
|
|
5
10
|
- Added: When using `with` scope syntax, blocks can now take given objects as arguments
|
data/README.md
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
[](https://badge.fury.io/rb/factory_bot-with)
|
4
4
|
[](https://github.com/yubrot/factory_bot-with/actions/workflows/main.yml)
|
5
5
|
|
6
|
-
FactoryBot::With is a FactoryBot extension that
|
6
|
+
FactoryBot::With is a FactoryBot extension that enhances usability by wrapping factory methods.
|
7
7
|
|
8
8
|
[FactoryBot における関連の扱いと、factory_bot-with gem を作った話 (Japanese)](https://zenn.dev/yubrot/articles/032447068e308e)
|
9
9
|
|
10
|
-
For example,
|
10
|
+
For example, given these factories:
|
11
11
|
|
12
12
|
```ruby
|
13
13
|
FactoryBot.define do
|
@@ -26,18 +26,18 @@ create(:blog) do |blog|
|
|
26
26
|
end
|
27
27
|
```
|
28
28
|
|
29
|
-
FactoryBot::With allows you to write:
|
29
|
+
FactoryBot::With allows you to write like this:
|
30
30
|
|
31
31
|
```ruby
|
32
|
-
create.blog
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
create.blog do
|
33
|
+
create.article { create.comment }
|
34
|
+
create.article { create_list.comment(3) }
|
35
|
+
end
|
36
36
|
```
|
37
37
|
|
38
38
|
## Installation
|
39
39
|
|
40
|
-
|
40
|
+
Add the following line to your Gemfile:
|
41
41
|
|
42
42
|
```ruby
|
43
43
|
gem "factory_bot-with"
|
@@ -54,28 +54,29 @@ RSpec.configure do |config|
|
|
54
54
|
end
|
55
55
|
```
|
56
56
|
|
57
|
+
Alternatively, these factory methods are also provided as class methods of `FactoryBot::With`.
|
58
|
+
|
57
59
|
## What differs from `FactoryBot::Syntax::Methods`?
|
58
60
|
|
59
|
-
### Method
|
61
|
+
### Method-style syntax
|
60
62
|
|
61
|
-
FactoryBot::With overrides the behavior
|
63
|
+
FactoryBot::With overrides the behavior of factory methods called without arguments.
|
62
64
|
|
63
65
|
```ruby
|
64
|
-
create(:foo, ...) #
|
66
|
+
create(:foo, ...) # normal usage
|
65
67
|
create # returns a Proxy (an intermediate) object
|
66
68
|
create.foo(...) # is equivalent to create(:foo, ...)
|
67
|
-
```
|
68
69
|
|
69
|
-
This applies to other factory methods
|
70
|
-
|
71
|
-
```ruby
|
70
|
+
# This also applies to other factory methods:
|
72
71
|
build_stubbed.foo(...)
|
73
72
|
create_list.foo(10, ...)
|
74
73
|
```
|
75
74
|
|
76
75
|
### Smarter interpretation of positional arguments
|
77
76
|
|
78
|
-
FactoryBot::With
|
77
|
+
FactoryBot::With allows factory methods to accept `Hash`, `Array`, and falsy values (`false` or `nil`) as positional arguments[^1].
|
78
|
+
|
79
|
+
[^1]: The idea for this behavior came from JavaScript libraries such as [clsx](https://github.com/lukeed/clsx).
|
79
80
|
|
80
81
|
```ruby
|
81
82
|
create.foo({ title: "Recipe" }, is_new && %i[latest hot])
|
@@ -85,54 +86,57 @@ create.foo({ title: "Recipe" }, is_new && %i[latest hot])
|
|
85
86
|
|
86
87
|
### `with`, `with_pair`, and `with_list` operator
|
87
88
|
|
88
|
-
FactoryBot::With
|
89
|
+
FactoryBot::With introduces new operators: `with` (and its family).
|
90
|
+
|
91
|
+
- `with(:factory_name, ...)`
|
92
|
+
- `with_pair(:factory_name, ...)`
|
93
|
+
- `with_list(:factory_name, number_of_items, ...)`
|
94
|
+
|
95
|
+
These operators produce a `With` instance. This instance can be passed as an argument to factory methods such as `build` or `create`:
|
89
96
|
|
90
97
|
```ruby
|
91
|
-
with(
|
92
|
-
with_pair(:factory_name, ...)
|
93
|
-
with_list(:factory_name, number_of_items, ...)
|
98
|
+
create.blog(with.article(with.comment))
|
94
99
|
```
|
95
100
|
|
96
|
-
|
101
|
+
When the factory method is called, it first collects and removes `With` arguments, then delegates the actual object creation to the standard FactoryBot factory method, and finally creates additional objects based on the factory definition. Above example is equivalent to:
|
97
102
|
|
98
103
|
```ruby
|
99
|
-
create
|
100
|
-
|
101
|
-
|
102
|
-
|
104
|
+
_tmp1 = FactoryBot.create(:blog)
|
105
|
+
_tmp2 = FactoryBot.create(:article, blog: _tmp1)
|
106
|
+
_tmp3 = FactoryBot.create(:comment, article: _tmp2)
|
107
|
+
# Here, `blog: _tmp1` and `article: _tmp2` are automatically completed by AAR (described later)
|
103
108
|
```
|
104
109
|
|
105
|
-
The overridden factory methods collect these `with` arguments before delegating object creation to the actual factory methods.
|
106
|
-
|
107
110
|
<details>
|
108
|
-
<summary>Automatic
|
111
|
+
<summary>Automatic Association Resolution (AAR)</summary>
|
109
112
|
|
110
|
-
`with` automatically resolves references to ancestor objects based on the definition
|
113
|
+
`with` automatically resolves references to ancestor objects based on the definition in your FactoryBot factories.
|
111
114
|
|
112
|
-
This automatic resolution takes into account any [traits](https://thoughtbot.github.io/factory_bot/traits/summary.html)
|
115
|
+
This automatic resolution takes into account any [traits](https://thoughtbot.github.io/factory_bot/traits/summary.html), [aliases](https://thoughtbot.github.io/factory_bot/sequences/aliases.html), and [factory specifications](https://thoughtbot.github.io/factory_bot/associations/specifying-the-factory.html) in the definition.
|
113
116
|
|
114
117
|
```ruby
|
115
118
|
FactoryBot.define do
|
116
119
|
factory(:video)
|
117
120
|
factory(:photo)
|
118
121
|
factory(:tag) do
|
122
|
+
# `tag` potentially has an association on `taggable` field. `taggable` is either `video` or `photo`.
|
119
123
|
trait(:for_video) { taggable factory: :video }
|
120
124
|
trait(:for_photo) { taggable factory: :photo }
|
121
125
|
end
|
122
126
|
end
|
123
127
|
|
124
|
-
create.video(with.tag(text: "latest")) # resolved as taggable: video
|
125
|
-
create.photo(with.tag(text: "latest")) #
|
128
|
+
create.video(with.tag(text: "latest")) # resolved as `taggable: <created video object>`
|
129
|
+
create.photo(with.tag(text: "latest")) # resolved as `taggable: <created photo object>`
|
126
130
|
```
|
127
131
|
|
128
|
-
Due to technical limitations, [inline associations](https://thoughtbot.github.io/factory_bot/associations/inline-definition.html)
|
132
|
+
Due to technical limitations, [inline associations](https://thoughtbot.github.io/factory_bot/associations/inline-definition.html) are not taken into account.
|
129
133
|
|
130
134
|
</details>
|
131
135
|
|
132
136
|
<details>
|
133
|
-
<summary>
|
137
|
+
<summary>Factory Name Completion (FNC)</summary>
|
134
138
|
|
135
|
-
For a factory name that is prefixed by the
|
139
|
+
For a factory name that is prefixed by the ancestor object's factory name, the prefix can be omitted.
|
136
140
|
|
137
141
|
```ruby
|
138
142
|
FactoryBot.define do
|
@@ -140,70 +144,80 @@ FactoryBot.define do
|
|
140
144
|
factory(:blog_article) { blog }
|
141
145
|
end
|
142
146
|
|
143
|
-
create.blog(with.article) #
|
147
|
+
create.blog(with.article) # completes to :blog_article
|
144
148
|
```
|
145
149
|
|
146
150
|
</details>
|
147
151
|
|
148
|
-
|
149
|
-
|
150
|
-
### `with` scope syntax for automatic association resolution
|
152
|
+
### Implicit context scope
|
151
153
|
|
152
|
-
|
154
|
+
FactoryBot::With factory methods can accept a block argument, just like standard FactoryBot. However, in FactoryBot::With, nested factory method calls within a block recognize ancestor objects. This means that nested factory method calls perform AAR and FNC in the same way as the `with` operator.
|
153
155
|
|
154
156
|
```ruby
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
create
|
157
|
+
# Instead of writing:
|
158
|
+
create.blog(with.article(with.comment))
|
159
|
+
# You can write:
|
160
|
+
create.blog { create.article { create.comment } }
|
161
|
+
# ^ This works in the same way as:
|
162
|
+
create(:blog) do |blog|
|
163
|
+
create(:article, blog:) do |article|
|
164
|
+
create(:comment, article:)
|
163
165
|
end
|
164
166
|
end
|
165
167
|
```
|
166
168
|
|
167
|
-
The same behavior occurs when a block is passed to factory methods.
|
168
|
-
|
169
|
-
```ruby
|
170
|
-
create.blog do
|
171
|
-
create.article(with.comment)
|
172
|
-
create.article(with_list.comment(3))
|
173
|
-
end
|
174
|
-
```
|
175
|
-
|
176
169
|
<details>
|
177
|
-
<summary>
|
170
|
+
<summary>Incompatible behavior when calling <code>_list</code> or <code>_pair</code> factory methods with a block</summary>
|
178
171
|
|
179
|
-
|
172
|
+
To align the behavior with the `with_list` operator, there is [an incompatible behavior](./lib/factory_bot/with.rb#L121) compared to standard FactoryBot:
|
180
173
|
|
181
174
|
```ruby
|
182
|
-
# This creates a blog with 2 articles, each with
|
183
|
-
#
|
175
|
+
# This code creates a blog with 2 articles, each with a comment in standard FactoryBot:
|
176
|
+
# This does not work in FactoryBot::With!
|
184
177
|
create(:blog) do |blog|
|
185
|
-
create_list(:article, 2, blog:) do |articles| # yielded once with an array of articles
|
178
|
+
create_list(:article, 2, blog:) do |articles| # yielded *once* with an array of articles
|
186
179
|
articles.each { |article| create(:comment, article:) }
|
187
180
|
end
|
188
181
|
end
|
189
182
|
|
190
|
-
#
|
191
|
-
create.blog
|
192
|
-
|
193
|
-
)
|
194
|
-
|
195
|
-
# factory methods with blocks:
|
196
|
-
create.blog do
|
197
|
-
create_list.article(2) { create.comment } # yielded *for each article* in factory_bot-with
|
183
|
+
# In FactoryBot::With, blocks are yielded for each object. So we must write like this:
|
184
|
+
create.blog do |blog|
|
185
|
+
create_list.article(2, blog:) do |article| # yielded *for each article*
|
186
|
+
create.comment(article:)
|
187
|
+
end
|
198
188
|
end
|
199
189
|
|
200
|
-
#
|
201
|
-
blog
|
202
|
-
with(
|
203
|
-
|
190
|
+
# Again, you can simplify this by (1)omitting the block or (2)using the `with` operator:
|
191
|
+
create.blog { create_list.article(2) { create.comment } } # (1)
|
192
|
+
create.blog(with_list.article(2, with.comment)) # (2)
|
193
|
+
```
|
194
|
+
|
195
|
+
If you want to avoid this incompatibility, you can use `Object#tap`.
|
196
|
+
|
197
|
+
</details>
|
198
|
+
|
199
|
+
## Additional features
|
200
|
+
|
201
|
+
### Implicit context scope with existing objects
|
202
|
+
|
203
|
+
By calling `with` without positional arguments, but with keyword arguments that define the relationship between factory names and objects, along with a block, it creates a context scope where those objects become candidates for AAR and FNC.
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
let(:blog) { create.blog }
|
207
|
+
|
208
|
+
before do
|
209
|
+
with(blog:) do
|
210
|
+
# Just like `create.blog { ... }`,
|
211
|
+
# the `blog` object is available for AAR and FNC in the following `create.article` calls:
|
212
|
+
create.article(with.comment)
|
213
|
+
create.article(with_list.comment(3))
|
214
|
+
end
|
204
215
|
end
|
216
|
+
```
|
217
|
+
|
218
|
+
`with_list` works similarly to `with`, except that it accepts arrays as its values:
|
205
219
|
|
206
|
-
|
220
|
+
```ruby
|
207
221
|
blog = create.blog
|
208
222
|
articles = create_list.article(2, blog:)
|
209
223
|
with_list(article: articles) { create.comment } # yielded *for each article*
|
@@ -211,9 +225,9 @@ with_list(article: articles) { create.comment } # yielded *for each article*
|
|
211
225
|
|
212
226
|
</details>
|
213
227
|
|
214
|
-
### `with` as a template
|
228
|
+
### `with` as a factory method call template
|
215
229
|
|
216
|
-
`
|
230
|
+
A `With` instance can also be used as a template for factory method calls.
|
217
231
|
|
218
232
|
Instead of writing:
|
219
233
|
|
@@ -45,7 +45,7 @@ module FactoryBot
|
|
45
45
|
# @param ancestors [Array<Array(AssocInfo, Object)>]
|
46
46
|
# @param partial_factory_name [Symbol]
|
47
47
|
# @return [Symbol]
|
48
|
-
def
|
48
|
+
def perform_factory_name_completion(ancestors, partial_factory_name)
|
49
49
|
ancestors.each do |(ancestor_assoc_info, _)|
|
50
50
|
ancestor_assoc_info.factory_names.each do |ancestor_factory_name|
|
51
51
|
factory_name = :"#{ancestor_factory_name}_#{partial_factory_name}"
|
@@ -53,8 +53,8 @@ module FactoryBot
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
# Attempt to resolve with the
|
57
|
-
# If we want to avoid
|
56
|
+
# Attempt to resolve with the completed names, then attempt to resolve with the original name.
|
57
|
+
# If we want to avoid completion, we should be able to simply use a factory such as build or create.
|
58
58
|
return partial_factory_name if exists?(partial_factory_name)
|
59
59
|
|
60
60
|
raise ArgumentError, "FactoryBot factory #{partial_factory_name} is not defined"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FactoryBot
|
4
|
+
class With
|
5
|
+
# An internal class to implement implicit context scope.
|
6
|
+
class Scoped
|
7
|
+
class << self
|
8
|
+
# @!visibility private
|
9
|
+
# @return [Array<Array(AssocInfo, Object)>, nil]
|
10
|
+
def ancestors = Thread.current[:factory_bot_with_scoped_ancestors]
|
11
|
+
|
12
|
+
# @param ancestors [Array<Array(AssocInfo, Object)>]
|
13
|
+
def with_ancestors(ancestors, &)
|
14
|
+
tmp_ancestors = self.ancestors
|
15
|
+
Thread.current[:factory_bot_with_scoped_ancestors] = [*ancestors, *tmp_ancestors || []]
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
Thread.current[:factory_bot_with_scoped_ancestors] = tmp_ancestors
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!visibility private
|
22
|
+
# @param objects [{Symbol => Object}]
|
23
|
+
def with_objects(objects, &) = with_ancestors(objects.map { [AssocInfo.get(_1), _2] }, &)
|
24
|
+
|
25
|
+
# @!visibility private
|
26
|
+
def block(&block)
|
27
|
+
params = block.parameters
|
28
|
+
if params.any? { %i[req opt rest].include?(_1[0]) }
|
29
|
+
->(objects) { with_objects(objects) { block.call(*objects.values) } }
|
30
|
+
elsif params.any? { %i[keyreq key keyrest].include?(_1[0]) }
|
31
|
+
->(objects) { with_objects(objects) { block.call(**objects) } }
|
32
|
+
else
|
33
|
+
->(objects) { with_objects(objects, &block) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/factory_bot/with.rb
CHANGED
@@ -4,11 +4,14 @@ require "factory_bot"
|
|
4
4
|
require_relative "with/version"
|
5
5
|
require_relative "with/proxy"
|
6
6
|
require_relative "with/assoc_info"
|
7
|
+
require_relative "with/scoped"
|
7
8
|
require_relative "with/methods"
|
8
9
|
|
9
10
|
module FactoryBot
|
10
11
|
# An intermediate state for <code>with</code> operator.
|
11
12
|
class With
|
13
|
+
extend Methods
|
14
|
+
|
12
15
|
# @return [:singular, :pair, :list]
|
13
16
|
attr_reader :variation
|
14
17
|
# @return [Symbol]
|
@@ -23,7 +26,7 @@ module FactoryBot
|
|
23
26
|
attr_reader :block
|
24
27
|
|
25
28
|
# @!visibility private
|
26
|
-
def initialize(variation, factory_name, withes: [], traits: [], attrs: {}, &block)
|
29
|
+
def initialize(variation, factory_name, *, withes: [], traits: [], attrs: {}, &block)
|
27
30
|
raise ArgumentError unless %i[singular pair list].include? variation
|
28
31
|
raise TypeError unless factory_name.is_a? Symbol
|
29
32
|
raise TypeError unless withes.is_a?(Array) && withes.all? { _1.is_a? self.class }
|
@@ -36,6 +39,8 @@ module FactoryBot
|
|
36
39
|
@traits = traits
|
37
40
|
@attrs = attrs
|
38
41
|
@block = block
|
42
|
+
|
43
|
+
collect!(*)
|
39
44
|
end
|
40
45
|
|
41
46
|
# @!visibility private
|
@@ -46,7 +51,7 @@ module FactoryBot
|
|
46
51
|
# @param other [With]
|
47
52
|
# @return [With]
|
48
53
|
def merge(other)
|
49
|
-
raise TypeError, "
|
54
|
+
raise TypeError, "other must be an instance of #{self.class}" unless other.is_a? self.class
|
50
55
|
raise ArgumentError, "other must have the same variation" if other.variation != variation
|
51
56
|
raise ArgumentError, "other must have the same factory_name" if other.factory_name != factory_name
|
52
57
|
|
@@ -71,32 +76,11 @@ module FactoryBot
|
|
71
76
|
)
|
72
77
|
end
|
73
78
|
|
74
|
-
# @!visibility private
|
75
|
-
def extend!(*args)
|
76
|
-
args.each do |arg|
|
77
|
-
case arg
|
78
|
-
when self.class
|
79
|
-
withes << arg
|
80
|
-
when Symbol, Numeric
|
81
|
-
traits << arg
|
82
|
-
when Array
|
83
|
-
extend!(*arg)
|
84
|
-
when Hash
|
85
|
-
attrs.merge!(arg)
|
86
|
-
when false, nil
|
87
|
-
# Ignored. This behavior is useful for conditional arguments like `is_premium && :premium`
|
88
|
-
else
|
89
|
-
raise ArgumentError, "Unsupported type for factory argument: #{arg}"
|
90
|
-
end
|
91
|
-
end
|
92
|
-
self
|
93
|
-
end
|
94
|
-
|
95
79
|
# @!visibility private
|
96
80
|
# @param build_strategy [Symbol]
|
97
81
|
# @param ancestors [Array<Array(AssocInfo, Object)>, nil]
|
98
82
|
# @return [Object]
|
99
|
-
def instantiate(build_strategy, ancestors =
|
83
|
+
def instantiate(build_strategy, ancestors = Scoped.ancestors)
|
100
84
|
return self if build_strategy == :with
|
101
85
|
|
102
86
|
factory_bot_method =
|
@@ -104,7 +88,7 @@ module FactoryBot
|
|
104
88
|
factory_name, attrs =
|
105
89
|
if ancestors
|
106
90
|
attrs = @attrs.dup
|
107
|
-
factory_name = AssocInfo.
|
91
|
+
factory_name = AssocInfo.perform_factory_name_completion(ancestors, @factory_name)
|
108
92
|
AssocInfo.get(factory_name).perform_automatic_association_resolution(ancestors, attrs)
|
109
93
|
[factory_name, attrs]
|
110
94
|
else
|
@@ -120,25 +104,35 @@ module FactoryBot
|
|
120
104
|
withes.each { _1.instantiate(build_strategy, ancestors_for_children) }
|
121
105
|
# We call the block for each parent object. This is an incompatible behavior with FactoryBot!
|
122
106
|
# If you want to avoid this, use `Object#tap` manually.
|
123
|
-
|
107
|
+
Scoped.with_ancestors(ancestors_for_children) { block.call(result) } if block
|
124
108
|
end
|
125
109
|
end
|
126
110
|
|
127
111
|
result
|
128
112
|
end
|
129
113
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
114
|
+
private
|
115
|
+
|
116
|
+
def collect!(*args)
|
117
|
+
args.each do |arg|
|
118
|
+
case arg
|
119
|
+
when self.class
|
120
|
+
withes << arg
|
121
|
+
when Symbol, Numeric
|
122
|
+
traits << arg
|
123
|
+
when Array
|
124
|
+
collect!(*arg)
|
125
|
+
when Hash
|
126
|
+
attrs.merge!(arg)
|
127
|
+
when false, nil
|
128
|
+
# Ignored. This behavior is useful for conditional arguments like `is_premium && :premium`
|
129
|
+
else
|
130
|
+
raise ArgumentError, "Unsupported type for factory argument: #{arg}"
|
131
|
+
end
|
140
132
|
end
|
133
|
+
end
|
141
134
|
|
135
|
+
class << self
|
142
136
|
# If you want to use a custom strategy, call this along with <code>FactoryBot.register_strategy</code>.
|
143
137
|
# @param build_strategy [Symbol]
|
144
138
|
# @example
|
@@ -151,57 +145,30 @@ module FactoryBot
|
|
151
145
|
list: :"#{build_strategy}_list",
|
152
146
|
}.each do |variation, method_name|
|
153
147
|
Methods.define_method(method_name) do |factory = nil, *args, **kwargs, &block|
|
154
|
-
if factory
|
148
|
+
if factory.is_a? With
|
149
|
+
# <__method__>(<with_instance>, ...)
|
150
|
+
factory
|
151
|
+
.merge(With.new(variation, factory.factory_name, *args, attrs: kwargs, &block))
|
152
|
+
.instantiate(build_strategy)
|
153
|
+
elsif factory
|
155
154
|
# <__method__>(<factory_name>, ...)
|
156
|
-
With.
|
155
|
+
With.new(variation, factory, *args, attrs: kwargs, &block).instantiate(build_strategy)
|
157
156
|
elsif args.empty? && kwargs.empty? && !block
|
158
157
|
# <__method__>.<factory_name>(...)
|
159
158
|
Proxy.new(self, __method__)
|
160
159
|
elsif __method__ == :with && args.empty? && !kwargs.empty? && block
|
161
160
|
# with(<factory_name>: <object>, ...) { ... }
|
162
|
-
block
|
163
|
-
With.call_with_scope(kwargs, &block)
|
161
|
+
Scoped.block(&block).call(kwargs)
|
164
162
|
elsif __method__ == :with_list && args.empty? && !kwargs.empty? && block
|
165
163
|
# with_list(<factory_name>: [<object>, ...], ...) { ... }
|
166
|
-
block =
|
167
|
-
kwargs.values.inject(:product).map {
|
164
|
+
block = Scoped.block(&block)
|
165
|
+
kwargs.values.inject(:product).map { block.call(kwargs.keys.zip(_1).to_h) }
|
168
166
|
else
|
169
167
|
raise ArgumentError, "Invalid use of #{__method__}"
|
170
168
|
end
|
171
169
|
end
|
172
170
|
end
|
173
171
|
end
|
174
|
-
|
175
|
-
# @!visibility private
|
176
|
-
# @return [Array<Array(AssocInfo, Object)>, nil]
|
177
|
-
def scoped_ancestors = Thread.current[:factory_bot_with_scoped_ancestors]
|
178
|
-
|
179
|
-
# @param ancestors [Array<Array(AssocInfo, Object)>]
|
180
|
-
def with_scoped_ancestors(ancestors, &)
|
181
|
-
tmp_scoped_ancestors = scoped_ancestors
|
182
|
-
Thread.current[:factory_bot_with_scoped_ancestors] = [*ancestors, *tmp_scoped_ancestors || []]
|
183
|
-
result = yield
|
184
|
-
Thread.current[:factory_bot_with_scoped_ancestors] = tmp_scoped_ancestors
|
185
|
-
result
|
186
|
-
end
|
187
|
-
|
188
|
-
# @!visibility private
|
189
|
-
# @param objects [{Symbol => Object}]
|
190
|
-
def call_with_scope(objects, &block)
|
191
|
-
with_scoped_ancestors(objects.map { [AssocInfo.get(_1), _2] }) { block.call(objects) }
|
192
|
-
end
|
193
|
-
|
194
|
-
# @!visibility private
|
195
|
-
def call_with_scope_adapter(&block)
|
196
|
-
params = block.parameters
|
197
|
-
if params.any? { %i[req opt rest].include?(_1[0]) }
|
198
|
-
->(objects) { block.call(*objects.values) }
|
199
|
-
elsif params.any? { %i[keyreq key keyrest].include?(_1[0]) }
|
200
|
-
->(objects) { block.call(**objects) }
|
201
|
-
else
|
202
|
-
->(_) { block.call }
|
203
|
-
end
|
204
|
-
end
|
205
172
|
end
|
206
173
|
|
207
174
|
%i[build build_stubbed create attributes_for with].each { register_strategy _1 }
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: factory_bot-with
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yubrot
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-04-11 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: factory_bot
|
@@ -23,8 +23,7 @@ dependencies:
|
|
23
23
|
- - "~>"
|
24
24
|
- !ruby/object:Gem::Version
|
25
25
|
version: '6.0'
|
26
|
-
description: FactoryBot extension that
|
27
|
-
a little easier to use
|
26
|
+
description: a FactoryBot extension that enhances usability by wrapping factory methods
|
28
27
|
email:
|
29
28
|
- yubrot@gmail.com
|
30
29
|
executables: []
|
@@ -33,6 +32,7 @@ extra_rdoc_files: []
|
|
33
32
|
files:
|
34
33
|
- ".github/workflows/main.yml"
|
35
34
|
- ".gitignore"
|
35
|
+
- ".irbrc"
|
36
36
|
- ".solargraph.yml"
|
37
37
|
- CHANGELOG.md
|
38
38
|
- CODE_OF_CONDUCT.md
|
@@ -42,6 +42,7 @@ files:
|
|
42
42
|
- lib/factory_bot/with/assoc_info.rb
|
43
43
|
- lib/factory_bot/with/methods.rb
|
44
44
|
- lib/factory_bot/with/proxy.rb
|
45
|
+
- lib/factory_bot/with/scoped.rb
|
45
46
|
- lib/factory_bot/with/version.rb
|
46
47
|
homepage: https://github.com/yubrot/factory_bot-with
|
47
48
|
licenses:
|
@@ -67,6 +68,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
68
|
requirements: []
|
68
69
|
rubygems_version: 3.6.2
|
69
70
|
specification_version: 4
|
70
|
-
summary: FactoryBot extension that
|
71
|
-
little easier to use
|
71
|
+
summary: a FactoryBot extension that enhances usability by wrapping factory methods
|
72
72
|
test_files: []
|