rom-factory 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +3 -2
- data/.github/workflows/docsite.yml +2 -3
- data/.github/workflows/sync_configs.yml +9 -15
- data/{.action_hero.yml → .repobot.yml} +8 -5
- data/CHANGELOG.md +34 -14
- data/Gemfile +3 -3
- data/changelog.yml +12 -0
- data/docsite/source/index.html.md +4 -4
- data/lib/rom/factory/attribute_registry.rb +4 -0
- data/lib/rom/factory/attributes/association.rb +91 -33
- data/lib/rom/factory/builder/persistable.rb +19 -2
- data/lib/rom/factory/builder.rb +9 -1
- data/lib/rom/factory/dsl.rb +7 -1
- data/lib/rom/factory/sequences.rb +3 -2
- data/lib/rom/factory/tuple_evaluator.rb +74 -21
- data/lib/rom/factory/version.rb +1 -1
- data/rom-factory.gemspec +1 -1
- 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: 0cae4bd0ca2ec550f5fdfb9e17151f6e4be3b575f382935281721f75f905f46b
|
4
|
+
data.tar.gz: 1c6291ac1413d16b15fe946378d1eb59e08d8bafa61f2c8eb7743412bd53d31a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14b6dcc4ea8a9077684b372baba986a0bd51b081ab75faadb954156c9b09dbcdf5f05bfe1a0cfc886e915b461c13afa8c0d42213337264188d438a0064ab8c34
|
7
|
+
data.tar.gz: 5705eb8df8fd99a9ff93c53b769a3c3c7f09749740642abf9a2d8f6907b67104147286d667d18de8ae372c8bafd7edad86d1baba8273cd2e67613ecc68a474c9
|
data/.github/workflows/ci.yml
CHANGED
@@ -23,9 +23,10 @@ jobs:
|
|
23
23
|
fail-fast: false
|
24
24
|
matrix:
|
25
25
|
ruby:
|
26
|
+
- '3.3'
|
27
|
+
- '3.2'
|
26
28
|
- '3.1'
|
27
29
|
- '3.0'
|
28
|
-
- '2.7'
|
29
30
|
env:
|
30
31
|
COVERAGE: "${{matrix.coverage}}"
|
31
32
|
COVERAGE_TOKEN: "${{secrets.CODACY_PROJECT_TOKEN}}"
|
@@ -78,7 +79,7 @@ jobs:
|
|
78
79
|
- name: Set up Ruby
|
79
80
|
uses: ruby/setup-ruby@v1
|
80
81
|
with:
|
81
|
-
ruby-version:
|
82
|
+
ruby-version: "3.1"
|
82
83
|
- name: Install dependencies
|
83
84
|
run: gem install ossy --no-document
|
84
85
|
- name: Trigger release workflow
|
@@ -22,9 +22,9 @@ jobs:
|
|
22
22
|
- run: |
|
23
23
|
git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
|
24
24
|
- name: Set up Ruby
|
25
|
-
uses:
|
25
|
+
uses: ruby/setup-ruby@v1
|
26
26
|
with:
|
27
|
-
ruby-version: "
|
27
|
+
ruby-version: "3.1"
|
28
28
|
- name: Set up git user
|
29
29
|
run: |
|
30
30
|
git config --local user.email "rom-bot@rom-rb.org"
|
@@ -60,4 +60,3 @@ jobs:
|
|
60
60
|
GITHUB_LOGIN: rom-bot
|
61
61
|
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
62
62
|
run: ossy github workflow rom-rb/rom-rb.org ci
|
63
|
-
|
@@ -1,31 +1,25 @@
|
|
1
1
|
# This file is synced from rom-rb/template-gem repo
|
2
2
|
|
3
|
-
name:
|
3
|
+
name: sync
|
4
4
|
|
5
5
|
on:
|
6
|
+
repository_dispatch:
|
6
7
|
push:
|
7
|
-
paths:
|
8
|
-
- "changelog.yml"
|
9
8
|
branches:
|
10
9
|
- "main"
|
11
|
-
pull_request:
|
12
|
-
branches:
|
13
|
-
- "main"
|
14
|
-
types: [closed]
|
15
10
|
|
16
11
|
jobs:
|
17
|
-
|
12
|
+
main:
|
18
13
|
runs-on: ubuntu-latest
|
19
|
-
if: github.event.
|
20
|
-
name: Update
|
14
|
+
if: (github.event_name == 'repository_dispatch' && github.event.action == 'sync_configs') || github.event_name != 'repository_dispatch'
|
21
15
|
env:
|
22
16
|
GITHUB_LOGIN: rom-bot
|
23
17
|
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
24
18
|
steps:
|
25
19
|
- name: Checkout ${{github.repository}}
|
26
|
-
uses: actions/checkout@
|
20
|
+
uses: actions/checkout@v3
|
27
21
|
- name: Checkout devtools
|
28
|
-
uses: actions/checkout@
|
22
|
+
uses: actions/checkout@v3
|
29
23
|
with:
|
30
24
|
repository: rom-rb/devtools
|
31
25
|
path: tmp/devtools
|
@@ -34,9 +28,9 @@ jobs:
|
|
34
28
|
git config --local user.email "rom-bot@rom-rb.org"
|
35
29
|
git config --local user.name "rom-bot"
|
36
30
|
- name: Set up Ruby
|
37
|
-
uses:
|
31
|
+
uses: ruby/setup-ruby@v1
|
38
32
|
with:
|
39
|
-
ruby-version: "
|
33
|
+
ruby-version: "3.1"
|
40
34
|
- name: Install dependencies
|
41
35
|
run: gem install ossy --no-document
|
42
36
|
- name: Update changelog.yml from commit
|
@@ -46,7 +40,7 @@ jobs:
|
|
46
40
|
- name: Commit
|
47
41
|
run: |
|
48
42
|
git add -A
|
49
|
-
git commit -m "
|
43
|
+
git commit -m "[devtools] sync" || echo "nothing to commit"
|
50
44
|
- name: Push changes
|
51
45
|
run: |
|
52
46
|
git pull --rebase origin main
|
@@ -1,12 +1,17 @@
|
|
1
|
+
###########################################################
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
#
|
1
4
|
# This is a config synced from rom-rb/template-gem repo
|
5
|
+
###########################################################
|
2
6
|
|
3
7
|
sources:
|
4
8
|
- repo: rom-rb/template-gem
|
5
9
|
sync:
|
6
|
-
- "
|
7
|
-
- ".action_hero.yml.erb"
|
10
|
+
- ".repobot.yml.erb"
|
8
11
|
- ".devtools/templates/*.sync:${{dir}}/${{name}}"
|
9
12
|
- ".github/**/*.*"
|
13
|
+
- ".rspec"
|
14
|
+
- ".rubocop.yml"
|
10
15
|
- "spec/support/*"
|
11
16
|
- "CODE_OF_CONDUCT.md"
|
12
17
|
- "CONTRIBUTING.md"
|
@@ -14,8 +19,6 @@ sources:
|
|
14
19
|
- "LICENSE.erb"
|
15
20
|
- "README.erb"
|
16
21
|
- "Gemfile.devtools"
|
17
|
-
|
18
|
-
- ".rubocop.yml"
|
19
|
-
- repo: action-hero/workflows
|
22
|
+
- repo: repobot-app/workflows
|
20
23
|
sync:
|
21
24
|
- ".github/workflows/*.yml"
|
data/CHANGELOG.md
CHANGED
@@ -1,35 +1,55 @@
|
|
1
1
|
<!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
|
2
2
|
|
3
|
-
## 0.
|
3
|
+
## 0.12.0 2024-01-19
|
4
|
+
|
5
|
+
|
6
|
+
### Added
|
7
|
+
|
8
|
+
- Support for many-to-many and one-to-one-through associations (via
|
9
|
+
- Support for UUID as PKs in associations (via
|
4
10
|
|
5
11
|
### Fixed
|
6
12
|
|
7
|
-
-
|
13
|
+
- Relations without PKs should work too (via
|
14
|
+
- Relations with PK values generated on the Ruby side should work in SQlite too (via
|
15
|
+
|
16
|
+
|
17
|
+
[Compare v0.11.0...v0.12.0](https://github.com/rom-rb/rom-factory/compare/v0.11.0...v0.12.0)
|
18
|
+
|
19
|
+
## 0.11.0 2022-11-11
|
20
|
+
|
8
21
|
|
9
22
|
### Added
|
10
23
|
|
11
24
|
- Support for one-to-one associations (@ianks)
|
12
25
|
- [internal] cache for Faker constants (@flash-gordon)
|
13
26
|
|
27
|
+
### Fixed
|
28
|
+
|
29
|
+
- Support for plural Faker generators (@wuarmin)
|
30
|
+
|
14
31
|
### Changed
|
15
32
|
|
16
33
|
- [BREAKING] attributes are always passed as keywords (@alassek)
|
17
|
-
|
18
|
-
|
34
|
+
This may affect your code in places where attributes are passed as hashes.
|
35
|
+
Places like
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
user_attributes = { name: 'Jane' }
|
39
|
+
Factory[:user, user_attributes]
|
40
|
+
|
41
|
+
```
|
42
|
+
|
43
|
+
must be updated to
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
user_attributes = { name: 'Jane' }
|
47
|
+
Factory[:user, **user_attributes]
|
48
|
+
```
|
19
49
|
|
20
|
-
```ruby
|
21
|
-
user_attributes = { name: 'Jane' }
|
22
|
-
Factory[:user, user_attributes]
|
23
|
-
```
|
24
|
-
must be updated to
|
25
|
-
```ruby
|
26
|
-
user_attributes = { name: 'Jane' }
|
27
|
-
Factory[:user, **user_attributes]
|
28
|
-
```
|
29
50
|
- Upgraded to the latest versions of dry-rb dependencies, compatible with rom 5.3 (@flash-gordon)
|
30
51
|
- Support for Faker 1.x was dropped (@alassek)
|
31
52
|
|
32
|
-
|
33
53
|
[Compare v0.10.2...v0.11.0](https://github.com/rom-rb/rom-factory/compare/v0.10.2...v0.11.0)
|
34
54
|
|
35
55
|
## 0.10.2 2020-04-05
|
data/Gemfile
CHANGED
@@ -6,7 +6,7 @@ gemspec
|
|
6
6
|
|
7
7
|
eval_gemfile "Gemfile.devtools"
|
8
8
|
|
9
|
-
gem "faker", "~>
|
9
|
+
gem "faker", "~> 3.0"
|
10
10
|
|
11
11
|
gem "rspec", "~> 3.0"
|
12
12
|
|
@@ -18,12 +18,12 @@ git "https://github.com/rom-rb/rom.git", branch: "release-5.3" do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
group :test do
|
21
|
-
gem "pry"
|
21
|
+
gem "pry"
|
22
22
|
gem "pry-byebug", "~> 3.8", platforms: :ruby
|
23
23
|
gem "rom-sql", github: "rom-rb/rom-sql", branch: "release-3.6"
|
24
24
|
|
25
25
|
gem "jdbc-postgres", platforms: :jruby
|
26
|
-
gem "pg", "~>
|
26
|
+
gem "pg", "~> 1.5", platforms: :ruby
|
27
27
|
end
|
28
28
|
|
29
29
|
group :tools do
|
data/changelog.yml
CHANGED
@@ -1,4 +1,12 @@
|
|
1
1
|
---
|
2
|
+
- version: 0.12.0
|
3
|
+
date: 2024-01-19
|
4
|
+
added:
|
5
|
+
- Support for many-to-many and one-to-one-through associations (via #86) (@solnic)
|
6
|
+
- Support for UUID as PKs in associations (via #87) (@solnic)
|
7
|
+
fixed:
|
8
|
+
- Relations without PKs should work too (via #87) (@solnic)
|
9
|
+
- Relations with PK values generated on the Ruby side should work in SQlite too (via #87) (@solnic)
|
2
10
|
- version: 0.11.0
|
3
11
|
date: 2022-11-11
|
4
12
|
added:
|
@@ -9,11 +17,15 @@
|
|
9
17
|
[BREAKING] attributes are always passed as keywords (@alassek)
|
10
18
|
This may affect your code in places where attributes are passed as hashes.
|
11
19
|
Places like
|
20
|
+
|
12
21
|
```ruby
|
13
22
|
user_attributes = { name: 'Jane' }
|
14
23
|
Factory[:user, user_attributes]
|
24
|
+
|
15
25
|
```
|
26
|
+
|
16
27
|
must be updated to
|
28
|
+
|
17
29
|
```ruby
|
18
30
|
user_attributes = { name: 'Jane' }
|
19
31
|
Factory[:user, **user_attributes]
|
@@ -44,7 +44,7 @@ end
|
|
44
44
|
|
45
45
|
#### Specify namespace for your structs
|
46
46
|
|
47
|
-
Struct `User` will be
|
47
|
+
Struct `User` will be found in `MyApp::Entities` namespace
|
48
48
|
|
49
49
|
```ruby
|
50
50
|
Factory.define(:user, struct_namespace: MyApp::Entities) do |f|
|
@@ -86,7 +86,7 @@ end
|
|
86
86
|
|
87
87
|
Factory.define(:user) do |f|
|
88
88
|
f.name 'John'
|
89
|
-
f.association(:
|
89
|
+
f.association(:group)
|
90
90
|
end
|
91
91
|
```
|
92
92
|
|
@@ -115,7 +115,7 @@ Factory.define(admin: :user) do |f|
|
|
115
115
|
f.admin true
|
116
116
|
end
|
117
117
|
|
118
|
-
# Factory.structs
|
118
|
+
# Factory.structs[:admin]
|
119
119
|
```
|
120
120
|
|
121
121
|
#### Traits
|
@@ -130,7 +130,7 @@ Factory.define(:user) do |f|
|
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
133
|
-
# Factory.structs
|
133
|
+
# Factory.structs[:user, :with_age]
|
134
134
|
```
|
135
135
|
|
136
136
|
#### Build-in [Faker](https://github.com/faker-ruby/faker) objects
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module ROM::Factory
|
4
4
|
module Attributes
|
5
5
|
# @api private
|
6
|
+
# rubocop:disable Style/OptionalArguments
|
6
7
|
module Association
|
7
8
|
class << self
|
8
9
|
def new(assoc, builder, *traits, **options)
|
@@ -46,28 +47,62 @@ module ROM::Factory
|
|
46
47
|
def value?
|
47
48
|
false
|
48
49
|
end
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
def factories
|
53
|
+
builder.factories
|
54
|
+
end
|
55
|
+
|
56
|
+
# @api private
|
57
|
+
def foreign_key
|
58
|
+
assoc.foreign_key
|
59
|
+
end
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
def count
|
63
|
+
options.fetch(:count, 1)
|
64
|
+
end
|
49
65
|
end
|
50
66
|
|
51
67
|
# @api private
|
52
68
|
class ManyToOne < Core
|
53
69
|
# @api private
|
70
|
+
# rubocop:disable Metrics/AbcSize
|
54
71
|
def call(attrs, persist: true)
|
55
|
-
if attrs.key?(name) &&
|
72
|
+
return if attrs.key?(name) && attrs[name].nil?
|
73
|
+
|
74
|
+
assoc_data = attrs.fetch(name, EMPTY_HASH)
|
75
|
+
|
76
|
+
if assoc_data.is_a?(Hash) && assoc_data[assoc.target.primary_key] && !attrs[foreign_key]
|
56
77
|
assoc.associate(attrs, attrs[name])
|
57
|
-
elsif
|
58
|
-
|
59
|
-
|
78
|
+
elsif assoc_data.is_a?(ROM::Struct)
|
79
|
+
assoc.associate(attrs, assoc_data)
|
80
|
+
else
|
81
|
+
parent = if persist && !attrs[foreign_key]
|
82
|
+
builder.persistable.create(*parent_traits, **assoc_data)
|
60
83
|
else
|
61
|
-
builder.struct(
|
84
|
+
builder.struct(
|
85
|
+
*parent_traits,
|
86
|
+
**assoc_data.merge(assoc.target.primary_key => attrs[foreign_key])
|
87
|
+
)
|
62
88
|
end
|
63
|
-
|
64
|
-
|
89
|
+
|
90
|
+
tuple = {name => parent}
|
91
|
+
|
92
|
+
assoc.associate(tuple, parent)
|
65
93
|
end
|
66
94
|
end
|
95
|
+
# rubocop:enable Metrics/AbcSize
|
67
96
|
|
68
|
-
|
69
|
-
|
70
|
-
|
97
|
+
private
|
98
|
+
|
99
|
+
def parent_traits
|
100
|
+
@parent_traits ||=
|
101
|
+
if assoc.target.associations.key?(assoc.source.name)
|
102
|
+
traits + [assoc.target.associations[assoc.source.name].key => false]
|
103
|
+
else
|
104
|
+
traits
|
105
|
+
end
|
71
106
|
end
|
72
107
|
end
|
73
108
|
|
@@ -95,11 +130,6 @@ module ROM::Factory
|
|
95
130
|
def dependency?(rel)
|
96
131
|
assoc.source == rel
|
97
132
|
end
|
98
|
-
|
99
|
-
# @api private
|
100
|
-
def count
|
101
|
-
options.fetch(:count)
|
102
|
-
end
|
103
133
|
end
|
104
134
|
|
105
135
|
# @api private
|
@@ -124,28 +154,45 @@ module ROM::Factory
|
|
124
154
|
|
125
155
|
{name => struct}
|
126
156
|
end
|
127
|
-
|
128
|
-
# @api private
|
129
|
-
def count
|
130
|
-
options.fetch(:count, 1)
|
131
|
-
end
|
132
157
|
end
|
133
158
|
|
134
|
-
class
|
159
|
+
class ManyToMany < Core
|
135
160
|
def call(attrs = EMPTY_HASH, parent, persist: true)
|
136
161
|
return if attrs.key?(name)
|
137
162
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
163
|
+
structs = count.times.map do
|
164
|
+
if persist && attrs[tpk]
|
165
|
+
attrs
|
166
|
+
elsif persist
|
167
|
+
builder.persistable.create(*traits, **attrs)
|
168
|
+
else
|
169
|
+
builder.struct(*traits, **attrs)
|
170
|
+
end
|
171
|
+
end
|
145
172
|
|
146
|
-
|
173
|
+
# Delegate to through factory if it exists
|
174
|
+
if persist
|
175
|
+
if through_factory?
|
176
|
+
structs.each do |child|
|
177
|
+
through_attrs = {
|
178
|
+
Dry::Core::Inflector.singularize(assoc.source.name.key).to_sym => parent,
|
179
|
+
assoc.through.assoc_name => child
|
180
|
+
}
|
181
|
+
|
182
|
+
factories[through_factory_name, **through_attrs]
|
183
|
+
end
|
184
|
+
else
|
185
|
+
assoc.persist([parent], structs)
|
186
|
+
end
|
147
187
|
|
148
|
-
|
188
|
+
{name => result(structs)}
|
189
|
+
else
|
190
|
+
result(structs)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def result(structs)
|
195
|
+
{name => structs}
|
149
196
|
end
|
150
197
|
|
151
198
|
def dependency?(rel)
|
@@ -156,16 +203,27 @@ module ROM::Factory
|
|
156
203
|
true
|
157
204
|
end
|
158
205
|
|
159
|
-
|
206
|
+
def through_factory?
|
207
|
+
factories.registry.key?(through_factory_name)
|
208
|
+
end
|
160
209
|
|
161
|
-
def
|
162
|
-
|
210
|
+
def through_factory_name
|
211
|
+
ROM::Inflector.singularize(assoc.definition.through.source).to_sym
|
163
212
|
end
|
164
213
|
|
214
|
+
private
|
215
|
+
|
165
216
|
def tpk
|
166
217
|
assoc.target.primary_key
|
167
218
|
end
|
168
219
|
end
|
220
|
+
|
221
|
+
class OneToOneThrough < ManyToMany
|
222
|
+
def result(structs)
|
223
|
+
{name => structs[0]}
|
224
|
+
end
|
225
|
+
end
|
169
226
|
end
|
170
227
|
end
|
228
|
+
# rubocop:enable Style/OptionalArguments
|
171
229
|
end
|
@@ -22,8 +22,9 @@ module ROM
|
|
22
22
|
|
23
23
|
# @api private
|
24
24
|
def create(*traits, **attrs)
|
25
|
-
tuple = tuple(*traits, **attrs)
|
26
25
|
validate_keys(traits, attrs)
|
26
|
+
|
27
|
+
tuple = tuple(*traits, **attrs)
|
27
28
|
persisted = persist(tuple)
|
28
29
|
|
29
30
|
if tuple_evaluator.has_associations?(traits)
|
@@ -41,13 +42,29 @@ module ROM
|
|
41
42
|
|
42
43
|
# @api private
|
43
44
|
def persist(attrs)
|
44
|
-
relation
|
45
|
+
result = relation
|
46
|
+
.with(auto_struct: !tuple_evaluator.has_associations?)
|
47
|
+
.command(:create)
|
48
|
+
.call(attrs)
|
49
|
+
|
50
|
+
# Handle PK values generated by the factory
|
51
|
+
if pk? && (pks = attrs.values_at(*primary_key_names)).compact.size == primary_key_names.size
|
52
|
+
relation.by_pk(*pks).one!
|
53
|
+
elsif result
|
54
|
+
result
|
55
|
+
else
|
56
|
+
relation.where(attrs).one!
|
57
|
+
end
|
45
58
|
end
|
46
59
|
|
47
60
|
# @api private
|
48
61
|
def primary_key_names
|
49
62
|
relation.schema.primary_key.map(&:name)
|
50
63
|
end
|
64
|
+
|
65
|
+
def pk?
|
66
|
+
primary_key_names.any?
|
67
|
+
end
|
51
68
|
end
|
52
69
|
end
|
53
70
|
end
|
data/lib/rom/factory/builder.rb
CHANGED
@@ -28,6 +28,10 @@ module ROM::Factory
|
|
28
28
|
# @return [Module] Custom struct namespace
|
29
29
|
option :struct_namespace, reader: false
|
30
30
|
|
31
|
+
# @!attribute [r] factories
|
32
|
+
# @return [Module] Factories with other builders
|
33
|
+
option :factories, reader: true, optional: true
|
34
|
+
|
31
35
|
# @api private
|
32
36
|
def tuple(*traits, **attrs)
|
33
37
|
tuple_evaluator.defaults(traits, attrs)
|
@@ -57,7 +61,11 @@ module ROM::Factory
|
|
57
61
|
|
58
62
|
# @api private
|
59
63
|
def tuple_evaluator
|
60
|
-
@__tuple_evaluator__ ||= TupleEvaluator.new(
|
64
|
+
@__tuple_evaluator__ ||= TupleEvaluator.new(
|
65
|
+
attributes,
|
66
|
+
tuple_evaluator_relation,
|
67
|
+
traits
|
68
|
+
)
|
61
69
|
end
|
62
70
|
|
63
71
|
# @api private
|
data/lib/rom/factory/dsl.rb
CHANGED
@@ -50,7 +50,13 @@ module ROM
|
|
50
50
|
|
51
51
|
# @api private
|
52
52
|
def call
|
53
|
-
::ROM::Factory::Builder.new(
|
53
|
+
::ROM::Factory::Builder.new(
|
54
|
+
_attributes,
|
55
|
+
_traits,
|
56
|
+
relation: _relation,
|
57
|
+
struct_namespace: _struct_namespace,
|
58
|
+
factories: _factories
|
59
|
+
)
|
54
60
|
end
|
55
61
|
|
56
62
|
# Delegate to a builder and persist a struct
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "concurrent/map"
|
3
4
|
require "singleton"
|
4
5
|
|
5
6
|
module ROM
|
@@ -24,12 +25,12 @@ module ROM
|
|
24
25
|
|
25
26
|
# @api private
|
26
27
|
def next(key)
|
27
|
-
registry
|
28
|
+
registry.compute(key) { |v| (v || 0).succ }
|
28
29
|
end
|
29
30
|
|
30
31
|
# @api private
|
31
32
|
def reset
|
32
|
-
@registry = Concurrent::Map.new
|
33
|
+
@registry = Concurrent::Map.new
|
33
34
|
self
|
34
35
|
end
|
35
36
|
end
|
@@ -6,6 +6,29 @@ module ROM
|
|
6
6
|
module Factory
|
7
7
|
# @api private
|
8
8
|
class TupleEvaluator
|
9
|
+
class TupleEvaluatorError < StandardError
|
10
|
+
attr_reader :original_exception
|
11
|
+
|
12
|
+
def initialize(relation, original_exception, attrs, traits, assoc_attrs)
|
13
|
+
super(<<~STR)
|
14
|
+
Failed to build attributes for #{relation.name}
|
15
|
+
|
16
|
+
Attributes:
|
17
|
+
#{attrs.inspect}
|
18
|
+
|
19
|
+
Associations:
|
20
|
+
#{assoc_attrs}
|
21
|
+
|
22
|
+
Traits:
|
23
|
+
#{traits.inspect}
|
24
|
+
|
25
|
+
Original exception: #{original_exception.message}
|
26
|
+
STR
|
27
|
+
|
28
|
+
set_backtrace(original_exception.backtrace)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
9
32
|
# @api private
|
10
33
|
attr_reader :attributes
|
11
34
|
|
@@ -15,9 +38,6 @@ module ROM
|
|
15
38
|
# @api private
|
16
39
|
attr_reader :traits
|
17
40
|
|
18
|
-
# @api private
|
19
|
-
attr_reader :model
|
20
|
-
|
21
41
|
# @api private
|
22
42
|
attr_reader :sequence
|
23
43
|
|
@@ -26,10 +46,13 @@ module ROM
|
|
26
46
|
@attributes = attributes
|
27
47
|
@relation = relation.with(auto_struct: true)
|
28
48
|
@traits = traits
|
29
|
-
@model = @relation.combine(*assoc_names).mapper.model
|
30
49
|
@sequence = Sequences[relation]
|
31
50
|
end
|
32
51
|
|
52
|
+
def model(traits, combine: assoc_names(traits))
|
53
|
+
@relation.combine(*combine).mapper.model
|
54
|
+
end
|
55
|
+
|
33
56
|
# @api private
|
34
57
|
def defaults(traits, attrs, **opts)
|
35
58
|
mergeable_attrs = select_mergeable_attrs(traits, attrs)
|
@@ -45,21 +68,41 @@ module ROM
|
|
45
68
|
attributes = merged_attrs.reject(&is_callable)
|
46
69
|
|
47
70
|
materialized_callables = {}
|
48
|
-
callables.
|
71
|
+
callables.each_value do |callable|
|
49
72
|
materialized_callables.merge!(callable.call(attributes, persist: false))
|
50
73
|
end
|
51
74
|
|
52
75
|
attributes.merge!(materialized_callables)
|
53
76
|
|
54
|
-
|
55
|
-
|
56
|
-
|
77
|
+
assoc_attrs = attributes.slice(*assoc_names(traits)).merge(
|
78
|
+
assoc_names(traits)
|
79
|
+
.select { |key|
|
80
|
+
build_assoc?(key, attributes)
|
81
|
+
}
|
82
|
+
.map { |key|
|
83
|
+
[key, build_assoc_attrs(key, attributes[relation.primary_key], attributes[key])]
|
84
|
+
}
|
57
85
|
.to_h
|
86
|
+
)
|
87
|
+
|
88
|
+
model_attrs = relation.output_schema[attributes]
|
89
|
+
model_attrs.update(assoc_attrs)
|
90
|
+
|
91
|
+
model(traits).new(**model_attrs)
|
92
|
+
rescue StandardError => e
|
93
|
+
raise TupleEvaluatorError.new(relation, e, attrs, traits, assoc_attrs)
|
94
|
+
end
|
58
95
|
|
59
|
-
|
60
|
-
attributes.
|
96
|
+
def build_assoc?(name, attributes)
|
97
|
+
attributes.key?(name) && attributes[name] != [] && !attributes[name].nil?
|
98
|
+
end
|
61
99
|
|
62
|
-
|
100
|
+
def build_assoc_attrs(key, fk, value)
|
101
|
+
if value.is_a?(Array)
|
102
|
+
value.map { |el| build_assoc_attrs(key, fk, el) }
|
103
|
+
else
|
104
|
+
{attributes[key].foreign_key => fk}.merge(value.to_h)
|
105
|
+
end
|
63
106
|
end
|
64
107
|
|
65
108
|
# @api private
|
@@ -76,10 +119,15 @@ module ROM
|
|
76
119
|
end
|
77
120
|
|
78
121
|
def assocs(traits_names = [])
|
79
|
-
traits
|
122
|
+
found_assocs = traits
|
80
123
|
.values_at(*traits_names)
|
124
|
+
.compact
|
81
125
|
.map(&:associations).flat_map(&:elements)
|
82
126
|
.inject(AttributeRegistry.new(attributes.associations.elements), :<<)
|
127
|
+
|
128
|
+
exclude = traits_names.select { |t| t.is_a?(Hash) }.reduce(:merge) || EMPTY_HASH
|
129
|
+
|
130
|
+
found_assocs.reject { |a| exclude[a.name] == false }
|
83
131
|
end
|
84
132
|
|
85
133
|
# @api private
|
@@ -97,7 +145,7 @@ module ROM
|
|
97
145
|
# @api private
|
98
146
|
def evaluate(traits, attrs, opts)
|
99
147
|
evaluate_values(attrs)
|
100
|
-
.merge(evaluate_associations(attrs, opts))
|
148
|
+
.merge(evaluate_associations(traits, attrs, opts))
|
101
149
|
.merge(evaluate_traits(traits, attrs, opts))
|
102
150
|
end
|
103
151
|
|
@@ -113,24 +161,29 @@ module ROM
|
|
113
161
|
end
|
114
162
|
end
|
115
163
|
|
116
|
-
def evaluate_traits(
|
117
|
-
return {} if
|
164
|
+
def evaluate_traits(trait_list, attrs, opts)
|
165
|
+
return {} if trait_list.empty?
|
166
|
+
|
167
|
+
traits = trait_list.map { |v| v.is_a?(Hash) ? v : {v => true} }.reduce(:merge)
|
118
168
|
|
119
|
-
traits_attrs = self.traits.
|
169
|
+
traits_attrs = self.traits.select { |key, _value| traits[key] }.values.flat_map(&:elements)
|
120
170
|
registry = AttributeRegistry.new(traits_attrs)
|
171
|
+
|
121
172
|
self.class.new(registry, relation).defaults([], attrs, **opts)
|
122
173
|
end
|
123
174
|
|
124
175
|
# @api private
|
125
|
-
def evaluate_associations(attrs, opts)
|
126
|
-
|
127
|
-
if
|
128
|
-
|
176
|
+
def evaluate_associations(traits, attrs, opts)
|
177
|
+
assocs(traits).associations.each_with_object({}) do |assoc, memo|
|
178
|
+
if attrs.key?(assoc.name) && attrs[assoc.name].nil?
|
179
|
+
memo
|
180
|
+
elsif assoc.dependency?(relation)
|
181
|
+
memo[assoc.name] = ->(parent, call_opts) do
|
129
182
|
assoc.call(parent, **opts, **call_opts)
|
130
183
|
end
|
131
184
|
else
|
132
185
|
result = assoc.(attrs, **opts)
|
133
|
-
|
186
|
+
memo.update(result) if result
|
134
187
|
end
|
135
188
|
end
|
136
189
|
end
|
data/lib/rom/factory/version.rb
CHANGED
data/rom-factory.gemspec
CHANGED
@@ -30,6 +30,6 @@ Gem::Specification.new do |spec|
|
|
30
30
|
spec.add_runtime_dependency "dry-configurable", "~> 1.0"
|
31
31
|
spec.add_runtime_dependency "dry-core", "~> 1.0"
|
32
32
|
spec.add_runtime_dependency "dry-struct", "~> 1.6"
|
33
|
-
spec.add_runtime_dependency "faker", ">= 2.0", "<
|
33
|
+
spec.add_runtime_dependency "faker", ">= 2.0", "< 4"
|
34
34
|
spec.add_runtime_dependency "rom-core", "~> 5.3"
|
35
35
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rom-factory
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janis Miezitis
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-01-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: dry-configurable
|
@@ -62,7 +62,7 @@ dependencies:
|
|
62
62
|
version: '2.0'
|
63
63
|
- - "<"
|
64
64
|
- !ruby/object:Gem::Version
|
65
|
-
version: '
|
65
|
+
version: '4'
|
66
66
|
type: :runtime
|
67
67
|
prerelease: false
|
68
68
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -72,7 +72,7 @@ dependencies:
|
|
72
72
|
version: '2.0'
|
73
73
|
- - "<"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '4'
|
76
76
|
- !ruby/object:Gem::Dependency
|
77
77
|
name: rom-core
|
78
78
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,7 +95,6 @@ executables: []
|
|
95
95
|
extensions: []
|
96
96
|
extra_rdoc_files: []
|
97
97
|
files:
|
98
|
-
- ".action_hero.yml"
|
99
98
|
- ".devtools/templates/changelog.erb"
|
100
99
|
- ".devtools/templates/release.erb"
|
101
100
|
- ".github/FUNDING.yml"
|
@@ -107,6 +106,7 @@ files:
|
|
107
106
|
- ".github/workflows/rubocop.yml"
|
108
107
|
- ".github/workflows/sync_configs.yml"
|
109
108
|
- ".gitignore"
|
109
|
+
- ".repobot.yml"
|
110
110
|
- ".rspec"
|
111
111
|
- ".rubocop.yml"
|
112
112
|
- ".yardopts"
|
@@ -161,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
161
161
|
- !ruby/object:Gem::Version
|
162
162
|
version: '0'
|
163
163
|
requirements: []
|
164
|
-
rubygems_version: 3.
|
164
|
+
rubygems_version: 3.5.5
|
165
165
|
signing_key:
|
166
166
|
specification_version: 4
|
167
167
|
summary: ROM based builder library to make your specs awesome. DSL partially inspired
|