rom-repository 1.4.0 → 2.0.0.beta1

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -6
  3. data/{LICENSE.txt → LICENSE} +1 -1
  4. data/README.md +18 -1
  5. data/lib/rom-repository.rb +1 -2
  6. data/lib/rom/repository.rb +9 -216
  7. data/lib/rom/repository/class_interface.rb +16 -33
  8. data/lib/rom/repository/relation_reader.rb +46 -0
  9. data/lib/rom/repository/root.rb +3 -59
  10. data/lib/rom/repository/version.rb +1 -1
  11. metadata +9 -98
  12. data/.gitignore +0 -3
  13. data/.rspec +0 -3
  14. data/.travis.yml +0 -27
  15. data/.yardopts +0 -2
  16. data/Gemfile +0 -38
  17. data/Rakefile +0 -19
  18. data/lib/rom/open_struct.rb +0 -35
  19. data/lib/rom/repository/changeset.rb +0 -155
  20. data/lib/rom/repository/changeset/associated.rb +0 -100
  21. data/lib/rom/repository/changeset/create.rb +0 -16
  22. data/lib/rom/repository/changeset/delete.rb +0 -17
  23. data/lib/rom/repository/changeset/pipe.rb +0 -97
  24. data/lib/rom/repository/changeset/restricted.rb +0 -28
  25. data/lib/rom/repository/changeset/stateful.rb +0 -282
  26. data/lib/rom/repository/changeset/update.rb +0 -82
  27. data/lib/rom/repository/command_compiler.rb +0 -257
  28. data/lib/rom/repository/command_proxy.rb +0 -26
  29. data/lib/rom/repository/header_builder.rb +0 -65
  30. data/lib/rom/repository/mapper_builder.rb +0 -23
  31. data/lib/rom/repository/relation_proxy.rb +0 -337
  32. data/lib/rom/repository/relation_proxy/combine.rb +0 -320
  33. data/lib/rom/repository/relation_proxy/wrap.rb +0 -78
  34. data/lib/rom/repository/struct_builder.rb +0 -83
  35. data/lib/rom/struct.rb +0 -113
  36. data/log/.gitkeep +0 -0
  37. data/rom-repository.gemspec +0 -23
  38. data/spec/integration/changeset_spec.rb +0 -193
  39. data/spec/integration/command_macros_spec.rb +0 -191
  40. data/spec/integration/command_spec.rb +0 -228
  41. data/spec/integration/multi_adapter_spec.rb +0 -73
  42. data/spec/integration/repository/aggregate_spec.rb +0 -58
  43. data/spec/integration/repository_spec.rb +0 -406
  44. data/spec/integration/root_repository_spec.rb +0 -106
  45. data/spec/integration/typed_structs_spec.rb +0 -64
  46. data/spec/shared/database.rb +0 -79
  47. data/spec/shared/mappers.rb +0 -35
  48. data/spec/shared/models.rb +0 -41
  49. data/spec/shared/plugins.rb +0 -66
  50. data/spec/shared/relations.rb +0 -115
  51. data/spec/shared/repo.rb +0 -86
  52. data/spec/shared/seeds.rb +0 -30
  53. data/spec/shared/structs.rb +0 -140
  54. data/spec/spec_helper.rb +0 -83
  55. data/spec/support/mapper_registry.rb +0 -9
  56. data/spec/support/mutant.rb +0 -10
  57. data/spec/unit/changeset/associate_spec.rb +0 -120
  58. data/spec/unit/changeset/map_spec.rb +0 -111
  59. data/spec/unit/changeset_spec.rb +0 -186
  60. data/spec/unit/relation_proxy_spec.rb +0 -202
  61. data/spec/unit/repository/changeset_spec.rb +0 -197
  62. data/spec/unit/repository/inspect_spec.rb +0 -18
  63. data/spec/unit/repository/session_spec.rb +0 -251
  64. data/spec/unit/repository/transaction_spec.rb +0 -42
  65. data/spec/unit/session_spec.rb +0 -46
  66. data/spec/unit/struct_builder_spec.rb +0 -128
