rom 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +81 -0
  3. data/.travis.yml +2 -1
  4. data/CHANGELOG.md +41 -0
  5. data/Gemfile +12 -8
  6. data/Guardfile +17 -11
  7. data/README.md +7 -7
  8. data/Rakefile +29 -0
  9. data/lib/rom.rb +9 -66
  10. data/lib/rom/adapter.rb +45 -12
  11. data/lib/rom/adapter/memory.rb +0 -4
  12. data/lib/rom/adapter/memory/commands.rb +0 -10
  13. data/lib/rom/adapter/memory/dataset.rb +18 -6
  14. data/lib/rom/adapter/memory/storage.rb +0 -3
  15. data/lib/rom/command_registry.rb +24 -43
  16. data/lib/rom/commands.rb +5 -6
  17. data/lib/rom/commands/create.rb +5 -5
  18. data/lib/rom/commands/delete.rb +8 -6
  19. data/lib/rom/commands/result.rb +82 -0
  20. data/lib/rom/commands/update.rb +5 -4
  21. data/lib/rom/commands/with_options.rb +1 -4
  22. data/lib/rom/config.rb +70 -0
  23. data/lib/rom/env.rb +11 -3
  24. data/lib/rom/global.rb +107 -0
  25. data/lib/rom/header.rb +122 -89
  26. data/lib/rom/header/attribute.rb +148 -0
  27. data/lib/rom/mapper.rb +46 -67
  28. data/lib/rom/mapper_builder.rb +20 -73
  29. data/lib/rom/mapper_builder/mapper_dsl.rb +114 -0
  30. data/lib/rom/mapper_builder/model_dsl.rb +29 -0
  31. data/lib/rom/mapper_registry.rb +21 -0
  32. data/lib/rom/model_builder.rb +11 -17
  33. data/lib/rom/processor.rb +28 -0
  34. data/lib/rom/processor/transproc.rb +105 -0
  35. data/lib/rom/reader.rb +81 -21
  36. data/lib/rom/reader_builder.rb +14 -4
  37. data/lib/rom/relation.rb +19 -5
  38. data/lib/rom/relation_builder.rb +20 -6
  39. data/lib/rom/repository.rb +0 -2
  40. data/lib/rom/setup.rb +156 -0
  41. data/lib/rom/{boot → setup}/base_relation_dsl.rb +4 -8
  42. data/lib/rom/setup/command_dsl.rb +46 -0
  43. data/lib/rom/setup/finalize.rb +125 -0
  44. data/lib/rom/setup/mapper_dsl.rb +19 -0
  45. data/lib/rom/{boot → setup}/relation_dsl.rb +1 -4
  46. data/lib/rom/setup/schema_dsl.rb +33 -0
  47. data/lib/rom/support/registry.rb +10 -6
  48. data/lib/rom/version.rb +1 -1
  49. data/rom.gemspec +3 -1
  50. data/spec/integration/adapters/extending_relations_spec.rb +0 -2
  51. data/spec/integration/commands/create_spec.rb +2 -9
  52. data/spec/integration/commands/delete_spec.rb +4 -5
  53. data/spec/integration/commands/error_handling_spec.rb +4 -3
  54. data/spec/integration/commands/update_spec.rb +3 -8
  55. data/spec/integration/mappers/deep_embedded_spec.rb +52 -0
  56. data/spec/integration/mappers/definition_dsl_spec.rb +0 -118
  57. data/spec/integration/mappers/embedded_spec.rb +82 -0
  58. data/spec/integration/mappers/group_spec.rb +170 -0
  59. data/spec/integration/mappers/prefixing_attributes_spec.rb +2 -2
  60. data/spec/integration/mappers/renaming_attributes_spec.rb +8 -6
  61. data/spec/integration/mappers/symbolizing_attributes_spec.rb +80 -0
  62. data/spec/integration/mappers/wrap_spec.rb +162 -0
  63. data/spec/integration/multi_repo_spec.rb +64 -0
  64. data/spec/integration/relations/reading_spec.rb +12 -8
  65. data/spec/integration/relations/registry_dsl_spec.rb +1 -3
  66. data/spec/integration/schema_spec.rb +10 -0
  67. data/spec/integration/setup_spec.rb +57 -6
  68. data/spec/spec_helper.rb +2 -1
  69. data/spec/unit/config_spec.rb +60 -0
  70. data/spec/unit/rom/adapter/memory/dataset_spec.rb +52 -0
  71. data/spec/unit/rom/adapter_spec.rb +31 -11
  72. data/spec/unit/rom/header_spec.rb +60 -16
  73. data/spec/unit/rom/mapper_builder_spec.rb +311 -0
  74. data/spec/unit/rom/mapper_registry_spec.rb +25 -0
  75. data/spec/unit/rom/mapper_spec.rb +4 -5
  76. data/spec/unit/rom/model_builder_spec.rb +15 -13
  77. data/spec/unit/rom/processor/transproc_spec.rb +331 -0
  78. data/spec/unit/rom/reader_spec.rb +73 -0
  79. data/spec/unit/rom/registry_spec.rb +38 -0
  80. data/spec/unit/rom/relation_spec.rb +0 -1
  81. data/spec/unit/rom/setup_spec.rb +55 -0
  82. data/spec/unit/rom_spec.rb +14 -0
  83. metadata +62 -22
  84. data/Gemfile.devtools +0 -71
  85. data/lib/rom/boot.rb +0 -197
  86. data/lib/rom/boot/command_dsl.rb +0 -48
  87. data/lib/rom/boot/dsl.rb +0 -37
  88. data/lib/rom/boot/mapper_dsl.rb +0 -23
  89. data/lib/rom/boot/schema_dsl.rb +0 -27
  90. data/lib/rom/ra.rb +0 -172
  91. data/lib/rom/ra/operation/group.rb +0 -47
  92. data/lib/rom/ra/operation/join.rb +0 -39
  93. data/lib/rom/ra/operation/wrap.rb +0 -45
  94. data/lib/rom/transformer.rb +0 -77
  95. data/spec/integration/ra/group_spec.rb +0 -46
  96. data/spec/integration/ra/join_spec.rb +0 -50
  97. data/spec/integration/ra/wrap_spec.rb +0 -37
  98. data/spec/unit/rom/ra/operation/group_spec.rb +0 -55
  99. data/spec/unit/rom/ra/operation/wrap_spec.rb +0 -29
  100. data/spec/unit/rom/transformer_spec.rb +0 -41
