factory_bot-with 0.4.0 → 0.5.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/CHANGELOG.md +7 -1
- data/README.md +65 -17
- data/lib/factory_bot/with/version.rb +1 -1
- data/lib/factory_bot/with.rb +37 -17
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa2396f8ce3d44a17caacc293e2b5914b781eb167c50de541c2950b6f3e8ce0f
|
4
|
+
data.tar.gz: a5b0e4b93ba6f97439dd25290c2ab5c3832175799e7e45f538e0c9db28be14bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a655f59ca867e26316fb08d3fd80e9352d4cefeb600c9d324de89358096a031e549acc5d98da43d67f608f615084bfc384a4c8d45f9e528891d4f90f9fe9de5f
|
7
|
+
data.tar.gz: 4709882eea9a1a90e6719a44133d66e1de9c189da2c602963f77b9c80b14952b99dbd2b350085d496f15cf70f20bbbce4edc1a5a37b2dcf9272d244be31d7589
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
-
## [0.
|
3
|
+
## [0.5.0] - 2025-03-20
|
4
|
+
|
5
|
+
- Added: When using `with` scope syntax, blocks can now take given objects as arguments
|
6
|
+
- Added: `with_list` also works as a scope syntax, but calls a block for each product of objects
|
7
|
+
- **Changed**: Passing blocks to factory methods behaves same as `with` scope syntax
|
8
|
+
|
9
|
+
## [0.4.0] - 2025-03-15
|
4
10
|
|
5
11
|
- Fixed: Improved error message for incorrect factory usage
|
6
12
|
- Added: Added `with` scope syntax for automatic association resolution
|
data/README.md
CHANGED
@@ -62,7 +62,7 @@ FactoryBot::With overrides the behavior when factory methods are called without
|
|
62
62
|
|
63
63
|
```ruby
|
64
64
|
create(:foo, ...) # behaves in the same way as FactoryBot.create
|
65
|
-
create # returns a Proxy (an
|
65
|
+
create # returns a Proxy (an intermediate) object
|
66
66
|
create.foo(...) # is equivalent to create(:foo, ...)
|
67
67
|
```
|
68
68
|
|
@@ -147,6 +147,70 @@ create.blog(with.article) # autocomplete to :blog_article
|
|
147
147
|
|
148
148
|
## Additional features
|
149
149
|
|
150
|
+
### `with` scope syntax for automatic association resolution
|
151
|
+
|
152
|
+
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 scope where those objects become candidates for automatic association resolution.
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
let(:blog) { create.blog }
|
156
|
+
|
157
|
+
before do
|
158
|
+
with(blog:) do
|
159
|
+
# Just like when using `create.blog(with.article)`,
|
160
|
+
# `blog:` is completed automatically at each `create.article`
|
161
|
+
create.article(with.comment)
|
162
|
+
create.article(with_list.comment(3))
|
163
|
+
end
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
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
|
+
<details>
|
177
|
+
<summary>Comparison</summary>
|
178
|
+
|
179
|
+
`_pair` and `_list` methods have [an incompatible behavior](./lib/factory_bot/with.rb#L121) with FactoryBot. If you want to avoid this, just use `Object#tap`.
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
# This creates a blog with 2 articles, each with 1 comment
|
183
|
+
# plain factory_bot:
|
184
|
+
create(:blog) do |blog|
|
185
|
+
create_list(:article, 2, blog:) do |articles| # yielded once with an array of articles in plain factory_bot
|
186
|
+
articles.each { |article| create(:comment, article:) }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# `with` operator:
|
191
|
+
create.blog(
|
192
|
+
with_list.article(2, with.comment)
|
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
|
198
|
+
end
|
199
|
+
|
200
|
+
# with as a scope syntax for existing blog:
|
201
|
+
blog = create.blog
|
202
|
+
with(blog:) do
|
203
|
+
create_list.article(2) { create.comment }
|
204
|
+
end
|
205
|
+
|
206
|
+
# with_list can also be used as a scope syntax:
|
207
|
+
blog = create.blog
|
208
|
+
articles = create_list.article(2, blog:)
|
209
|
+
with_list(article: articles) { create.comment } # yielded *for each article*
|
210
|
+
```
|
211
|
+
|
212
|
+
</details>
|
213
|
+
|
150
214
|
### `with` as a template
|
151
215
|
|
152
216
|
`with` can also be used stand-alone. Stand-alone `with` can be used in place of the factory name. It works as a template for factory method calls.
|
@@ -180,22 +244,6 @@ context "when published more than one year ago" do
|
|
180
244
|
end
|
181
245
|
```
|
182
246
|
|
183
|
-
### `with` scope for automatic association resolution
|
184
|
-
|
185
|
-
By calling `with` without positional arguments, but with keyword arguments that define the relationship between factory names and objects, along with a block, `factory_bot-with` creates a scope where those objects become candidates for automatic association resolution.
|
186
|
-
|
187
|
-
```ruby
|
188
|
-
let(:blog) { create.blog }
|
189
|
-
|
190
|
-
before do
|
191
|
-
with(blog:) do
|
192
|
-
# Just like when using create.blog, blog is automatically resolved when create.article is called
|
193
|
-
create.article(with.comment)
|
194
|
-
create.article(with_list.comment(3))
|
195
|
-
end
|
196
|
-
end
|
197
|
-
```
|
198
|
-
|
199
247
|
## Development
|
200
248
|
|
201
249
|
```bash
|
data/lib/factory_bot/with.rb
CHANGED
@@ -63,9 +63,9 @@ module FactoryBot
|
|
63
63
|
return first unless second
|
64
64
|
return second unless first
|
65
65
|
|
66
|
-
|
67
|
-
first.call(
|
68
|
-
second.call(
|
66
|
+
proc do |arg|
|
67
|
+
first.call(arg)
|
68
|
+
second.call(arg)
|
69
69
|
end
|
70
70
|
end.call(block, other.block)
|
71
71
|
)
|
@@ -110,14 +110,17 @@ module FactoryBot
|
|
110
110
|
else
|
111
111
|
[@factory_name, @attrs]
|
112
112
|
end
|
113
|
-
result = FactoryBot.__send__(factory_bot_method, factory_name, *traits, **attrs
|
113
|
+
result = FactoryBot.__send__(factory_bot_method, factory_name, *traits, **attrs)
|
114
114
|
|
115
|
-
|
116
|
-
parents = variation == :singular ? [result] : result
|
115
|
+
if block || !withes.empty?
|
117
116
|
assoc_info = AssocInfo.get(factory_name)
|
117
|
+
parents = variation == :singular ? [result] : result
|
118
118
|
parents.each do |parent|
|
119
119
|
ancestors_for_children = [[assoc_info, parent], *ancestors || []]
|
120
120
|
withes.each { _1.instantiate(build_strategy, ancestors_for_children) }
|
121
|
+
# We call the block for each parent object. This is an incompatible behavior with FactoryBot!
|
122
|
+
# If you want to avoid this, use `Object#tap` manually.
|
123
|
+
self.class.with_scoped_ancestors(ancestors_for_children) { block.call(result) } if block
|
121
124
|
end
|
122
125
|
end
|
123
126
|
|
@@ -154,9 +157,14 @@ module FactoryBot
|
|
154
157
|
elsif args.empty? && kwargs.empty? && !block
|
155
158
|
# <__method__>.<factory_name>(...)
|
156
159
|
Proxy.new(self, __method__)
|
157
|
-
elsif __method__ == :with && args.empty? && !kwargs.empty?
|
160
|
+
elsif __method__ == :with && args.empty? && !kwargs.empty? && block
|
158
161
|
# with(<factory_name>: <object>, ...) { ... }
|
159
|
-
With.
|
162
|
+
block = With.call_with_scope_adapter(&block)
|
163
|
+
With.call_with_scope(kwargs, &block)
|
164
|
+
elsif __method__ == :with_list && args.empty? && !kwargs.empty? && block
|
165
|
+
# with_list(<factory_name>: [<object>, ...], ...) { ... }
|
166
|
+
block = With.call_with_scope_adapter(&block)
|
167
|
+
kwargs.values.inject(:product).map { With.call_with_scope(kwargs.keys.zip(_1).to_h, &block) }
|
160
168
|
else
|
161
169
|
raise ArgumentError, "Invalid use of #{__method__}"
|
162
170
|
end
|
@@ -165,23 +173,35 @@ module FactoryBot
|
|
165
173
|
end
|
166
174
|
|
167
175
|
# @!visibility private
|
168
|
-
# @
|
169
|
-
def
|
170
|
-
return unless block_given?
|
176
|
+
# @return [Array<Array(AssocInfo, Object)>, nil]
|
177
|
+
def scoped_ancestors = Thread.current[:factory_bot_with_scoped_ancestors]
|
171
178
|
|
179
|
+
# @param ancestors [Array<Array(AssocInfo, Object)>]
|
180
|
+
def with_scoped_ancestors(ancestors, &)
|
172
181
|
tmp_scoped_ancestors = scoped_ancestors
|
173
|
-
Thread.current[:factory_bot_with_scoped_ancestors] = [
|
174
|
-
*objects.map { [AssocInfo.get(_1), _2] },
|
175
|
-
*tmp_scoped_ancestors || [],
|
176
|
-
]
|
182
|
+
Thread.current[:factory_bot_with_scoped_ancestors] = [*ancestors, *tmp_scoped_ancestors || []]
|
177
183
|
result = yield
|
178
184
|
Thread.current[:factory_bot_with_scoped_ancestors] = tmp_scoped_ancestors
|
179
185
|
result
|
180
186
|
end
|
181
187
|
|
182
188
|
# @!visibility private
|
183
|
-
# @
|
184
|
-
def
|
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
|
185
205
|
end
|
186
206
|
|
187
207
|
%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.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yubrot
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-03-
|
10
|
+
date: 2025-03-20 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: factory_bot
|