rom-mapper 0.5.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/LICENSE +1 -1
- data/README.md +1 -9
- data/lib/rom/header/attribute.rb +2 -0
- data/lib/rom/mapper/builder.rb +37 -0
- data/lib/rom/mapper/configuration_plugin.rb +26 -0
- data/lib/rom/mapper/mapper_dsl.rb +43 -0
- data/lib/rom/mapper/version.rb +1 -1
- data/lib/rom/mapper.rb +2 -2
- data/lib/rom/mapper_compiler.rb +71 -0
- data/lib/rom/open_struct.rb +35 -0
- data/lib/rom/processor/transproc.rb +6 -2
- data/lib/rom/struct.rb +113 -0
- data/lib/rom/struct_compiler.rb +104 -0
- data/lib/rom/transformer.rb +32 -0
- data/lib/rom-mapper.rb +1 -1
- metadata +17 -28
- data/.gitignore +0 -19
- data/.rspec +0 -3
- data/.ruby-gemset +0 -1
- data/.travis.yml +0 -23
- data/Gemfile +0 -31
- data/Guardfile +0 -19
- data/Rakefile +0 -16
- data/rakelib/benchmark.rake +0 -15
- data/rakelib/mutant.rake +0 -16
- data/rakelib/rubocop.rake +0 -18
- data/rom-mapper.gemspec +0 -22
- data/spec/integration/mapper_spec.rb +0 -113
- data/spec/spec_helper.rb +0 -57
- data/spec/support/constant_leak_finder.rb +0 -14
- data/spec/support/mutant.rb +0 -10
- data/spec/unit/rom/mapper/dsl_spec.rb +0 -479
- data/spec/unit/rom/mapper/model_dsl_spec.rb +0 -19
- data/spec/unit/rom/mapper_spec.rb +0 -83
- data/spec/unit/rom/processor/transproc_spec.rb +0 -506
@@ -1,506 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'virtus'
|
3
|
-
|
4
|
-
RSpec.describe ROM::Processor::Transproc do
|
5
|
-
subject(:transproc) { ROM::Processor::Transproc.build(binding, header) }
|
6
|
-
|
7
|
-
let(:binding) { nil }
|
8
|
-
let(:header) { ROM::Header.coerce(attributes, options) }
|
9
|
-
let(:options) { {} }
|
10
|
-
|
11
|
-
context 'no mapping' do
|
12
|
-
let(:attributes) { [[:name]] }
|
13
|
-
let(:relation) { [{ name: 'Jane' }, { name: 'Joe' }] }
|
14
|
-
|
15
|
-
it 'returns tuples' do
|
16
|
-
expect(transproc[relation]).to eql(relation)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
context 'coercing values' do
|
21
|
-
let(:attributes) { [[:name, type: :string], [:age, type: :integer]] }
|
22
|
-
let(:relation) { [{ name: :Jane, age: '1' }, { name: :Joe, age: '2' }] }
|
23
|
-
|
24
|
-
it 'returns tuples' do
|
25
|
-
expect(transproc[relation]).to eql([
|
26
|
-
{ name: 'Jane', age: 1 }, { name: 'Joe', age: 2 }
|
27
|
-
])
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
context 'mapping to object' do
|
32
|
-
let(:options) { { model: model } }
|
33
|
-
|
34
|
-
let(:model) do
|
35
|
-
Class.new do
|
36
|
-
include Virtus.value_object
|
37
|
-
values { attribute :name }
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
let(:attributes) { [[:name]] }
|
42
|
-
let(:relation) { [{ name: 'Jane' }, { name: 'Joe' }] }
|
43
|
-
|
44
|
-
it 'returns tuples' do
|
45
|
-
expect(transproc[relation]).to eql([
|
46
|
-
model.new(name: 'Jane'), model.new(name: 'Joe')
|
47
|
-
])
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
context 'renaming keys' do
|
52
|
-
let(:attributes) do
|
53
|
-
[[:name, from: 'name']]
|
54
|
-
end
|
55
|
-
|
56
|
-
let(:options) do
|
57
|
-
{ reject_keys: true }
|
58
|
-
end
|
59
|
-
|
60
|
-
let(:relation) do
|
61
|
-
[
|
62
|
-
{ 'name' => 'Jane', 'age' => 21 }, { 'name' => 'Joe', age: 22 }
|
63
|
-
]
|
64
|
-
end
|
65
|
-
|
66
|
-
it 'returns tuples with rejected keys' do
|
67
|
-
expect(transproc[relation]).to eql([{ name: 'Jane' }, { name: 'Joe' }])
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
context 'copying keys' do
|
72
|
-
let(:options) do
|
73
|
-
{ copy_keys: true }
|
74
|
-
end
|
75
|
-
|
76
|
-
let(:attributes) do
|
77
|
-
[['b', from: 'a'], ['c', from: 'b']]
|
78
|
-
end
|
79
|
-
|
80
|
-
let(:relation) do
|
81
|
-
[{ 'a' => 'copy' }]
|
82
|
-
end
|
83
|
-
|
84
|
-
it 'copies without removing the original' do
|
85
|
-
expect(transproc[relation]).to eql([{ 'a' => 'copy', 'b' => 'copy', 'c' => 'copy' }])
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
context 'key from existing keys' do
|
90
|
-
let(:attributes) do
|
91
|
-
coercer = ->(a, b) { b + a }
|
92
|
-
[[:c, { from: [:a, :b], coercer: coercer }]]
|
93
|
-
end
|
94
|
-
|
95
|
-
let(:relation) do
|
96
|
-
[
|
97
|
-
{ a: 'works', b: 'this' }
|
98
|
-
]
|
99
|
-
end
|
100
|
-
|
101
|
-
let(:expected_result) do
|
102
|
-
[
|
103
|
-
{ c: 'thisworks' }
|
104
|
-
]
|
105
|
-
end
|
106
|
-
|
107
|
-
let(:copy_keys_expected_result) do
|
108
|
-
[
|
109
|
-
{ a: 'works', b: 'this', c: 'thisworks'}
|
110
|
-
]
|
111
|
-
end
|
112
|
-
|
113
|
-
it 'returns tuples a new key added based on exsiting keys' do
|
114
|
-
expect(transproc[relation]).to eql(expected_result)
|
115
|
-
end
|
116
|
-
|
117
|
-
it 'raises a configuration exception if coercer block does not exist' do
|
118
|
-
attributes[0][1][:coercer] = nil
|
119
|
-
expect { transproc[relation] }.to raise_error(ROM::MapperMisconfiguredError)
|
120
|
-
end
|
121
|
-
|
122
|
-
it 'honors the copy_keys option' do
|
123
|
-
options.merge!({ copy_keys: true })
|
124
|
-
expect(transproc[relation]).to eql(copy_keys_expected_result)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
describe 'rejecting keys' do
|
129
|
-
let(:options) { { reject_keys: true } }
|
130
|
-
|
131
|
-
let(:attributes) do
|
132
|
-
[
|
133
|
-
['name'],
|
134
|
-
['tasks', type: :array, group: true, header: [['title']]]
|
135
|
-
]
|
136
|
-
end
|
137
|
-
|
138
|
-
let(:relation) do
|
139
|
-
[
|
140
|
-
{ 'name' => 'Jane', 'age' => 21, 'title' => 'Task One' },
|
141
|
-
{ 'name' => 'Jane', 'age' => 21, 'title' => 'Task Two' },
|
142
|
-
{ 'name' => 'Joe', 'age' => 22, 'title' => 'Task One' }
|
143
|
-
]
|
144
|
-
end
|
145
|
-
|
146
|
-
it 'returns tuples with unknown keys rejected' do
|
147
|
-
expect(transproc[relation]).to eql([
|
148
|
-
{ 'name' => 'Jane',
|
149
|
-
'tasks' => [{ 'title' => 'Task One' }, { 'title' => 'Task Two' }] },
|
150
|
-
{ 'name' => 'Joe',
|
151
|
-
'tasks' => [{ 'title' => 'Task One' }] }
|
152
|
-
])
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
context 'mapping nested hash' do
|
157
|
-
let(:relation) do
|
158
|
-
[
|
159
|
-
{ 'name' => 'Jane', 'task' => { 'title' => 'Task One' } },
|
160
|
-
{ 'name' => 'Joe', 'task' => { 'title' => 'Task Two' } }
|
161
|
-
]
|
162
|
-
end
|
163
|
-
|
164
|
-
context 'when no mapping is needed' do
|
165
|
-
let(:attributes) { [['name'], ['task', type: :hash, header: [[:title]]]] }
|
166
|
-
|
167
|
-
it 'returns tuples' do
|
168
|
-
expect(transproc[relation]).to eql(relation)
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
context 'with deeply nested hashes' do
|
173
|
-
context 'when no renaming is required' do
|
174
|
-
let(:relation) do
|
175
|
-
[
|
176
|
-
{ 'user' => { 'name' => 'Jane', 'task' => { 'title' => 'Task One' } } },
|
177
|
-
{ 'user' => { 'name' => 'Joe', 'task' => { 'title' => 'Task Two' } } }
|
178
|
-
]
|
179
|
-
end
|
180
|
-
|
181
|
-
let(:attributes) do
|
182
|
-
[[
|
183
|
-
'user', type: :hash, header: [
|
184
|
-
['name'],
|
185
|
-
['task', type: :hash, header: [['title']]]
|
186
|
-
]
|
187
|
-
]]
|
188
|
-
end
|
189
|
-
|
190
|
-
it 'returns tuples' do
|
191
|
-
expect(transproc[relation]).to eql(relation)
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
context 'when renaming is required' do
|
196
|
-
let(:relation) do
|
197
|
-
[
|
198
|
-
{ user: { name: 'Jane', task: { title: 'Task One' } } },
|
199
|
-
{ user: { name: 'Joe', task: { title: 'Task Two' } } }
|
200
|
-
]
|
201
|
-
end
|
202
|
-
|
203
|
-
let(:attributes) do
|
204
|
-
[[
|
205
|
-
'user', type: :hash, header: [
|
206
|
-
['name'],
|
207
|
-
['task', type: :hash, header: [['title']]]
|
208
|
-
]
|
209
|
-
]]
|
210
|
-
end
|
211
|
-
|
212
|
-
it 'returns tuples' do
|
213
|
-
expect(transproc[relation]).to eql(relation)
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
context 'renaming keys' do
|
219
|
-
context 'when only hash needs renaming' do
|
220
|
-
let(:attributes) do
|
221
|
-
[
|
222
|
-
['name'],
|
223
|
-
[:task, from: 'task', type: :hash, header: [[:title, from: 'title']]]
|
224
|
-
]
|
225
|
-
end
|
226
|
-
|
227
|
-
it 'returns tuples with key renamed in the nested hash' do
|
228
|
-
expect(transproc[relation]).to eql([
|
229
|
-
{ 'name' => 'Jane', :task => { title: 'Task One' } },
|
230
|
-
{ 'name' => 'Joe', :task => { title: 'Task Two' } }
|
231
|
-
])
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
context 'when all attributes need renaming' do
|
236
|
-
let(:attributes) do
|
237
|
-
[
|
238
|
-
[:name, from: 'name'],
|
239
|
-
[:task, from: 'task', type: :hash, header: [[:title, from: 'title']]]
|
240
|
-
]
|
241
|
-
end
|
242
|
-
|
243
|
-
it 'returns tuples with key renamed in the nested hash' do
|
244
|
-
expect(transproc[relation]).to eql([
|
245
|
-
{ name: 'Jane', task: { title: 'Task One' } },
|
246
|
-
{ name: 'Joe', task: { title: 'Task Two' } }
|
247
|
-
])
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
context 'wrapping tuples' do
|
254
|
-
let(:relation) do
|
255
|
-
[
|
256
|
-
{ 'name' => 'Jane', 'title' => 'Task One' },
|
257
|
-
{ 'name' => 'Joe', 'title' => 'Task Two' }
|
258
|
-
]
|
259
|
-
end
|
260
|
-
|
261
|
-
context 'when no mapping is needed' do
|
262
|
-
let(:attributes) do
|
263
|
-
[
|
264
|
-
['name'],
|
265
|
-
['task', type: :hash, wrap: true, header: [['title']]]
|
266
|
-
]
|
267
|
-
end
|
268
|
-
|
269
|
-
it 'returns wrapped tuples' do
|
270
|
-
expect(transproc[relation]).to eql([
|
271
|
-
{ 'name' => 'Jane', 'task' => { 'title' => 'Task One' } },
|
272
|
-
{ 'name' => 'Joe', 'task' => { 'title' => 'Task Two' } }
|
273
|
-
])
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
context 'with deeply wrapped tuples' do
|
278
|
-
let(:attributes) do
|
279
|
-
[
|
280
|
-
['user', type: :hash, wrap: true, header: [
|
281
|
-
['name'],
|
282
|
-
['task', type: :hash, wrap: true, header: [['title']]]
|
283
|
-
]]
|
284
|
-
]
|
285
|
-
end
|
286
|
-
|
287
|
-
it 'returns wrapped tuples' do
|
288
|
-
expect(transproc[relation]).to eql([
|
289
|
-
{ 'user' => { 'name' => 'Jane', 'task' => { 'title' => 'Task One' } } },
|
290
|
-
{ 'user' => { 'name' => 'Joe', 'task' => { 'title' => 'Task Two' } } }
|
291
|
-
])
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
context 'renaming keys' do
|
296
|
-
context 'when only wrapped tuple requires renaming' do
|
297
|
-
let(:attributes) do
|
298
|
-
[
|
299
|
-
['name'],
|
300
|
-
['task', type: :hash, wrap: true, header: [[:title, from: 'title']]]
|
301
|
-
]
|
302
|
-
end
|
303
|
-
|
304
|
-
it 'returns wrapped tuples with renamed keys' do
|
305
|
-
expect(transproc[relation]).to eql([
|
306
|
-
{ 'name' => 'Jane', 'task' => { title: 'Task One' } },
|
307
|
-
{ 'name' => 'Joe', 'task' => { title: 'Task Two' } }
|
308
|
-
])
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
context 'when all attributes require renaming' do
|
313
|
-
let(:attributes) do
|
314
|
-
[
|
315
|
-
[:name, from: 'name'],
|
316
|
-
[:task, type: :hash, wrap: true, header: [[:title, from: 'title']]]
|
317
|
-
]
|
318
|
-
end
|
319
|
-
|
320
|
-
it 'returns wrapped tuples with all keys renamed' do
|
321
|
-
expect(transproc[relation]).to eql([
|
322
|
-
{ name: 'Jane', task: { title: 'Task One' } },
|
323
|
-
{ name: 'Joe', task: { title: 'Task Two' } }
|
324
|
-
])
|
325
|
-
end
|
326
|
-
end
|
327
|
-
end
|
328
|
-
end
|
329
|
-
|
330
|
-
context 'unwrapping tuples' do
|
331
|
-
let(:relation) do
|
332
|
-
[
|
333
|
-
{ 'user' => { 'name' => 'Leo', 'task' => { 'title' => 'Task 1' } } },
|
334
|
-
{ 'user' => { 'name' => 'Joe', 'task' => { 'title' => 'Task 2' } } }
|
335
|
-
]
|
336
|
-
end
|
337
|
-
|
338
|
-
context 'when no mapping is needed' do
|
339
|
-
let(:attributes) do
|
340
|
-
[
|
341
|
-
['user', type: :hash, unwrap: true, header: [['name'], ['task']]]
|
342
|
-
]
|
343
|
-
end
|
344
|
-
|
345
|
-
it 'returns unwrapped tuples' do
|
346
|
-
expect(transproc[relation]).to eql([
|
347
|
-
{ 'name' => 'Leo', 'task' => { 'title' => 'Task 1' } },
|
348
|
-
{ 'name' => 'Joe', 'task' => { 'title' => 'Task 2' } }
|
349
|
-
])
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
context 'partially' do
|
354
|
-
context 'without renaming the rest of the wrap' do
|
355
|
-
let(:attributes) do
|
356
|
-
[
|
357
|
-
['user', type: :hash, unwrap: true, header: [['task']]]
|
358
|
-
]
|
359
|
-
end
|
360
|
-
|
361
|
-
it 'returns unwrapped tuples' do
|
362
|
-
expect(transproc[relation]).to eql([
|
363
|
-
{ 'user' => { 'name' => 'Leo' }, 'task' => { 'title' => 'Task 1' } },
|
364
|
-
{ 'user' => { 'name' => 'Joe' }, 'task' => { 'title' => 'Task 2' } }
|
365
|
-
])
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
|
-
context 'with renaming the rest of the wrap' do
|
370
|
-
let(:attributes) do
|
371
|
-
[
|
372
|
-
['man', from: 'user', type: :hash, unwrap: true, header: [['task']]]
|
373
|
-
]
|
374
|
-
end
|
375
|
-
|
376
|
-
it 'returns unwrapped tuples' do
|
377
|
-
expect(transproc[relation]).to eql([
|
378
|
-
{ 'man' => { 'name' => 'Leo' }, 'task' => { 'title' => 'Task 1' } },
|
379
|
-
{ 'man' => { 'name' => 'Joe' }, 'task' => { 'title' => 'Task 2' } }
|
380
|
-
])
|
381
|
-
end
|
382
|
-
end
|
383
|
-
end
|
384
|
-
|
385
|
-
context 'deeply' do
|
386
|
-
let(:attributes) do
|
387
|
-
[
|
388
|
-
['user', type: :hash, unwrap: true, header: [
|
389
|
-
['name'],
|
390
|
-
['title'],
|
391
|
-
['task', type: :hash, unwrap: true, header: [['title']]]
|
392
|
-
]]
|
393
|
-
]
|
394
|
-
end
|
395
|
-
|
396
|
-
it 'returns unwrapped tuples' do
|
397
|
-
expect(transproc[relation]).to eql([
|
398
|
-
{ 'name' => 'Leo', 'title' => 'Task 1' },
|
399
|
-
{ 'name' => 'Joe', 'title' => 'Task 2' }
|
400
|
-
])
|
401
|
-
end
|
402
|
-
end
|
403
|
-
end
|
404
|
-
|
405
|
-
context 'grouping tuples' do
|
406
|
-
let(:relation) do
|
407
|
-
[
|
408
|
-
{ 'name' => 'Jane', 'title' => 'Task One' },
|
409
|
-
{ 'name' => 'Jane', 'title' => 'Task Two' },
|
410
|
-
{ 'name' => 'Joe', 'title' => 'Task One' },
|
411
|
-
{ 'name' => 'Joe', 'title' => nil }
|
412
|
-
]
|
413
|
-
end
|
414
|
-
|
415
|
-
context 'when no mapping is needed' do
|
416
|
-
let(:attributes) do
|
417
|
-
[
|
418
|
-
['name'],
|
419
|
-
['tasks', type: :array, group: true, header: [['title']]]
|
420
|
-
]
|
421
|
-
end
|
422
|
-
|
423
|
-
it 'returns wrapped tuples with all keys renamed' do
|
424
|
-
expect(transproc[relation]).to eql([
|
425
|
-
{ 'name' => 'Jane',
|
426
|
-
'tasks' => [{ 'title' => 'Task One' }, { 'title' => 'Task Two' }] },
|
427
|
-
{ 'name' => 'Joe',
|
428
|
-
'tasks' => [{ 'title' => 'Task One' }] }
|
429
|
-
])
|
430
|
-
end
|
431
|
-
end
|
432
|
-
|
433
|
-
context 'renaming keys' do
|
434
|
-
context 'when only grouped tuple requires renaming' do
|
435
|
-
let(:attributes) do
|
436
|
-
[
|
437
|
-
['name'],
|
438
|
-
['tasks', type: :array, group: true, header: [[:title, from: 'title']]]
|
439
|
-
]
|
440
|
-
end
|
441
|
-
|
442
|
-
it 'returns grouped tuples with renamed keys' do
|
443
|
-
expect(transproc[relation]).to eql([
|
444
|
-
{ 'name' => 'Jane',
|
445
|
-
'tasks' => [{ title: 'Task One' }, { title: 'Task Two' }] },
|
446
|
-
{ 'name' => 'Joe',
|
447
|
-
'tasks' => [{ title: 'Task One' }] }
|
448
|
-
])
|
449
|
-
end
|
450
|
-
end
|
451
|
-
|
452
|
-
context 'when all attributes require renaming' do
|
453
|
-
let(:attributes) do
|
454
|
-
[
|
455
|
-
[:name, from: 'name'],
|
456
|
-
[:tasks, type: :array, group: true, header: [[:title, from: 'title']]]
|
457
|
-
]
|
458
|
-
end
|
459
|
-
|
460
|
-
it 'returns grouped tuples with all keys renamed' do
|
461
|
-
expect(transproc[relation]).to eql([
|
462
|
-
{ name: 'Jane',
|
463
|
-
tasks: [{ title: 'Task One' }, { title: 'Task Two' }] },
|
464
|
-
{ name: 'Joe',
|
465
|
-
tasks: [{ title: 'Task One' }] }
|
466
|
-
])
|
467
|
-
end
|
468
|
-
end
|
469
|
-
end
|
470
|
-
|
471
|
-
context 'nested grouping' do
|
472
|
-
let(:relation) do
|
473
|
-
[
|
474
|
-
{ name: 'Jane', title: 'Task One', tag: 'red' },
|
475
|
-
{ name: 'Jane', title: 'Task One', tag: 'green' },
|
476
|
-
{ name: 'Joe', title: 'Task One', tag: 'blue' }
|
477
|
-
]
|
478
|
-
end
|
479
|
-
|
480
|
-
let(:attributes) do
|
481
|
-
[
|
482
|
-
[:name],
|
483
|
-
[:tasks, type: :array, group: true, header: [
|
484
|
-
[:title],
|
485
|
-
[:tags, type: :array, group: true, header: [[:tag]]]
|
486
|
-
]]
|
487
|
-
]
|
488
|
-
end
|
489
|
-
|
490
|
-
it 'returns deeply grouped tuples' do
|
491
|
-
expect(transproc[relation]).to eql([
|
492
|
-
{ name: 'Jane',
|
493
|
-
tasks: [
|
494
|
-
{ title: 'Task One', tags: [{ tag: 'red' }, { tag: 'green' }] }
|
495
|
-
]
|
496
|
-
},
|
497
|
-
{ name: 'Joe',
|
498
|
-
tasks: [
|
499
|
-
{ title: 'Task One', tags: [{ tag: 'blue' }] }
|
500
|
-
]
|
501
|
-
}
|
502
|
-
])
|
503
|
-
end
|
504
|
-
end
|
505
|
-
end
|
506
|
-
end
|