@@ -4,7 +4,7 @@ require 'ostruct'
4
4
 
5
5
  describe ROM::Mapper do
6
6
  subject(:mapper) do
7
- ROM::Mapper.build(ROM::Header.coerce(relation.header.zip), user_model)
7
+ ROM::Mapper.build(ROM::Header.coerce(relation.header.zip, user_model))
8
8
  end
9
9
 
10
10
  let(:relation) do
@@ -13,7 +13,7 @@ describe ROM::Mapper do
13
13
 
14
14
  let(:dataset) do
15
15
  ROM::Adapter::Memory::Dataset.new(
16
- [{id: 1, name: 'Jane'}, {id: 2, name: 'Joe'}], [:id, :name]
16
+ [{ id: 1, name: 'Jane' }, { id: 2, name: 'Joe' }], [:id, :name]
17
17
  )
18
18
  end
19
19
 
@@ -28,12 +28,11 @@ describe ROM::Mapper do
28
28
  it "yields all mapped objects" do
29
29
  result = []
30
30
 
31
- relation.each do |tuple|
32
- result << mapper.load(tuple)
31
+ mapper.process(relation).each do |tuple|
32
+ result << tuple
33
33
  end
34
34
 
35
35
  expect(result).to eql([jane, joe])
36
36
  end
37
37
  end
38
-
39
38
  end
@@ -5,7 +5,7 @@ describe ROM::ModelBuilder do
5
5
  it 'builds a class with a constructor accepting attributes' do
6
6
  builder = ROM::ModelBuilder::PORO.new
7
7
 
8
- klass = builder.call(name: :user_name)
8
+ klass = builder.call([:name])
9
9
 
