praxis 2.0.pre.29 → 2.0.pre.30

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +24 -0
  5. data/SELECTOR_NOTES.txt +0 -0
  6. data/lib/praxis/application.rb +4 -0
  7. data/lib/praxis/blueprint.rb +13 -1
  8. data/lib/praxis/blueprint_attribute_group.rb +29 -0
  9. data/lib/praxis/docs/open_api/schema_object.rb +8 -7
  10. data/lib/praxis/endpoint_definition.rb +1 -1
  11. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +11 -11
  12. data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +0 -1
  13. data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +1 -1
  14. data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +54 -4
  15. data/lib/praxis/extensions/pagination/ordering_params.rb +38 -10
  16. data/lib/praxis/extensions/pagination/pagination_handler.rb +3 -3
  17. data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +1 -1
  18. data/lib/praxis/mapper/resource.rb +155 -14
  19. data/lib/praxis/mapper/selector_generator.rb +248 -46
  20. data/lib/praxis/media_type_identifier.rb +1 -1
  21. data/lib/praxis/multipart/part.rb +2 -2
  22. data/lib/praxis/plugins/mapper_plugin.rb +4 -3
  23. data/lib/praxis/renderer.rb +1 -1
  24. data/lib/praxis/routing_config.rb +1 -1
  25. data/lib/praxis/tasks/console.rb +21 -26
  26. data/lib/praxis/types/multipart_array.rb +1 -1
  27. data/lib/praxis/version.rb +1 -1
  28. data/lib/praxis.rb +1 -0
  29. data/praxis.gemspec +1 -1
  30. data/spec/functional_library_spec.rb +187 -0
  31. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +11 -1
  32. data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +16 -4
  33. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +0 -2
  34. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +0 -2
  35. data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +111 -25
  36. data/spec/praxis/extensions/pagination/ordering_params_spec.rb +70 -0
  37. data/spec/praxis/mapper/resource_spec.rb +40 -4
  38. data/spec/praxis/mapper/selector_generator_spec.rb +979 -296
  39. data/spec/praxis/request_stages/action_spec.rb +1 -1
  40. data/spec/spec_app/app/controllers/authors.rb +37 -0
  41. data/spec/spec_app/app/controllers/books.rb +31 -0
  42. data/spec/spec_app/app/resources/author.rb +21 -0
  43. data/spec/spec_app/app/resources/base.rb +14 -0
  44. data/spec/spec_app/app/resources/book.rb +43 -0
  45. data/spec/spec_app/app/resources/tag.rb +9 -0
  46. data/spec/spec_app/app/resources/tagging.rb +9 -0
  47. data/spec/spec_app/config/environment.rb +16 -1
  48. data/spec/spec_app/design/media_types/author.rb +13 -0
  49. data/spec/spec_app/design/media_types/book.rb +22 -0
  50. data/spec/spec_app/design/media_types/tag.rb +11 -0
  51. data/spec/spec_app/design/media_types/tagging.rb +10 -0
  52. data/spec/spec_app/design/resources/authors.rb +35 -0
  53. data/spec/spec_app/design/resources/books.rb +39 -0
  54. data/spec/spec_helper.rb +0 -1
  55. data/spec/support/spec_resources.rb +20 -7
  56. data/spec/{praxis/extensions/support → support}/spec_resources_active_model.rb +14 -0
  57. metadata +24 -7
  58. /data/spec/{functional_spec.rb → functional_cloud_spec.rb} +0 -0
  59. /data/spec/{praxis/extensions/support → support}/spec_resources_sequel.rb +0 -0
@@ -9,134 +9,471 @@ describe Praxis::Mapper::SelectorGenerator do
9
9
  context '#add' do
10
10
  let(:resource) { SimpleResource }
11
11
  shared_examples 'a proper selector' do
12
- it { expect(generator.add(resource, fields).selectors.dump).to be_deep_equal selectors }
12
+ it do
13
+ dumped = generator.add(resource, fields).selectors.dump
14
+ # puts JSON.pretty_generate(dumped)
15
+ expect(dumped).to be_deep_equal selectors
16
+ end
13
17
  end
14
18
 
15
- context 'basic combos' do
16
- context 'direct column fields' do
17
- let(:fields) { { id: true, foobar: true } }
18
- let(:selectors) do
19
- {
20
- model: SimpleModel,
21
- columns: %i[id foobar]
22
- }
23
- end
24
- it_behaves_like 'a proper selector'
19
+ shared_examples 'a proper fields selector' do
20
+ it do
21
+ result = generator.add(resource, fields).selectors
22
+ dumped = result.dump(mode: :fields)
23
+ # puts JSON.pretty_generate(dumped)
24
+ expect(dumped).to be_deep_equal selectors
25
25
  end
26
+ end
26
27
 
27
- context 'aliased column fields' do
28
- let(:fields) { { id: true, name: true } }
29
- let(:selectors) do
30
- {
31
- model: SimpleModel,
32
- columns: %i[id simple_name]
33
- }
28
+ context 'selecting columns and tracks' do
29
+ context '#add' do
30
+ let(:resource) { SimpleResource }
31
+ shared_examples 'a proper selector' do
32
+ it { expect(generator.add(resource, fields).selectors.dump).to be_deep_equal selectors }
34
33
  end
