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

Sign up to get free protection for your applications and to get access to all the features.
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