10
10
  object = klass.new(name: 'Jane')
11
11
 
@@ -13,7 +13,7 @@ describe ROM::ModelBuilder do
13
13
 
14
14
  expect { object.name = 'Jane' }.to raise_error(NoMethodError)
15
15
 
16
- klass = builder.call(name: :user_name, email: :user_email)
16
+ klass = builder.call([:name, :email])
17
17
 
18
18
  object = klass.new(name: 'Jane', email: 'jane@doe.org')
19
19
 
@@ -21,23 +21,25 @@ describe ROM::ModelBuilder do
21
21
  expect(object.email).to eql('jane@doe.org')
22
22
  end
23
23
 
24
- it 'defines a constant for the model when :name option is present' do
25
- builder = ROM::ModelBuilder::PORO.new(name: 'User')
24
+ context 'when :name option is present' do
25
+ it 'defines a constant for the model' do
26
+ builder = ROM::ModelBuilder::PORO.new(name: 'User')
26
27
 
27
- builder.call(name: :user_name, email: :user_email)
28
+ builder.call([:name, :email])
28
29
 
29
- expect(Object.const_defined?(:User)).to be(true)
30
- end
30
+ expect(Object.const_defined?(:User)).to be(true)
31
+ end
31
32
 
32
- it 'defines a constant within a namespace for the model when :name option is present' do
33
- module MyApp; module Entities; end; end
33
+ it 'defines a constant within a namespace for the model' do
34
+ module MyApp; module Entities; end; end
34
35
 
35
- builder = ROM::ModelBuilder::PORO.new(name: 'MyApp::Entities::User')
36
+ builder = ROM::ModelBuilder::PORO.new(name: 'MyApp::Entities::User')
36
37
 
37
- builder.call(name: :user_name, email: :user_email)
38
+ builder.call([:name, :email])
38
39
 
39
- expect(MyApp::Entities.const_defined?(:User)).to be(true)
40
- expect(Object.const_defined?(:User)).to be(false)
40
+ expect(MyApp::Entities.const_defined?(:User)).to be(true)
41
+ expect(Object.const_defined?(:User)).to be(false)
42
+ end
41
43
  end
42
44
  end
43
45
  end