35
- it_behaves_like 'a proper selector'
36
- end
37
-
38
- context 'pure associations without recursion' do
39
- let(:fields) { { other_model: true } }
40
- let(:selectors) do
41
- {
42
- model: SimpleModel,
43
- columns: [:other_model_id], # FK of the other_model association
44
- tracks: {
45
- other_model: {
46
- columns: [:id], # joining key for the association
47
- model: OtherModel
34
+ context 'basic combos' do
35
+ context 'direct column fields' do
36
+ let(:fields) { { id: true, foobar: true } }
37
+ let(:selectors) do
38
+ {
39
+ model: SimpleModel,
40
+ columns: %i[id foobar]
48
41
  }
49
- }
50
- }
42
+ end
43
+ it_behaves_like 'a proper selector'
44
+ end
45
+ context 'aliased column fields' do
46
+ let(:fields) { { id: true, name: true } }
47
+ let(:selectors) do
48
+ {
49
+ model: SimpleModel,
50
+ columns: %i[id simple_name]
51
+ }
52
+ end
53
+ it_behaves_like 'a proper selector'
54
+ end
55
+ context 'pure associations without recursion' do
56
+ let(:fields) { { other_model: true } }
57
+ let(:selectors) do
58
+ {
59
+ model: SimpleModel,
60
+ columns: [:other_model_id], # FK of the other_model association
61
+ tracks: {
62
+ other_model: {
63
+ columns: [:id], # joining key for the association
64
+ model: OtherModel
65
+ }
66
+ }
67
+ }
68
+ end
69
+ it_behaves_like 'a proper selector'
70
+ end
71
+ context 'aliased associations without recursion' do
72
+ let(:fields) { { other_resource: true } }
73
+ let(:selectors) do
74
+ {
75
+ model: SimpleModel,
76
+ columns: [:other_model_id], # FK of the other_model association
77
+ tracks: {
78
+ other_model: {
79
+ columns: [:id], # joining key for the association
80
+ model: OtherModel
81
+ }
82
+ }
83
+ }
84
+ end
85
+ it_behaves_like 'a proper selector'
86
+ end
87
+ context 'aliased associations without recursion (that map to columns and other associations)' do
88
+ let(:fields) { { aliased_method: true } }
89
+ let(:selectors) do
90
+ {
91
+ model: SimpleModel,
92
+ columns: %i[column1 other_model_id], # other_model_id => because of the association
93
+ tracks: {
94
+ other_model: {
95
+ columns: [:id], # joining key for the association
96
+ model: OtherModel
97
+ }
98
+ }
99
+ }
100
+ end
101
+ it_behaves_like 'a proper selector'
102
+ end
103
+ context 'redefined associations that add some extra columns (would need both the underlying association AND the columns in place)' do
104
+ let(:fields) { { parent: true } }
105
+ let(:selectors) do
106
+ {
107
+ model: SimpleModel,
108
+ columns: %i[parent_id added_column],
109
+ tracks: {
110
+ parent: {
111
+ columns: [:id],
112
+ model: ParentModel
113
+ }
114
+ }
115
+ }
116
+ end
117
+ it_behaves_like 'a proper selector'
118
+ end
119
+ context 'a simple property that requires all fields' do
120
+ let(:fields) { { everything: true } }
121
+ let(:selectors) do
122
+ {
123
+ model: SimpleModel,
124
+ columns: [:*]
125
+ }
126
+ end
127
+ it_behaves_like 'a proper selector'
128
+ end
129
+ context 'a simple property that requires itself' do
130
+ let(:fields) { { circular_dep: true } }
131
+ let(:selectors) do
132
+ {
133
+ model: SimpleModel,
134
+ columns: %i[circular_dep column1] # allows to "expand" the dependency into itself + others
135
+ }
136
+ end
137
+ it_behaves_like 'a proper selector'
138
+ end
139
+ context 'a simple property without dependencies' do
140
+ let(:fields) { { no_deps: true } }
141
+ let(:selectors) do
142
+ {
143
+ model: SimpleModel
144
+ }
145
+ end
146
+ it_behaves_like 'a proper selector'
147
+ end
51
148
  end
52
- it_behaves_like 'a proper selector'
53
- end
54
-
55
- context 'aliased associations without recursion' do
56
- let(:fields) { { other_resource: true } }
57
- let(:selectors) do
58
- {
59
- model: SimpleModel,
60
- columns: [:other_model_id], # FK of the other_model association
61
- tracks: {
62
- other_model: {
63
- columns: [:id], # joining key for the association
64
- model: OtherModel
149
+ context 'nested tracking' do
150
+ context 'pure associations follow the nested fields' do
151
+ let(:fields) do
152
+ {
153
+ other_model: {
154
+ id: true
155
+ }
65
156
  }
66
- }
67
- }
157
+ end
158
+ let(:selectors) do
159
+ {
160
+ model: SimpleModel,
161
+ columns: [:other_model_id],
162
+ tracks: {
163
+ other_model: {
164
+ model: OtherModel,
165
+ columns: [:id]
166
+ }
167
+ }
168
+ }
169
+ end
170
+ it_behaves_like 'a proper selector'
171
+ end
172
+ context 'Aliased resources disregard any nested fields...' do
173
+ let(:fields) do
174
+ {
175
+ other_resource: {
176
+ id: true
177
+ }
178
+ }
179
+ end
180
+ let(:selectors) do
181
+ {
182
+ model: SimpleModel,
183
+ columns: [:other_model_id],
184
+ tracks: {
185
+ other_model: {
186
+ model: OtherModel,
187
+ columns: [:id]
188
+ }
189
+ }
190
+ }
191
+ end
192
+ it_behaves_like 'a proper selector'
193
+ end
194
+ context 'merging multiple tracks with the same name within a node' do
195
+ let(:fields) do
196
+ { # Both everything_from_parent and parent will track the underlying 'parent' assoc
197
+ # ...and the final respective fields and tracks will need to be merged together.
198
+ # columns will be merged by just *, and tracks will merge true with simple children
199
+ everything_from_parent: true,
200
+ parent: {
201
+ simple_children: true
202
+ }
203
+ }
204
+ end
205
+ let(:selectors) do
206
+ {
207
+ model: SimpleModel,
208
+ columns: %i[parent_id added_column],
209
+ tracks: {
210
+ parent: {
211
+ model: ParentModel,
212
+ columns: [:*],
213
+ tracks: {
214
+ simple_children: {
215
+ model: SimpleModel,
216
+ columns: [:parent_id]
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ end
223
+ it_behaves_like 'a proper selector'
224
+ end
225
+ context 'Aliased underlying associations follows any nested fields...' do
226
+ let(:fields) do
227
+ {
228
+ parent_id: true,
229
+ aliased_association: {
230
+ display_name: true
231
+ }
232
+ }
233
+ end
234
+ let(:selectors) do
235
+ {
236
+ model: SimpleModel,
237
+ columns: %i[other_model_id parent_id],
238
+ tracks: {
239
+ other_model: {
240
+ model: OtherModel,
241
+ columns: %i[id name]
242
+ }
243
+ }
244
+ }
245
+ end
246
+ it_behaves_like 'a proper selector'
247
+ end
248
+ context 'Deep aliased underlying associations also follows any nested fields at the end of the chain...' do
249
+ let(:fields) do
250
+ {
251
+ parent_id: true,
252
+ deep_aliased_association: {
253
+ name: true
254
+ }
255
+ }
256
+ end
257
+ # TODO: Legitimate failure!! there is a name in the parent column track...which shouldn't be there
258
+ let(:selectors) do
259
+ {
260
+ model: SimpleModel,
261
+ columns: %i[parent_id],
262
+ tracks: {
263
+ parent: {
264
+ model: ParentModel,
265
+ columns: %i[id],
266
+ tracks: {
267
+ simple_children: {
268
+ model: SimpleModel,
269
+ columns: %i[parent_id simple_name]
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+ end
276
+ it_behaves_like 'a proper selector'
277
+ end
278
+ context 'Using self for the underlying association: follows any nested fields skipping the association name and still applies dependencies' do
279
+ let(:fields) do
280
+ {
281
+ parent_id: true,
282
+ sub_struct: {
283
+ display_name: true
284
+ }
285
+ }
286
+ end
287
+ let(:selectors) do
288
+ {
289
+ model: SimpleModel,
290
+ # Parent_id is because we asked for it at the top
291
+ # display_name because we asked for it under sub_struct, but it is marked as :self
292
+ # alway_necessary_attribute because it is a dependency of sub_struct
293
+ columns: %i[parent_id display_name]
294
+ }
295
+ end
296
+ it_behaves_like 'a proper selector'
297
+ end
68
298
  end
69
- it_behaves_like 'a proper selector'
70
- end
71
- context 'aliased associations without recursion (that map to columns and other associations)' do
72
- let(:fields) { { aliased_method: true } }
73
- let(:selectors) do
74
- {
75
- model: SimpleModel,
76
- columns: %i[column1 other_model_id], # other_model_id => because of the association
77
- tracks: {
78
- other_model: {
79
- columns: [:id], # joining key for the association
80
- model: OtherModel
299
+ context 'string associations' do
300
+ context 'that specify a direct existing colum in the target dependency' do
301
+ let(:fields) { { direct_other_name: true } }
302
+ let(:selectors) do
303
+ {
304
+ model: SimpleModel,
305
+ columns: [:other_model_id],
306
+ tracks: {
307
+ other_model: {
308
+ model: OtherModel,
309
+ columns: %i[id name]
310
+ }
311
+ }
81
312
  }
82
- }
83
- }
313
+ end
314
+ it_behaves_like 'a proper selector'
315
+ end
316
+ context 'that specify an aliased property in the target dependency' do
317
+ let(:fields) { { aliased_other_name: true } }
318
+ let(:selectors) do
319
+ {
320
+ model: SimpleModel,
321
+ columns: [:other_model_id],
322
+ tracks: {
323
+ other_model: {
324
+ model: OtherModel,
325
+ columns: %i[id name]
326
+ }
327
+ }
328
+ }
329
+ end
330
+ it_behaves_like 'a proper selector'
331
+ end
332
+
333
+ context 'for a property that requires all fields from an association' do
334
+ let(:fields) { { everything_from_parent: true } }
335
+ let(:selectors) do
336
+ {
337
+ model: SimpleModel,
338
+ columns: [:parent_id],
339
+ tracks: {
340
+ parent: {
341
+ model: ParentModel,
342
+ columns: [:*]
343
+ }
344
+ }
345
+ }
346
+ end
347
+ it_behaves_like 'a proper selector'
348
+ end
84
349
  end
85
- it_behaves_like 'a proper selector'
86
- end
87
350
 
88
- context 'redefined associations that add some extra columns (would need both the underlying association AND the columns in place)' do
89
- let(:fields) { { parent: true } }
90
- let(:selectors) do
91
- {
92
- model: SimpleModel,
93
- columns: %i[parent_id added_column],
94
- tracks: {
95
- parent: {
96
- columns: [:id],
97
- model: ParentModel
351
+ context 'required extra select fields due to associations' do
352
+ context 'many_to_one' do
353
+ let(:fields) { { other_model: true } }
354
+ let(:selectors) do
355
+ {
356
+ model: SimpleModel,
357
+ columns: [:other_model_id], # FK of the other_model association
358
+ tracks: {
359
+ other_model: {
360
+ columns: [:id],
361
+ model: OtherModel
362
+ }
363
+ }
98
364
  }
99
- }
100
- }
365
+ end
366
+ it_behaves_like 'a proper selector'
367
+ end
368
+ context 'one_to_many' do
369
+ let(:resource) { ParentResource }
370
+ let(:fields) { { simple_children: true } }
371
+ let(:selectors) do
372
+ {
373
+ model: ParentModel,
374
+ columns: [:id], # No FKs in the source model for one_to_many
375
+ tracks: {
376
+ simple_children: {
377
+ columns: [:parent_id],
378
+ model: SimpleModel
379
+ }
380
+ }
381
+ }
382
+ end
383
+ it_behaves_like 'a proper selector'
384
+ end
385
+ context 'many_to_many' do
386
+ let(:resource) { OtherResource }
387
+ let(:fields) { { simple_models: true } }
388
+ let(:selectors) do
389
+ {
390
+ model: OtherModel,
391
+ columns: [:id], # join key in the source model for many_to_many (where the middle table points to)
392
+ tracks: {
393
+ simple_models: {
394
+ columns: [:id], # join key in the target model for many_to_many (where the middle table points to)
395
+ model: SimpleModel
396
+ }
397
+ }
398
+ }
399
+ end
400
+ it_behaves_like 'a proper selector'
401
+ end
402
+
403
+ context 'that are several attriutes deep' do
404
+ let(:fields) { { deep_nested_deps: true } }
405
+ let(:selectors) do
406
+ {
407
+ model: SimpleModel,
408
+ columns: [:parent_id],
409
+ tracks: {
410
+ parent: {
411
+ model: ParentModel,
412
+ columns: [:id], # No FKs in the source model for one_to_many
413
+ tracks: {
414
+ simple_children: {
415
+ columns: %i[parent_id other_model_id],
416
+ model: SimpleModel,
417
+ tracks: {
418
+ other_model: {
419
+ model: OtherModel,
420
+ columns: %i[id parent_id],
421
+ tracks: {
422
+ parent: {
423
+ model: ParentModel,
424
+ columns: %i[id simple_name other_attribute]
425
+ }
426
+ }
427
+ }
428
+ }
429
+ }
430
+ }
431
+ }
432
+ }
433
+ }
434
+ end
435
+ it_behaves_like 'a proper selector'
436
+ end
101
437
  end
102
- it_behaves_like 'a proper selector'
103
438
  end
439
+ end
104
440
 
105
- context 'a simple property that requires all fields' do
106
- let(:fields) { { everything: true } }
107
- let(:selectors) do
441
+ context 'selecting fields and subfields' do
442
+ context 'with nil dependencies' do
443
+ let(:fields) do
108
444
  {
109
- model: SimpleModel,
110
- columns: [:*]
445
+ nil_deps: true
111
446
  }
112
447
  end
113
- it_behaves_like 'a proper selector'
114
- end
115
-
116
- context 'a simple property that requires itself' do
117
- let(:fields) { { circular_dep: true } }
118
448
  let(:selectors) do
119
449
  {
120
450
  model: SimpleModel,
121
- columns: %i[circular_dep column1] # allows to "expand" the dependency into itself + others
451
+ fields: {
452
+ nil_deps: {
453
+ deps: %i[nil_deps]
454
+ }
455
+ }
122
456
  }
123
457
  end
124
- it_behaves_like 'a proper selector'
458
+ it_behaves_like 'a proper fields selector'
125
459
  end
126
-
127
- context 'a simple property without dependencies' do
460
+ context 'with empty array dependencies' do
128
461
  let(:fields) { { no_deps: true } }
129
462
  let(:selectors) do
130
463
  {
131
- model: SimpleModel
464
+ model: SimpleModel,
465
+ fields: {
466
+ no_deps: {
467
+ deps: %i[no_deps]
468
+ }
469
+ }
132
470
  }
133
471
  end
134
- it_behaves_like 'a proper selector'
472
+ it_behaves_like 'a proper fields selector'
135
473
  end
136
- end
137
-
138
- context 'nested tracking' do
139
- context 'pure associations follow the nested fields' do
474
+ context 'following nested fields from pure implicit association properties (not defined and no deps)' do
475
+ # For implicit properties that aren't defined, but that have a name that matches an association
476
+ # we want to make it work like an as: association, therefore subfield following will work.
140
477
  let(:fields) do
141
478
  {
142
479
  other_model: {
@@ -147,276 +484,622 @@ describe Praxis::Mapper::SelectorGenerator do
147
484
  let(:selectors) do
148
485
  {
149
486
  model: SimpleModel,
150
- columns: [:other_model_id],
487
+ fields: {
488
+ other_model: {
489
+ references: 'Linked to resource: OtherResource'
490
+ }
491
+ },
151
492
  tracks: {
152
493
  other_model: {
153
494
  model: OtherModel,
154
- columns: [:id]
495
+ fields: {
496
+ id: {
497
+ deps: %i[id]
498
+ }
499
+ }
155
500
  }
156
501
  }
157
502
  }
158
503
  end
159
- it_behaves_like 'a proper selector'
504
+ it_behaves_like 'a proper fields selector'
160
505
  end
161
-
162
- context 'Aliased resources disregard any nested fields...' do
163
- let(:fields) do
164
- {
165
- other_resource: {
166
- id: true
506
+ context 'implicit (not defined) properties that are direct associations can follow fields' do
507
+ context 'using a direct association name' do
508
+ let(:fields) do
509
+ {
510
+ other_model: true
167
511
  }
168
- }
169
- end
170
- let(:selectors) do
171
- {
172
- model: SimpleModel,
173
- columns: [:other_model_id],
174
- tracks: {
175
- other_model: {
176
- model: OtherModel,
177
- columns: [:id]
512
+ end
513
+ let(:selectors) do
514
+ {
515
+ model: SimpleModel,
516
+ fields: {
517
+ other_model: {
518
+ references: 'Linked to resource: OtherResource' # because other_model is implicit, without deps
519
+ }
520
+ },
521
+ tracks: {
522
+ other_model: {
523
+ model: OtherModel,
524
+ fields: {
525
+ id: { deps: %i[id] }
526
+ }
527
+ }
178
528
  }
179
529
  }
180
- }
530
+ end
531
+ it_behaves_like 'a proper fields selector'
181
532
  end
182
- it_behaves_like 'a proper selector'
183
533
  end
534
+ context 'properties with dependencies' do
535
+ context 'simple direct column name' do
536
+ let(:fields) do
537
+ {
538
+ simple_name: true
539
+ }
540
+ end
541
+ let(:selectors) do
542
+ {
543
+ model: SimpleModel,
544
+ fields: {
545
+ simple_name: {
546
+ deps: %i[simple_name]
547
+ }
548
+ }
549
+ }
550
+ end
551
+ it_behaves_like 'a proper fields selector'
552
+ end
553
+ context 'needing to resolve dependencies recursively' do
554
+ let(:fields) do
555
+ {
556
+ name: true
557
+ }
558
+ end
559
+ let(:selectors) do
560
+ {
561
+ model: SimpleModel,
562
+ fields: {
563
+ name: {
564
+ deps: %i[name nested_name simple_name]
565
+ }
566
+ }
567
+ }
568
+ end
569
+ it_behaves_like 'a proper fields selector'
570
+ end
184
571
 
185
- context 'merging multiple tracks with the same name within a node' do
186
- let(:fields) do
187
- { # Both everything_from_parent and parent will track the underlying 'parent' assoc
188
- # ...and the final respective fields and tracks will need to be merged together.
189
- # columns will be merged by just *, and tracks will merge true with simple children
190
- everything_from_parent: true,
191
- parent: {
192
- simple_children: true
572
+ context 'a simple property that requires all fields' do
573
+ let(:fields) { { everything: true } }
574
+ let(:selectors) do
575
+ {
576
+ model: SimpleModel,
577
+ fields: {
578
+ everything: {
579
+ deps: %i[everything]
580
+ }
581
+ }
193
582
  }
194
- }
583
+ end
584
+ it_behaves_like 'a proper fields selector'
195
585
  end
196
- let(:selectors) do
197
- {
198
- model: SimpleModel,
199
- columns: %i[parent_id added_column],
200
- tracks: {
201
- parent: {
202
- model: ParentModel,
203
- columns: [:*],
204
- tracks: {
205
- simple_children: {
206
- model: SimpleModel,
207
- columns: [:parent_id]
586
+ context 'self referential' do
587
+ let(:fields) do
588
+ {
589
+ circular_dep: true
590
+ }
591
+ end
592
+ let(:selectors) do
593
+ {
594
+ model: SimpleModel,
595
+ fields: {
596
+ circular_dep: {
597
+ deps: %i[circular_dep column1]
598
+ }
599
+ }
600
+ }
601
+ end
602
+ it_behaves_like 'a proper fields selector'
603
+ end
604
+ context 'combining dependencies with other properties and associacions (stops at the property)' do
605
+ # There won't be any references or anything as it is a property with dependencies...so we just
606
+ # unroll them, and discard any nested fields (in this case there aren't any though)
607
+ let(:fields) do
608
+ {
609
+ aliased_method: true
610
+ }
611
+ end
612
+ let(:selectors) do
613
+ {
614
+ model: SimpleModel,
615
+ fields: {
616
+ aliased_method: {
617
+ deps: %i[aliased_method other_model column1]
618
+ }
619
+ },
620
+ tracks: {
621
+ other_model: {
622
+ model: OtherModel,
623
+ fields: {
624
+ id: { deps: %i[id] }
208
625
  }
209
626
  }
210
627
  }
211
628
  }
212
- }
629
+ end
630
+ it_behaves_like 'a proper fields selector'
213
631
  end
214
- it_behaves_like 'a proper selector'
215
- end
216
- context 'Aliased underlying associations follows any nested fields...' do
217
- let(:fields) do
218
- {
219
- parent_id: true,
220
- aliased_association: {
221
- display_name: true
632
+ context 'following nested fields from a property that has dependencies (does not work, same result as above)' do
633
+ # This means we will track up to the property name (nothing under that will be reflected in fields, even references)
634
+ let(:fields) do
635
+ {
636
+ aliased_method: {
637
+ display_name: true
638
+ }
222
639
  }
223
- }
640
+ end
641
+ let(:selectors) do
642
+ {
643
+ model: SimpleModel,
644
+ fields: {
645
+ aliased_method: {
646
+ deps: %i[aliased_method other_model column1]
647
+ }
648
+ },
649
+ tracks: {
650
+ other_model: {
651
+ model: OtherModel,
652
+ fields: {
653
+ id: {
654
+ deps: %i[id] # There is no further tracking of display name here...since it is not an as: property
655
+ }
656
+ }
657
+ }
658
+ }
659
+ }
660
+ end
661
+ it_behaves_like 'a proper fields selector'
224
662
  end
225
- let(:selectors) do
226
- {
227
- model: SimpleModel,
228
- columns: %i[other_model_id parent_id simple_name],
229
- tracks: {
230
- other_model: {
231
- model: OtherModel,
232
- columns: %i[id name]
663
+ context 'a true substructure object' do
664
+ # once we hit true_struct, we know it is not a property group, so we'll bring ALL the inner dependencies
665
+ # even if the fields required are only a subset of it
666
+ let(:fields) do
667
+ {
668
+ true_struct: true
669
+ }
670
+ end
671
+ let(:selectors) do
672
+ {
673
+ model: SimpleModel,
674
+ fields: {
675
+ true_struct: {
676
+ deps: %i[true_struct name nested_name simple_name sub_id inner_sub_id id]
677
+ }
233
678
  }
234
679
  }
235
- }
680
+ end
681
+ it_behaves_like 'a proper fields selector'
682
+ end
683
+ context 'string associations' do
684
+ context 'that specify a direct existing colum in the target dependency' do
685
+ let(:fields) { { direct_other_name: true } }
686
+ let(:selectors) do
687
+ {
688
+ model: SimpleModel,
689
+ fields: {
690
+ direct_other_name: {
691
+ deps: %i[direct_other_name]
692
+ }
693
+ },
694
+ tracks: {
695
+ other_model: {
696
+ model: OtherModel,
697
+ fields: {
698
+ id: {
699
+ deps: %i[id]
700
+ },
701
+ name: {
702
+ deps: %i[name]
703
+ }
704
+ }
705
+ }
706
+ }
707
+ }
708
+ end
709
+ it_behaves_like 'a proper fields selector'
710
+ end
711
+ context 'that specify an aliased property in the target dependency' do
712
+ let(:fields) { { aliased_other_name: true } }
713
+ let(:selectors) do
714
+ {
715
+ model: SimpleModel,
716
+ fields: {
717
+ aliased_other_name: {
718
+ deps: %i[aliased_other_name]
719
+ }
720
+ },
721
+ tracks: {
722
+ other_model: {
723
+ model: OtherModel,
724
+ fields: {
725
+ id: {
726
+ deps: %i[id]
727
+ },
728
+ display_name: {
729
+ deps: %i[display_name name]
730
+ }
731
+ }
732
+ }
733
+ }
734
+ }
735
+ end
736
+ it_behaves_like 'a proper fields selector'
737
+ end
738
+ context 'for a property that requires all fields from an association' do
739
+ let(:fields) { { everything_from_parent: true } }
740
+ let(:selectors) do
741
+ {
742
+ model: SimpleModel,
743
+ fields: {
744
+ everything_from_parent: {
745
+ deps: %i[everything_from_parent]
746
+ }
747
+ },
748
+ tracks: {
749
+ parent: {
750
+ model: ParentModel
751
+ }
752
+ }
753
+ }
754
+ end
755
+ it_behaves_like 'a proper fields selector'
756
+ end
236
757
  end
237
- it_behaves_like 'a proper selector'
238
758
  end
239
- context 'Deep aliased underlying associations also follows any nested fields at the end of the chain...' do
240
- let(:fields) do
241
- {
242
- parent_id: true,
243
- deep_aliased_association: {
244
- name: true
759
+ context 'entire forwarding (i.e., association_name: true) associations (as: aliases)' do
760
+ context 'using a forwarding association name with 1 level deep' do
761
+ let(:fields) do
762
+ {
763
+ aliased_association: true
245
764
  }
246
- }
765
+ end
766
+ let(:selectors) do
767
+ {
768
+ model: SimpleModel,
769
+ fields: {
770
+ aliased_association: {
771
+ deps: [:aliased_association],
772
+ references: 'Linked to resource: OtherResource'
773
+ }
774
+ },
775
+ tracks: {
776
+ other_model: {
777
+ model: OtherModel,
778
+ fields: {
779
+ id: {
780
+ deps: %i[id]
781
+ }
782
+ }
783
+ }
784
+ }
785
+ }
786
+ end
787
+ it_behaves_like 'a proper fields selector'
247
788
  end
248
- let(:selectors) do
249
- {
250
- model: SimpleModel,
251
- columns: %i[parent_id simple_name], # No added_column, as it does not follow the dotted reference as properties, just associations
252
- tracks: {
253
- parent: {
254
- model: ParentModel,
255
- columns: %i[id],
256
- tracks: {
257
- simple_children: {
258
- model: SimpleModel,
259
- columns: %i[parent_id simple_name]
789
+
790
+ context 'using a forwarding association name with 2 levels deep' do
791
+ let(:fields) do
792
+ {
793
+ deep_aliased_association: true
794
+ }
795
+ end
796
+ let(:selectors) do
797
+ {
798
+ model: SimpleModel,
799
+ fields: {
800
+ deep_aliased_association: {
801
+ deps: [:deep_aliased_association],
802
+ references: 'Linked to resource: SimpleResource'
803
+ }
804
+ },
805
+ tracks: {
806
+ parent: {
807
+ model: ParentModel,
808
+ fields: {
809
+ id: {
810
+ deps: %i[id]
811
+ }
812
+ },
813
+ tracks: {
814
+ simple_children: {
815
+ model: SimpleModel,
816
+ fields: {
817
+ parent_id: {
818
+ deps: %i[parent_id]
819
+ }
820
+ }
821
+ }
260
822
  }
261
823
  }
262
824
  }
263
825
  }
264
- }
826
+ end
827
+ it_behaves_like 'a proper fields selector'
828
+ end
829
+ context 'using a self forwarding association ' do
830
+ let(:fields) do
831
+ {
832
+ sub_struct: true
833
+ }
834
+ end
835
+ let(:selectors) do
836
+ {
837
+ model: SimpleModel,
838
+ fields: {
839
+ sub_struct: {
840
+ deps: %i[sub_struct]
841
+ }
842
+ }
843
+ }
844
+ end
845
+ it_behaves_like 'a proper fields selector'
265
846
  end
266
- it_behaves_like 'a proper selector'
267
847
  end
268
- context 'Using self for the underlying association: follows any nested fields skipping the association name and still applies dependencies' do
269
- let(:fields) do
270
- {
271
- parent_id: true,
272
- sub_struct: {
273
- display_name: true
848
+ context 'forwarding associations (as: aliases) but specifying sub-fields' do
849
+ context 'using a self forwarding association name with 2 levels deep' do
850
+ let(:fields) do
851
+ {
852
+ sub_struct: {
853
+ name: true
854
+ }
274
855
  }
275
- }
856
+ end
857
+ let(:selectors) do
858
+ {
859
+ model: SimpleModel,
860
+ fields: {
861
+ sub_struct: {
862
+ deps: %i[sub_struct],
863
+ fields: {
864
+ name: {
865
+ deps: %i[name nested_name simple_name]
866
+ }
867
+ }
868
+ }
869
+ }
870
+ }
871
+ end
872
+ it_behaves_like 'a proper fields selector'
276
873
  end
277
- let(:selectors) do
278
- {
279
- model: SimpleModel,
280
- # Parent_id is because we asked for it at the top
281
- # display_name because we asked for it under sub_struct, but it is marked as :self
282
- # alway_necessary_attribute because it is a dependency of sub_struct
283
- columns: %i[parent_id display_name alway_necessary_attribute]
284
- }
874
+ context 'using a self forwarding association name with multiple levels which include deep forwarding associations' do
875
+ let(:fields) do
876
+ {
877
+ sub_struct: {
878
+ name: true,
879
+ deep_aliased_association: true
880
+ }
881
+ }
882
+ end
883
+ let(:selectors) do
884
+ {
885
+ model: SimpleModel,
886
+ fields: {
887
+ sub_struct: {
888
+ deps: %i[sub_struct],
889
+ fields: {
890
+ name: {
891
+ deps: %i[name nested_name simple_name]
892
+ },
893
+ deep_aliased_association: {
894
+ deps: %i[deep_aliased_association],
895
+ references: 'Linked to resource: SimpleResource'
896
+ }
897
+ }
898
+ }
899
+ },
900
+ tracks: {
901
+ parent: {
902
+ model: ParentModel,
903
+ fields: {
904
+ id: {
905
+ deps: %i[id]
906
+ }
907
+ },
908
+ tracks: {
909
+ simple_children: {
910
+ model: SimpleModel,
911
+ fields: {
912
+ parent_id: {
913
+ deps: %i[parent_id]
914
+ }
915
+ }
916
+ }
917
+ }
918
+ }
919
+ }
920
+ }
921
+ end
922
+ it_behaves_like 'a proper fields selector'
285
923
  end
286
- it_behaves_like 'a proper selector'
287
924
  end
288
- end
289
-
290
- context 'string associations' do
291
- context 'that specify a direct existing colum in the target dependency' do
292
- let(:fields) { { direct_other_name: true } }
293
- let(:selectors) do
294
- {
295
- model: SimpleModel,
296
- columns: [:other_model_id],
297
- tracks: {
298
- other_model: {
299
- model: OtherModel,
300
- columns: %i[id name]
925
+ context 'property groups' do
926
+ context 'a property group substructure object, but asking only 1 of the subfields' do
927
+ let(:resource) { Resources::Book }
928
+ let(:fields) do
929
+ {
930
+ grouped: {
931
+ # id: true,
932
+ name: true
933
+ # moar_tags
301
934
  }
302
935
  }
303
- }
936
+ end
937
+ let(:selectors) do
938
+ {
939
+ model: ::ActiveBook,
940
+ fields: {
941
+ grouped: {
942
+ deps: %i[grouped],
943
+ fields: {
944
+ name: {
945
+ deps: %i[grouped_name name nested_name simple_name]
946
+ }
947
+ }
948
+ # NO id or group_id or tags of any sort should be traversed and appear, as we didn't ask for them
949
+ }
950
+ }
951
+ }
952
+ end
953
+ it_behaves_like 'a proper fields selector'
304
954
  end
305
- it_behaves_like 'a proper selector'
306
- end
307
955
 
308
- context 'that specify an aliased property in the target dependency' do
309
- let(:fields) { { aliased_other_name: true } }
310
- let(:selectors) do
311
- {
312
- model: SimpleModel,
313
- columns: [:other_model_id],
314
- tracks: {
315
- other_model: {
316
- model: OtherModel,
317
- columns: %i[id name]
956
+ context 'a property group substructure object, but asking only a subset of fields, going deep in an association' do
957
+ let(:resource) { Resources::Book }
958
+ let(:fields) do
959
+ {
960
+ grouped: {
961
+ id: true,
962
+ # name: true,
963
+ moar_tags: {
964
+ # name: true,
965
+ label: true
966
+ }
318
967
  }
319
968
  }
320
- }
969
+ end
970
+ let(:selectors) do
971
+ {
972
+ model: ::ActiveBook,
973
+ fields: {
974
+ grouped: {
975
+ deps: %i[grouped],
976
+ fields: {
977
+ id: {
978
+ deps: %i[grouped_id id]
979
+ },
980
+ moar_tags: {
981
+ deps: %i[grouped_moar_tags],
982
+ references: 'Linked to resource: Resources::Tag'
983
+ }
984
+ }
985
+ # NO name of any other name related things
986
+ }
987
+ },
988
+ tracks: {
989
+ tags: {
990
+ model: ActiveTag,
991
+ fields: {
992
+ id: {
993
+ deps: %i[id]
994
+ },
995
+ label: {
996
+ deps: %i[label]
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+ end
1003
+ it_behaves_like 'a proper fields selector'
321
1004
  end
322
- it_behaves_like 'a proper selector'
323
1005
  end
324
-
325
- context 'for a property that requires all fields from an association' do
326
- let(:fields) { { everything_from_parent: true } }
327
- let(:selectors) do
328
- {
329
- model: SimpleModel,
330
- columns: [:parent_id],
331
- tracks: {
332
- parent: {
333
- model: ParentModel,
334
- columns: [:*]
1006
+ context 'true structs' do
1007
+ context 'with any named subattributes' do
1008
+ # once we hit true_struct, we know it is not a property group, so we'll bring ALL the inner dependencies
1009
+ # even if the fields required are only a subset of it
1010
+ let(:fields) do
1011
+ {
1012
+ true_struct: {
1013
+ sub_id: {
1014
+ sub_sub_id: true
1015
+ }
335
1016
  }
336
1017
  }
337
- }
1018
+ end
1019
+ let(:selectors) do
1020
+ {
1021
+ model: SimpleModel,
1022
+ fields: {
1023
+ true_struct: {
1024
+ deps: %i[true_struct name nested_name simple_name sub_id inner_sub_id id]
1025
+ }
1026
+ }
1027
+ }
1028
+ end
1029
+ it_behaves_like 'a proper fields selector'
338
1030
  end
339
- it_behaves_like 'a proper selector'
340
- end
341
- end
342
1031
 
343
- context 'required extra select fields due to associations' do
344
- context 'many_to_one' do
345
- let(:fields) { { other_model: true } }
346
- let(:selectors) do
347
- {
348
- model: SimpleModel,
349
- columns: [:other_model_id], # FK of the other_model association
350
- tracks: {
351
- other_model: {
352
- columns: [:id],
353
- model: OtherModel
1032
+ context 'a true substructure object with property names prefixed like a property group (is still treated as a struct loading it all)' do
1033
+ let(:fields) do
1034
+ {
1035
+ agroup: {
1036
+ id: true
354
1037
  }
355
1038
  }
356
- }
357
- end
358
- it_behaves_like 'a proper selector'
359
- end
360
- context 'one_to_many' do
361
- let(:resource) { ParentResource }
362
- let(:fields) { { simple_children: true } }
363
- let(:selectors) do
364
- {
365
- model: ParentModel,
366
- columns: [:id], # No FKs in the source model for one_to_many
367
- tracks: {
368
- simple_children: {
369
- columns: [:parent_id],
370
- model: SimpleModel
1039
+ end
1040
+ let(:selectors) do
1041
+ {
1042
+ model: SimpleModel,
1043
+ fields: {
1044
+ agroup: {
1045
+ deps: %i[agroup agroup_id id agroup_name name nested_name simple_name]
1046
+ }
371
1047
  }
372
1048
  }
373
- }
1049
+ end
1050
+ it_behaves_like 'a proper fields selector'
374
1051
  end
375
- it_behaves_like 'a proper selector'
376
1052
  end
377
- context 'many_to_many' do
378
- let(:resource) { OtherResource }
379
- let(:fields) { { simple_models: true } }
380
- let(:selectors) do
1053
+ end
1054
+
1055
+ context 'Traversal of field deps experiments' do
1056
+ context 'using a self forwarding association name with multiple levels which include deep forwarding associations' do
1057
+ let(:fields) do
381
1058
  {
382
- model: OtherModel,
383
- columns: [:id], # join key in the source model for many_to_many (where the middle table points to)
384
- tracks: {
385
- simple_models: {
386
- columns: [:id], # join key in the target model for many_to_many (where the middle table points to)
387
- model: SimpleModel
1059
+ sub_struct: {
1060
+ name: true,
1061
+ deep_aliased_association: {
1062
+ nested_name: true
388
1063
  }
389
1064
  }
390
1065
  }
391
1066
  end
392
- it_behaves_like 'a proper selector'
393
- end
394
-
395
- context 'that are several attriutes deep' do
396
- let(:fields) { { deep_nested_deps: true } }
397
1067
  let(:selectors) do
398
1068
  {
399
1069
  model: SimpleModel,
400
- columns: [:parent_id],
1070
+ fields: {
1071
+ sub_struct: {
1072
+ deps: %i[sub_struct],
1073
+ fields: {
1074
+ name: {
1075
+ deps: %i[name nested_name simple_name]
1076
+ },
1077
+ deep_aliased_association: {
1078
+ deps: %i[deep_aliased_association],
1079
+ references: 'Linked to resource: SimpleResource'
1080
+ }
1081
+ }
1082
+ }
1083
+ },
401
1084
  tracks: {
402
1085
  parent: {
403
1086
  model: ParentModel,
404
- columns: [:id], # No FKs in the source model for one_to_many
1087
+ fields: {
1088
+ id: {
1089
+ deps: %i[id]
1090
+ }
1091
+ },
405
1092
  tracks: {
406
1093
  simple_children: {
407
- columns: %i[parent_id other_model_id],
408
1094
  model: SimpleModel,
409
- tracks: {
410
- other_model: {
411
- model: OtherModel,
412
- columns: %i[id parent_id],
413
- tracks: {
414
- parent: {
415
- model: ParentModel,
416
- columns: %i[id simple_name other_attribute]
417
- }
418
- }
1095
+ fields: {
1096
+ parent_id: {
1097
+ deps: %i[parent_id]
1098
+ },
1099
+ nested_name: {
1100
+ deps: %i[nested_name simple_name]
419
1101
  }
1102
+
420
1103
  }
421
1104
  }
422
1105
  }
@@ -424,7 +1107,7 @@ describe Praxis::Mapper::SelectorGenerator do
424
1107
  }
425
1108
  }
426
1109
  end
427
- it_behaves_like 'a proper selector'
1110
+ it_behaves_like 'a proper fields selector'
428
1111
  end
429
1112
  end
430
1113
  end