rom-factory 0.11.0 → 0.12.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/.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
|