@@ -0,0 +1,331 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::Processor::Transproc do
4
+ subject(:transproc) { ROM::Processor::Transproc.build(header) }
5
+
6
+ let(:header) { ROM::Header.coerce(attributes) }
7
+
8
+ context 'no mapping' do
9
+ let(:attributes) { [[:name]] }
10
+ let(:relation) { [{ name: 'Jane' }, { name: 'Joe' }] }
11
+
12
+ it 'returns tuples' do
13
+ expect(transproc[relation]).to eql(relation)
14
+ end
15
+ end
16
+
17
+ context 'coercing values' do
18
+ let(:attributes) { [[:name, type: :string], [:age, type: :integer]] }
19
+ let(:relation) { [{ name: :Jane, age: '1' }, { name: :Joe, age: '2' }] }
20
+
21
+ it 'returns tuples' do
22
+ expect(transproc[relation]).to eql([
23
+ { name: 'Jane', age: 1 }, { name: 'Joe', age: 2 }
24
+ ])
25
+ end
26
+ end
27
+
28
+ context 'mapping to object' do
29
+ let(:header) { ROM::Header.coerce(attributes, model) }
30
+
31
+ let(:model) do
32
+ Class.new do
33
+ include Virtus.value_object
34
+ values { attribute :name }
35
+ end
36
+ end
37
+
38
+ let(:attributes) { [[:name]] }
39
+ let(:relation) { [{ name: 'Jane' }, { name: 'Joe' }] }
40
+
41
+ it 'returns tuples' do
42
+ expect(transproc[relation]).to eql([
43
+ model.new(name: 'Jane'), model.new(name: 'Joe')
44
+ ])
45
+ end
46
+ end
47
+
48
+ context 'renaming keys' do
49
+ let(:attributes) { [[:name, from: 'name']] }
50
+ let(:relation) { [{ 'name' => 'Jane' }, { 'name' => 'Joe' }] }
51
+
52
+ it 'returns tuples with renamed keys' do
53
+ expect(transproc[relation]).to eql([{ name: 'Jane' }, { name: 'Joe' }])
54
+ end
55
+ end
56
+
57
+ context 'mapping nested hash' do
58
+ let(:relation) do
59
+ [
60
+ { 'name' => 'Jane', 'task' => { 'title' => 'Task One' } },
61
+ { 'name' => 'Joe', 'task' => { 'title' => 'Task Two' } }
62
+ ]
63
+ end
64
+
65
+ context 'when no mapping is needed' do
66
+ let(:attributes) { [['name'], ['task', type: :hash, header: [[:title]]]] }
67
+
68
+ it 'returns tuples' do
69
+ expect(transproc[relation]).to eql(relation)
70
+ end
71
+ end
72
+
73
+ context 'with deeply nested hashes' do
74
+ context 'when no renaming is required' do
75
+ let(:relation) do
76
+ [
77
+ { 'user' => { 'name' => 'Jane', 'task' => { 'title' => 'Task One' } } },
78
+ { 'user' => { 'name' => 'Joe', 'task' => { 'title' => 'Task Two' } } }
79
+ ]
80
+ end
81
+
82
+ let(:attributes) do
83
+ [[
84
+ 'user', type: :hash, header: [
85
+ ['name'],
86
+ ['task', type: :hash, header: [['title']]]
87
+ ]
88
+ ]]
89
+ end
90
+
91
+ it 'returns tuples' do
92
+ expect(transproc[relation]).to eql(relation)
93
+ end
94
+ end
95
+
96
+ context 'when renaming is required' do
97
+ let(:relation) do
98
+ [
99
+ { user: { name: 'Jane', task: { title: 'Task One' } } },
100
+ { user: { name: 'Joe', task: { title: 'Task Two' } } }
101
+ ]
102
+ end
103
+
104
+ let(:attributes) do
105
+ [[
106
+ 'user', type: :hash, header: [
107
+ ['name'],
108
+ ['task', type: :hash, header: [['title']]]
109
+ ]
110
+ ]]
111
+ end
112
+
113
+ it 'returns tuples' do
114
+ expect(transproc[relation]).to eql(relation)
115
+ end
116
+ end
117
+ end
118
+
119
+ context 'renaming keys' do
120
+ context 'when only hash needs renaming' do
121
+ let(:attributes) do
122
+ [
123
+ ['name'],
124
+ [:task, from: 'task', type: :hash, header: [[:title, from: 'title']]]
125
+ ]
126
+ end
127
+
128
+ it 'returns tuples with key renamed in the nested hash' do
129
+ expect(transproc[relation]).to eql([
130
+ { 'name' => 'Jane', :task => { title: 'Task One' } },
131
+ { 'name' => 'Joe', :task => { title: 'Task Two' } }
132
+ ])
133
+ end
134
+ end
135
+
136
+ context 'when all attributes need renaming' do
137
+ let(:attributes) do
138
+ [
139
+ [:name, from: 'name'],
140
+ [:task, from: 'task', type: :hash, header: [[:title, from: 'title']]]
141
+ ]
142
+ end
143
+
144
+ it 'returns tuples with key renamed in the nested hash' do
145
+ expect(transproc[relation]).to eql([
146
+ { name: 'Jane', task: { title: 'Task One' } },
147
+ { name: 'Joe', task: { title: 'Task Two' } }
148
+ ])
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ context 'wrapping tuples' do
155
+ let(:relation) do
156
+ [
157
+ { 'name' => 'Jane', 'title' => 'Task One' },
158
+ { 'name' => 'Joe', 'title' => 'Task Two' }
159
+ ]
160
+ end
161
+
162
+ context 'when no mapping is needed' do
163
+ let(:attributes) do
164
+ [
165
+ ['name'],
166
+ ['task', type: :hash, wrap: true, header: [['title']]]
167
+ ]
168
+ end
169
+
170
+ it 'returns wrapped tuples' do
171
+ expect(transproc[relation]).to eql([
172
+ { 'name' => 'Jane', 'task' => { 'title' => 'Task One' } },
173
+ { 'name' => 'Joe', 'task' => { 'title' => 'Task Two' } }
174
+ ])
175
+ end
176
+ end
177
+
178
+ context 'with deeply wrapped tuples' do
179
+ let(:attributes) do
180
+ [
181
+ ['user', type: :hash, wrap: true, header: [
182
+ ['name'],
183
+ ['task', type: :hash, wrap: true, header: [['title']]]
184
+ ]]
185
+ ]
186
+ end
187
+
188
+ it 'returns wrapped tuples' do
189
+ expect(transproc[relation]).to eql([
190
+ { 'user' => { 'name' => 'Jane', 'task' => { 'title' => 'Task One' } } },
191
+ { 'user' => { 'name' => 'Joe', 'task' => { 'title' => 'Task Two' } } }
192
+ ])
193
+ end
194
+ end
195
+
196
+ context 'renaming keys' do
197
+ context 'when only wrapped tuple requires renaming' do
198
+ let(:attributes) do
199
+ [
200
+ ['name'],
201
+ ['task', type: :hash, wrap: true, header: [[:title, from: 'title']]]
202
+ ]
203
+ end
204
+
205
+ it 'returns wrapped tuples with renamed keys' do
206
+ expect(transproc[relation]).to eql([
207
+ { 'name' => 'Jane', 'task' => { title: 'Task One' } },
208
+ { 'name' => 'Joe', 'task' => { title: 'Task Two' } }
209
+ ])
210
+ end
211
+ end
212
+
213
+ context 'when all attributes require renaming' do
214
+ let(:attributes) do
215
+ [
216
+ [:name, from: 'name'],
217
+ [:task, type: :hash, wrap: true, header: [[:title, from: 'title']]]
218
+ ]
219
+ end
220
+
221
+ it 'returns wrapped tuples with all keys renamed' do
222
+ expect(transproc[relation]).to eql([
223
+ { name: 'Jane', task: { title: 'Task One' } },
224
+ { name: 'Joe', task: { title: 'Task Two' } }
225
+ ])
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ context 'grouping tuples' do
232
+ let(:relation) do
233
+ [
234
+ { 'name' => 'Jane', 'title' => 'Task One' },
235
+ { 'name' => 'Jane', 'title' => 'Task Two' },
236
+ { 'name' => 'Joe', 'title' => 'Task One' }
237
+ ]
238
+ end
239
+
240
+ context 'when no mapping is needed' do
241
+ let(:attributes) do
242
+ [
243
+ ['name'],
244
+ ['tasks', type: :array, group: true, header: [['title']]]
245
+ ]
246
+ end
247
+
248
+ it 'returns wrapped tuples with all keys renamed' do
249
+ expect(transproc[relation]).to eql([
250
+ { 'name' => 'Jane',
251
+ 'tasks' => [{ 'title' => 'Task One' }, { 'title' => 'Task Two' }] },
252
+ { 'name' => 'Joe',
253
+ 'tasks' => [{ 'title' => 'Task One' }] }
254
+ ])
255
+ end
256
+ end
257
+
258
+ context 'renaming keys' do
259
+ context 'when only grouped tuple requires renaming' do
260
+ let(:attributes) do
261
+ [
262
+ ['name'],
263
+ ['tasks', type: :array, group: true, header: [[:title, from: 'title']]]
264
+ ]
265
+ end
266
+
267
+ it 'returns grouped tuples with renamed keys' do
268
+ expect(transproc[relation]).to eql([
269
+ { 'name' => 'Jane',
270
+ 'tasks' => [{ title: 'Task One' }, { title: 'Task Two' }] },
271
+ { 'name' => 'Joe',
272
+ 'tasks' => [{ title: 'Task One' }] }
273
+ ])
274
+ end
275
+ end
276
+
277
+ context 'when all attributes require renaming' do
278
+ let(:attributes) do
279
+ [
280
+ [:name, from: 'name'],
281
+ [:tasks, type: :array, group: true, header: [[:title, from: 'title']]]
282
+ ]
283
+ end
284
+
285
+ it 'returns grouped tuples with all keys renamed' do
286
+ expect(transproc[relation]).to eql([
287
+ { name: 'Jane',
288
+ tasks: [{ title: 'Task One' }, { title: 'Task Two' }] },
289
+ { name: 'Joe',
290
+ tasks: [{ title: 'Task One' }] }
291
+ ])
292
+ end
293
+ end
294
+ end
295
+
296
+ context 'nested grouping' do
297
+ let(:relation) do
298
+ [
299
+ { name: 'Jane', title: 'Task One', tag: 'red' },
300
+ { name: 'Jane', title: 'Task One', tag: 'green' },
301
+ { name: 'Joe', title: 'Task One', tag: 'blue' }
302
+ ]
303
+ end
304
+
305
+ let(:attributes) do
306
+ [
307
+ [:name],
308
+ [:tasks, type: :array, group: true, header: [
309
+ [:title],
310
+ [:tags, type: :array, group: true, header: [[:tag]]]
311
+ ]]
312
+ ]
313
+ end
314
+
315
+ it 'returns deeply grouped tuples' do
316
+ expect(transproc[relation]).to eql([
317
+ { name: 'Jane',
318
+ tasks: [
319
+ { title: 'Task One', tags: [{ tag: 'red' }, { tag: 'green' }] }
320
+ ]
321
+ },
322
+ { name: 'Joe',
323
+ tasks: [
324
+ { title: 'Task One', tags: [{ tag: 'blue' }] }
325
+ ]
326
+ }
327
+ ])
328
+ end
329
+ end
330
+ end
331
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::Reader do
4
+ subject(:reader) { ROM::Reader.new(name, relation, mappers) }
5
+
6
+ let(:name) { :users }
7
+ let(:relation) { [jane, joe] }
8
+ let(:jane) { { name: 'Jane' } }
9
+ let(:joe) { { name: 'Joe' } }
10
+ let(:mappers) { ROM::MapperRegistry.new(users: mapper) }
11
+ let(:mapper) { double('mapper', header: []) }
12
+
13
+ describe '#initialize' do
14
+ it 'raises error when mapper cannot be found' do
15
+ expect { ROM::Reader.new(:not_here, relation, mappers) }
16
+ .to raise_error(ROM::Reader::MapperMissingError, /not_here/)
17
+ end
18
+ end
19
+
20
+ describe '#each' do
21
+ it 'yields mapped tuples from relations' do
22
+ expect(mapper).to receive(:process)
23
+ .with(relation)
24
+ .and_yield(jane).and_yield(joe)
25
+
26
+ result = []
27
+ reader.each { |user| result << user }
28
+ expect(result).to eql([jane, joe])
29
+ end
30
+ end
31
+
32
+ describe '.build' do
33
+ subject(:reader) { ROM::Reader.build(name, relation, mappers, [:all]) }
34
+
35
+ before do
36
+ relation.instance_exec do
37
+ def name
38
+ 'users'
39
+ end
40
+
41
+ def all(*_args)
42
+ find_all
43
+ end
44
+ end
45
+ end
46
+
47
+ it 'sets reader class name' do
48
+ expect(reader.class.name).to eql("ROM::Reader[Users]")
49
+ end
50
+
51
+ it 'defines methods from relation' do
52
+ block = proc {}
53
+
54
+ expect(relation).to receive(:all)
55
+ .with(1, &block)
56
+ .and_return([joe])
57
+
58
+ expect(mapper).to receive(:process)
59
+ .with([joe])
60
+ .and_yield(joe)
61
+
62
+ result = reader.all(1, &block)
63
+
64
+ expect(result.path).to eql('users.all')
65
+ expect(result.to_a).to eql([joe])
66
+ end
67
+
68
+ it 'raises error when relation does not respond to the method' do
69
+ expect { reader.not_here }
70
+ .to raise_error(ROM::NoRelationError, /not_here/)
71
+ end
72
+ end
73
+ end