@@ -1,58 +0,0 @@
1
- RSpec.describe ROM::Repository::Root, '#aggregate' do
2
- subject(:repo) do
3
- Class.new(ROM::Repository[:users]) do
4
- relations :tasks, :posts, :labels
5
- end.new(rom)
6
- end
7
-
8
- include_context 'database'
9
- include_context 'relations'
10
- include_context 'seeds'
11
-
12
- it 'loads a graph with aliased children and its parents' do
13
- user = repo.aggregate(aliased_posts: :author).first
14
-
15
- expect(user.aliased_posts.count).to be(1)
16
- expect(user.aliased_posts[0].author.id).to be(user.id)
17
- expect(user.aliased_posts[0].author.name).to eql(user.name)
18
- end
19
-
20
-
21
- it 'exposes nodes via `node` method' do
22
- jane = repo.
23
- aggregate(:posts).
24
- node(:posts) { |posts| posts.where(title: 'Another one') }.
25
- where(name: 'Jane').one
26
-
27
- expect(jane.name).to eql('Jane')
28
- expect(jane.posts).to be_empty
29
-
30
- repo.posts.insert author_id: 1, title: 'Another one'
31
-
32
- jane = repo.
33
- aggregate(:posts).
34
- node(:posts) { |posts| posts.where(title: 'Another one') }.
35
- where(name: 'Jane').one
36
-
37
- expect(jane.name).to eql('Jane')
38
- expect(jane.posts.size).to be(1)
39
- expect(jane.posts[0].title).to eql('Another one')
40
- end
41
-
42
- it 'exposes nested nodes via `node` method' do
43
- jane = repo.
44
- aggregate(posts: :labels).
45
- node(posts: :labels) { |labels| labels.where(name: 'red') }.
46
- where(name: 'Jane').one
47
-
48
- expect(jane.name).to eql('Jane')
49
- expect(jane.posts.size).to be(1)
50
- expect(jane.posts[0].labels.size).to be(1)
51
- expect(jane.posts[0].labels[0].name).to eql('red')
52
- end
53
-
54
- it 'raises arg error when invalid relation name was passed to `node` method' do
55
- expect { repo.aggregate(:posts).node(:poztz) {} }.
56
- to raise_error(ArgumentError, ':poztz is not a valid aggregate node name')
57
- end
58
- end
@@ -1,406 +0,0 @@
1
- RSpec.describe 'ROM repository' do
2
- include_context 'database'
3
- include_context 'relations'
4
- include_context 'repo'
5
- include_context 'structs'
6
- include_context 'seeds'
7
-
8
- it 'loads a single relation' do
9
- expect(repo.all_users.to_a).to match_array([jane, joe])
10
- end
11
-
12
- it 'can be used with a custom mapper' do
13
- expect(repo.all_users_as_users.to_a).to match_array([
14
- Test::Models::User.new(jane),
15
- Test::Models::User.new(joe)
16
- ])
17
- end
18
-
19
- it 'loads a relation by an association' do
20
- expect(repo.tasks_for_users(repo.all_users)).to match_array([jane_task, joe_task])
21
- end
22
-
23
- it 'loads a combine relation with one parent' do
24
- task = repo.task_with_user.first
25
-
26
- expect(task.id).to eql(task_with_user.id)
27
- expect(task.title).to eql(task_with_user.title)
28
- expect(task.user.id).to eql(task_with_user.user.id)
29
- expect(task.user.name).to eql(task_with_user.user.name)
30
- end
31
-
32
- it 'loads a combine relation with one parent with custom tuple key' do
33
- expect(repo.task_with_owner.first).to eql(task_with_owner)
34
- end
35
-
36
- it 'loads a combined relation with many children' do
37
- expect(repo.users_with_tasks.to_a).to match_array([jane_with_tasks, joe_with_tasks])
38
- end
39
-
40
- it 'loads a combined relation with one child' do
41
- expect(repo.users_with_task.to_a).to match_array([jane_with_task, joe_with_task])
42
- end
43
-
44
- it 'loads a combined relation with one child restricted by given criteria' do
45
- expect(repo.users_with_task_by_title('Joe Task').to_a).to match_array([
46
- jane_without_task, joe_with_task
47
- ])
48
- end
49
-
50
- it 'loads nested combined relations' do
51
- user = repo.users_with_tasks_and_tags.first
52
-
53
- expect(user.id).to be(1)
54
- expect(user.name).to eql('Jane')
55
- expect(user.all_tasks.size).to be(1)
56
- expect(user.all_tasks[0].id).to be(2)
57
- expect(user.all_tasks[0].title).to eql('Jane Task')
58
- expect(user.all_tasks[0].tags.size).to be(1)
59
- expect(user.all_tasks[0].tags[0].name).to eql('red')
60
- end
61
-
62
- it 'loads nested combined relations using configured associations' do
63
- jane = repo.users_with_posts_and_their_labels.first
64
-
65
- expect(jane.posts.size).to be(1)
66
- expect(jane.posts.map(&:title)).to eql(['Hello From Jane'])
67
- expect(jane.posts.flat_map(&:labels).flat_map(&:name)).to eql(%w(red blue))
68
- end
69
-
70
- it 'loads a wrapped relation' do
71
- expect(repo.tag_with_wrapped_task.first).to eql(tag_with_task)
72
- end
73
-
74
- it 'loads wraps using aliased relation' do
75
- author = repo.users.where(name: 'Jane').one
76
-
77
- repo.command(:create, repo.books).(title: 'Hello World', author_id: author.id)
78
-
79
- book = repo.books.wrap(:author).to_a.first
80
-
81
- expect(book.author.id).to eql(author.id)
82
- expect(book.author.name).to eql(author.name)
83
- end
84
-
85
- it 'loads multiple wraps' do
86
- post_label = repo.posts_labels.wrap(:post).wrap(:label).to_a.first
87
-
88
- expect(post_label.label_id).to be(post_label.label.id)
89
- expect(post_label.post_id).to be(post_label.post.id)
90
- end
91
-
92
- it 'loads an aggregate via custom fks' do
93
- jane = repo.aggregate(many: repo.posts).where(name: 'Jane').one
94
-
95
- expect(jane.posts.size).to be(1)
96
- expect(jane.posts.first.title).to eql('Hello From Jane')
97
- end
98
-
99
- it 'loads an aggregate via assoc name' do
100
- jane = repo.aggregate(:posts).where(name: 'Jane').one
101
-
102
- expect(jane.posts.size).to be(1)
103
- expect(jane.posts.first.title).to eql('Hello From Jane')
104
- end
105
-
106
- it 'loads an aggregate via assoc options' do
107
- jane = repo.aggregate(posts: :labels).where(name: 'Jane').one
108
-
109
- expect(jane.name).to eql('Jane')
110
- expect(jane.posts.size).to be(1)
111
- expect(jane.posts.first.title).to eql('Hello From Jane')
112
- expect(jane.posts[0].labels.size).to be(2)
113
- expect(jane.posts[0].labels[0].name).to eql('red')
114
- expect(jane.posts[0].labels[1].name).to eql('blue')
115
- end
116
-
117
- it 'loads an aggregate with multiple assoc options' do
118
- jane = repo.aggregate(:labels, posts: :labels).where(name: 'Jane').one
119
-
120
- expect(jane.name).to eql('Jane')
121
-
122
- expect(jane.labels.size).to be(2)
123
- expect(jane.labels[0].name).to eql('red')
124
- expect(jane.labels[1].name).to eql('blue')
125
-
126
- expect(jane.posts.size).to be(1)
127
- expect(jane.posts[0].title).to eql('Hello From Jane')
128
-
129
- expect(jane.posts[0].labels.size).to be(2)
130
- expect(jane.posts[0].labels[0].name).to eql('red')
131
- expect(jane.posts[0].labels[1].name).to eql('blue')
132
- end
133
-
134
- it 'loads an aggregate with deeply nested assoc options' do
135
- jane = repo.aggregate(posts: [{ author: :labels }]).where(name: 'Jane').one
136
-
137
- expect(jane.posts.size).to be(1)
138
- expect(jane.posts[0].title).to eql('Hello From Jane')
139
-
140
- expect(jane.posts[0].author.id).to eql(jane.id)
141
- expect(jane.posts[0].author.labels.size).to be(2)
142
- expect(jane.posts[0].author.labels[0].name).to eql('red')
143
- expect(jane.posts[0].author.labels[1].name).to eql('blue')
144
- end
145
-
146
- it 'loads an aggregate with multiple associations' do
147
- jane = repo.aggregate(:posts, :labels).where(name: 'Jane').one
148
-
149
- expect(jane.posts.size).to be(1)
150
- expect(jane.posts.first.title).to eql('Hello From Jane')
151
-
152
- expect(jane.labels.size).to be(2)
153
- expect(jane.labels[0].name).to eql('red')
154
- expect(jane.labels[1].name).to eql('blue')
155
- end
156
-
157
- it 'loads children and its parents via wrap_parent' do
158
- posts = repo.posts.wrap_parent(author: repo.users)
159
-
160
- label = repo.labels.combine(many: { posts: posts }).first
161
-
162
- expect(label.name).to eql('red')
163
- expect(label.posts.size).to be(1)
164
- expect(label.posts[0].title).to eql('Hello From Jane')
165
- expect(label.posts[0].author.name).to eql('Jane')
166
- end
167
-
168
- it 'loads children and its parents via wrap and association name' do
169
- label = repo.labels.combine(many: { posts: repo.posts.wrap(:author) }).first
170
-
171
- expect(label.name).to eql('red')
172
- expect(label.posts.size).to be(1)
173
- expect(label.posts[0].title).to eql('Hello From Jane')
174
- expect(label.posts[0].author.name).to eql('Jane')
175
- end
176
-
177
- it 'loads a parent via custom fks' do
178
- post = repo.posts.combine(:author).where(title: 'Hello From Jane').one
179
-
180
- expect(post.title).to eql('Hello From Jane')
181
- expect(post.author.name).to eql('Jane')
182
- end
183
-
184
- it 'loads aggregate through many-to-many via custom options' do
185
- post = repo.posts
186
- .combine_children(many: repo.labels)
187
- .where(title: 'Hello From Jane')
188
- .one
189
-
190
- expect(post.title).to eql('Hello From Jane')
191
- expect(post.labels.size).to be(2)
192
- expect(post.labels.map(&:name)).to eql(%w(red blue))
193
- end
194
-
195
- it 'loads aggregate through many-to-many association' do
196
- post = repo.posts.combine(:labels).where(title: 'Hello From Jane').one
197
-
198
- expect(post.title).to eql('Hello From Jane')
199
- expect(post.labels.size).to be(2)
200
- expect(post.labels.map(&:name)).to eql(%w(red blue))
201
- end
202
-
203
- it 'loads multiple child relations' do
204
- user = repo.users.combine_children(many: [repo.posts, repo.tasks]).where(name: 'Jane').one
205
-
206
- expect(user.name).to eql('Jane')
207
- expect(user.posts.size).to be(1)
208
- expect(user.posts[0].title).to eql('Hello From Jane')
209
- expect(user.tasks.size).to be(1)
210
- expect(user.tasks[0].title).to eql('Jane Task')
211
- end
212
-
213
- it 'loads multiple parent relations' do
214
- post_label = repo.posts_labels.combine_parents(one: [repo.posts]).first
215
-
216
- expect(post_label.post.title).to eql('Hello From Jane')
217
- end
218
-
219
- context 'not common naming conventions' do
220
- it 'still loads nested relations' do
221
- comments = comments_repo.comments_with_likes.to_a
222
-
223
- expect(comments.size).to be(2)
224
- expect(comments[0].author).to eql('Jane')
225
- expect(comments[0].likes[0].author).to eql('Joe')
226
- expect(comments[0].likes[1].author).to eql('Anonymous')
227
- expect(comments[1].author).to eql('Joe')
228
- expect(comments[1].likes[0].author).to eql('Jane')
229
- end
230
-
231
- it 'loads nested relations by association name' do
232
- comments = comments_repo.comments_with_emotions.to_a
233
-
234
- expect(comments.size).to be(2)
235
- expect(comments[0].emotions[0].author).to eql('Joe')
236
- expect(comments[0].emotions[1].author).to eql('Anonymous')
237
- end
238
- end
239
-
240
- describe 'projecting virtual attributes' do
241
- before do
242
- ROM::Repository::StructBuilder.cache.clear
243
- ROM::Repository::MapperBuilder.cache.clear
244
- end
245
-
246
- shared_context 'auto-mapping' do
247
- it 'loads auto-mapped structs' do
248
- user = repo.users.
249
- inner_join(:posts, author_id: :id).
250
- select_group { [id.qualified, name.qualified] }.
251
- select_append { int::count(:posts).as(:post_count) }.
252
- having { count(id.qualified) >= 1 }.
253
- first
254
-
255
- expect(user.id).to be(1)
256
- expect(user.name).to eql('Jane')
257
- expect(user.post_count).to be(1)
258
- end
259
- end
260
-
261
- context 'with default namespace' do
262
- include_context 'auto-mapping'
263
- end
264
-
265
- context 'with custom struct namespace' do
266
- before do
267
- repo_class.struct_namespace(Test)
268
- end
269
-
270
- include_context 'auto-mapping'
271
-
272
- it 'uses custom namespace' do
273
- expect(Test.const_defined?(:User)).to be(false)
274
- user = repo.users.limit(1).one!
275
-
276
- expect(user.name).to eql('Jane')
277
- expect(user.class).to be < Test::User
278
- expect(user.class.name).to eql(Test::User.name)
279
- end
280
- end
281
- end
282
-
283
- describe 'projecting aliased attributes' do
284
- it 'loads auto-mapped structs' do
285
- user = repo.users.select { [id.aliased(:userId), name.aliased(:userName)] }.first
286
-
287
- expect(user.userId).to be(1)
288
- expect(user.userName).to eql('Jane')
289
- end
290
- end
291
-
292
- context 'with a table without columns' do
293
- before { conn.create_table(:dummy) unless conn.table_exists?(:dummy) }
294
-
295
- it 'does not fail with a weird error when a relation does not have attributes' do
296
- configuration.relation(:dummy) { schema(infer: true) }
297
-
298
- repo = Class.new(ROM::Repository[:dummy]).new(rom)
299
- expect(repo.dummy.to_a).to eql([])
300
- end
301
- end
302
-
303
- describe 'mapping without structs' do
304
- shared_context 'plain hash mapping' do
305
- describe '#one' do
306
- it 'returns a hash' do
307
- expect(repo.users.limit(1).one).to eql(id: 1, name: 'Jane')
308
- end
309
-
310
- it 'returns a nested hash for an aggregate' do
311
- expect(repo.aggregate(:posts).limit(1).one).
312
- to eql(id: 1, name: 'Jane', posts: [{ id: 1, author_id: 1, title: 'Hello From Jane', body: 'Jane Post'}])
313
- end
314
- end
315
- end
316
-
317
- context 'with auto_struct disabled upon initialization' do
318
- subject(:repo) do
319
- repo_class.new(rom, auto_struct: false)
320
- end
321
-
322
- include_context 'plain hash mapping'
323
- end
324
-
325
- context 'with auto_struct disabled at the class level' do
326
- before do
327
- repo_class.auto_struct(false)
328
- end
329
-
330
- include_context 'plain hash mapping'
331
- end
332
- end
333
-
334
- describe 'using custom mappers along with auto-mapping' do
335
- before do
336
- configuration.mappers do
337
- define(:users) do
338
- register_as :embed_address
339
-
340
- def call(rel)
341
- rel.map { |tuple| Hash(tuple).merge(mapped: true) }
342
- end
343
- end
344
-
345
- define(:posts) do
346
- register_as :nested_mapper
347
-
348
- def call(rel)
349
- rel.map { |tuple| Hash(tuple).tap { |h| h[:title] = h[:title].upcase } }
350
- end
351
- end
352
- end
353
- end
354
-
355
- it 'auto-maps and applies a custom mapper' do
356
- jane = repo.users.combine(:posts).map_with(:embed_address, auto_map: true).to_a.first
357
-
358
- expect(jane).
359
- to eql(id:1, name: 'Jane', mapped: true, posts: [
360
- { id: 1, author_id: 1, title: 'Hello From Jane', body: 'Jane Post' }
361
- ])
362
- end
363
-
364
- it 'applies a custom mapper inside #node' do
365
- jane = repo.aggregate(:posts).node(:posts) { |posts| posts.as(:nested_mapper, auto_map: true) }.to_a.first
366
-
367
- expect(jane).to be_a ROM::Struct
368
-
369
- expect(jane.to_h).
370
- to eql(id:1, name: 'Jane', posts: [
371
- { id: 1, author_id: 1, title: 'HELLO FROM JANE', body: 'Jane Post' }
372
- ])
373
- end
374
- end
375
-
376
- describe 'using a custom model for a node' do
377
- before do
378
- class Test::Post < OpenStruct; end
379
- end
380
-
381
- it 'uses provided model for the member type' do
382
- jane = repo.users.combine(many: repo.posts.as(Test::Post)).where(name: 'Jane').one
383
-
384
- expect(jane.name).to eql('Jane')
385
- expect(jane.posts.size).to be(1)
386
- expect(jane.posts[0]).to be_instance_of(Test::Post)
387
- expect(jane.posts[0].title).to eql('Hello From Jane')
388
- expect(jane.posts[0].body).to eql('Jane Post')
389
- end
390
-
391
- it 'uses provided model for the attribute type' do
392
- jane = repo.users.combine(one: repo.posts.as(Test::Post)).where(name: 'Jane').one
393
-
394
- expect(jane.name).to eql('Jane')
395
- expect(jane.post).to be_instance_of(Test::Post)
396
- expect(jane.post.title).to eql('Hello From Jane')
397
- expect(jane.post.body).to eql('Jane Post')
398
- end
399
- end
400
-
401
- it 'loads structs using plain SQL' do
402
- jane = repo.users.read("SELECT name FROM users WHERE name = 'Jane'").one
403
-
404
- expect(jane.name).to eql('Jane')
405
- end
406
- end