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,55 +4,99 @@ describe ROM::Header do
4
4
  describe '.coerce' do
5
5
  subject(:header) { ROM::Header.coerce(input) }
6
6
 
7
-
8
7
  context 'with a primitive type' do
9
- let(:input) { [[:name, type: String]] }
8
+ let(:input) { [[:name, type: :string]] }
10
9
 
11
- let(:expected) { ROM::Header.new(name: ROM::Header::Attribute.coerce(input.first)) }
10
+ let(:expected) do
11
+ ROM::Header.new(name: ROM::Header::Attribute.coerce(input.first))
12
+ end
12
13
 
13
14
  it 'returns a header with coerced attributes' do
14
15
  expect(header).to eql(expected)
15
16
 
16
- expect(header[:name].type).to be(String)
17
+ expect(header[:name].type).to be(:string)
17
18
  end
18
19
  end
19
20
 
20
21
  context 'with a collection type' do
21
- let(:input) { [[:tasks, header: [[:title]], type: Array, model: model]] }
22
+ let(:input) { [[:tasks, header: [[:title]], type: :array, model: model]] }
22
23
  let(:model) { Class.new }
23
24
 
24
- let(:expected) { ROM::Header.new(tasks: ROM::Header::Attribute.coerce(input.first)) }
25
+ let(:expected) do
26
+ ROM::Header.new(tasks: ROM::Header::Attribute.coerce(input.first))
27
+ end
25
28
 
26
29
  it 'returns a header with coerced attributes' do
27
30
  expect(header).to eql(expected)
28
31
 
29
32
  tasks = header[:tasks]
30
33
 
31
- expect(tasks.type).to be(Array)
32
- expect(tasks.model).to be(model)
33
- expect(tasks.header).to eql(ROM::Header.coerce([[:title]]))
34
+ expect(tasks.type).to be(:array)
35
+ expect(tasks.header.model).to be(model)
36
+ expect(tasks.header).to eql(ROM::Header.coerce([[:title]], model))
34
37
 
35
- expect(input.first[1]).to eql(header: [[:title]], type: Array, model: model)
38
+ expect(input.first[1])
39
+ .to eql(header: [[:title]], type: :array, model: model)
36
40
  end
37
41
  end
38
42
 
39
43
  context 'with a hash type' do
40
- let(:input) { [[:task, header: [[:title]], type: Hash, model: model]] }
44
+ let(:input) { [[:task, header: [[:title]], type: :hash, model: model]] }
41
45
  let(:model) { Class.new }
42
46
 
43
- let(:expected) { ROM::Header.new(task: ROM::Header::Attribute.coerce(input.first)) }
47
+ let(:expected) do
48
+ ROM::Header.new(task: ROM::Header::Attribute.coerce(input.first))
49
+ end
44
50
 
45
51
  it 'returns a header with coerced attributes' do
46
52
  expect(header).to eql(expected)
47
53
 
48
54
  tasks = header[:task]
49
55
 
50
- expect(tasks.type).to be(Hash)
51
- expect(tasks.model).to be(model)
52
- expect(tasks.header).to eql(ROM::Header.coerce([[:title]]))
56
+ expect(tasks.type).to be(:hash)
57
+ expect(tasks.header.model).to be(model)
58
+ expect(tasks.header).to eql(ROM::Header.coerce([[:title]], model))
53
59
 
54
- expect(input.first[1]).to eql(header: [[:title]], type: Hash, model: model)
60
+ expect(input.first[1])
61
+ .to eql(header: [[:title]], type: :hash, model: model)
55
62
  end
56
63
  end
57
64
  end
65
+
66
+ describe '#mapping' do
67
+ it 'return mapping hash for primitive attributes' do
68
+ header = ROM::Header.coerce([[:title], [:name, from: :user_name]])
69
+
70
+ expect(header.mapping).to eql(title: :title, user_name: :name)
71
+ end
72
+
73
+ it 'returns an empty hash when there are no primitive attributes' do
74
+ header = ROM::Header.coerce([
75
+ [:city, type: :hash, wrap: true, header: [[:name]]]
76
+ ])
77
+
78
+ expect(header.mapping).to eql({})
79
+ end
80
+ end
81
+
82
+ describe '#tuple_keys' do
83
+ it 'return primitive attribute keys' do
84
+ header = ROM::Header.coerce([[:title], [:name, from: :user_name]])
85
+ expect(header.tuple_keys).to eql([:title, :user_name])
86
+ end
87
+
88
+ it 'return primitive attribute, wrap and group keys' do
89
+ header = ROM::Header.coerce([
90
+ [:title],
91
+ [:comments, type: :array, header: [[:author], [:text]]],
92
+ [:location, type: :hash, header: [[:lat], [:lng]]],
93
+ [:city, type: :hash, wrap: true, header: [[:name, from: :city_name]]],
94
+ [:tags, type: :array, group: true, header: [[:name, from: :tag_name]]]
95
+ ])
96
+
97
+ expect(header.tuple_keys).to match_array([
98
+ :title, :comments, :location, :city_name, :tag_name
99
+ ])
100
+ end
101
+ end
58
102
  end
@@ -0,0 +1,311 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::MapperBuilder do
4
+ subject(:builder) { ROM::MapperBuilder.new(:users, relation, options) }
5
+
6
+ let(:options) { {} }
7
+ let(:relation) { double('relation', header: []) }
8
+ let(:header) { mapper.header }
9
+ let(:mapper) { builder.call }
10
+
11
+ let(:expected_header) { ROM::Header.coerce(attributes) }
12
+
13
+ describe '#attribute' do
14
+ context 'simple attribute' do
15
+ let(:attributes) { [[:name]] }
16
+
17
+ it 'adds an attribute for the header' do
18
+ builder.attribute :name
19
+
20
+ expect(header).to eql(expected_header)
21
+ end
22
+ end
23
+
24
+ context 'aliased attribute' do
25
+ let(:attributes) { [[:name, from: :user_name]] }
26
+
27
+ it 'adds an aliased attribute for the header' do
28
+ builder.attribute :name, from: :user_name
29
+
30
+ expect(header).to eql(expected_header)
31
+ end
32
+ end
33
+
34
+ context 'prefixed attribute' do
35
+ let(:attributes) { [[:name, from: :user_name]] }
36
+ let(:options) { { prefix: :user } }
37
+
38
+ it 'adds an aliased attribute for the header using configured :prefix' do
39
+ builder.attribute :name
40
+
41
+ expect(header).to eql(expected_header)
42
+ end
43
+ end
44
+
45
+ context 'symbolized attribute' do
46
+ let(:attributes) { [[:name, from: 'name']] }
47
+ let(:options) { { symbolize_keys: true } }
48
+
49
+ it 'adds an attribute with symbolized alias' do
50
+ builder.attribute :name
51
+
52
+ expect(header).to eql(expected_header)
53
+ end
54
+ end
55
+ end
56
+
57
+ describe 'overriding inherited attributes from the relation header' do
58
+ context 'when name matches' do
59
+ let(:attributes) { [[:name, type: :string]] }
60
+
61
+ it 'excludes the inherited attribute' do
62
+ allow(relation).to receive(:header).and_return([:name])
63
+
64
+ builder.attribute :name, type: :string
65
+
66
+ expect(header).to eql(expected_header)
67
+ end
68
+ end
69
+
70
+ context 'when alias matches' do
71
+ let(:attributes) { [[:name, from: 'name', type: :string]] }
72
+
73
+ it 'excludes the inherited attribute' do
74
+ allow(relation).to receive(:header).and_return(['name'])
75
+
76
+ builder.attribute :name, from: 'name', type: :string
77
+
78
+ expect(header).to eql(expected_header)
79
+ end
80
+ end
81
+
82
+ context 'when name in a wrapped attribute matches' do
83
+ let(:attributes) do
84
+ [
85
+ [:city, type: :hash, wrap: true, header: [[:name, from: :city_name]]]
86
+ ]
87
+ end
88
+
89
+ it 'excludes the inherited attribute' do
90
+ allow(relation).to receive(:header).and_return([:city_name])
91
+
92
+ builder.wrap :city do
93
+ attribute :name, from: :city_name
94
+ end
95
+
96
+ expect(header).to eql(expected_header)
97
+ end
98
+ end
99
+
100
+ context 'when name in a grouped attribute matches' do
101
+ let(:attributes) do
102
+ [
103
+ [:tags, type: :array, group: true, header: [[:name, from: :tag_name]]]
104
+ ]
105
+ end
106
+
107
+ it 'excludes the inherited attribute' do
108
+ allow(relation).to receive(:header).and_return([:tag_name])
109
+
110
+ builder.group :tags do
111
+ attribute :name, from: :tag_name
112
+ end
113
+
114
+ expect(header).to eql(expected_header)
115
+ end
116
+ end
117
+
118
+ context 'when name in a hash attribute matches' do
119
+ let(:attributes) do
120
+ [
121
+ [:city, type: :hash, header: [[:name, from: :city_name]]]
122
+ ]
123
+ end
124
+
125
+ it 'excludes the inherited attribute' do
126
+ allow(relation).to receive(:header).and_return([:city])
127
+
128
+ builder.embedded :city, type: :hash do
129
+ attribute :name, from: :city_name
130
+ end
131
+
132
+ expect(header).to eql(expected_header)
133
+ end
134
+ end
135
+
136
+ context 'when name of an array attribute matches' do
137
+ let(:attributes) do
138
+ [
139
+ [:tags, type: :array, header: [[:name, from: :tag_name]]]
140
+ ]
141
+ end
142
+
143
+ it 'excludes the inherited attribute' do
144
+ allow(relation).to receive(:header).and_return([:tags])
145
+
146
+ builder.embedded :tags, type: :array do
147
+ attribute :name, from: :tag_name
148
+ end
149
+
150
+ expect(header).to eql(expected_header)
151
+ end
152
+ end
153
+ end
154
+
155
+ describe '#exclude' do
156
+ let(:attributes) { [[:name, from: 'name']] }
157
+
158
+ it 'removes an attribute from the inherited header' do
159
+ allow(relation).to receive(:header).and_return(['name'])
160
+ builder.attribute :name, from: 'name'
161
+ expect(header).to eql(expected_header)
162
+ end
163
+ end
164
+
165
+ describe '#embedded' do
166
+ context 'when :type is set to :hash' do
167
+ let(:attributes) { [[:city, type: :hash, header: [[:name]]]] }
168
+
169
+ it 'adds an embedded hash attribute' do
170
+ builder.embedded :city, type: :hash do
171
+ attribute :name
172
+ end
173
+
174
+ expect(header).to eql(expected_header)
175
+ end
176
+ end
177
+
178
+ context 'when :type is set to :array' do
179
+ let(:attributes) { [[:tags, type: :array, header: [[:name]]]] }
180
+
181
+ it 'adds an embedded array attribute' do
182
+ builder.embedded :tags, type: :array do
183
+ attribute :name
184
+ end
185
+
186
+ expect(header).to eql(expected_header)
187
+ end
188
+ end
189
+ end
190
+
191
+ describe '#wrap' do
192
+ let(:attributes) { [[:city, type: :hash, wrap: true, header: [[:name]]]] }
193
+
194
+ it 'adds an wrapped hash attribute using a block to define attributes' do
195
+ builder.wrap :city do
196
+ attribute :name
197
+ end
198
+
199
+ expect(header).to eql(expected_header)
200
+ end
201
+
202
+ it 'adds an wrapped hash attribute using a options define attributes' do
203
+ builder.wrap city: [:name]
204
+
205
+ expect(header).to eql(expected_header)
206
+ end
207
+ end
208
+
209
+ describe '#group' do
210
+ let(:attributes) { [[:tags, type: :array, group: true, header: [[:name]]]] }
211
+
212
+ it 'adds a group attribute using a block to define attributes' do
213
+ builder.group :tags do
214
+ attribute :name
215
+ end
216
+
217
+ expect(header).to eql(expected_header)
218
+ end
219
+
220
+ it 'adds a group attribute using a options define attributes' do
221
+ builder.group tags: [:name]
222
+
223
+ expect(header).to eql(expected_header)
224
+ end
225
+ end
226
+
227
+ describe 'top-level :prefix option' do
228
+ let(:options) do
229
+ { prefix: :user }
230
+ end
231
+
232
+ context 'when no attribute overrides top-level setting' do
233
+ let(:attributes) do
234
+ [
235
+ [:name, from: :user_name],
236
+ [:address, from: :user_address, type: :hash, header: [
237
+ [:city, from: :user_city]]
238
+ ],
239
+ [:contact, type: :hash, wrap: true, header: [
240
+ [:mobile, from: :user_mobile]]
241
+ ],
242
+ [:tasks, type: :array, group: true, header: [
243
+ [:title, from: :user_title]]
244
+ ]
245
+ ]
246
+ end
247
+
248
+ it 'sets aliased attributes using prefix automatically' do
249
+ builder.attribute :name
250
+
251
+ builder.embedded :address, type: :hash do
252
+ attribute :city
253
+ end
254
+
255
+ builder.wrap :contact do
256
+ attribute :mobile
257
+ end
258
+
259
+ builder.group :tasks do
260
+ attribute :title
261
+ end
262
+
263
+ expect(header).to eql(expected_header)
264
+ end
265
+ end
266
+
267
+ context 'when an attribute overrides top-level setting' do
268
+ let(:attributes) do
269
+ [
270
+ [:name, from: :user_name],
271
+ [:birthday, from: :user_birthday, type: :hash, header: [
272
+ [:year, from: :bd_year],
273
+ [:month, from: :bd_month],
274
+ [:day, from: :bd_day]]
275
+ ],
276
+ [:address, from: :user_address, type: :hash, header: [[:city]]],
277
+ [:contact, type: :hash, wrap: true, header: [
278
+ [:mobile, from: :contact_mobile]]
279
+ ],
280
+ [:tasks, type: :array, group: true, header: [
281
+ [:title, from: :task_title]]
282
+ ]
283
+ ]
284
+ end
285
+
286
+ it 'excludes from aliasing the ones which override it' do
287
+ builder.attribute :name
288
+
289
+ builder.embedded :birthday, type: :hash, prefix: :bd do
290
+ attribute :year
291
+ attribute :month
292
+ attribute :day
293
+ end
294
+
295
+ builder.embedded :address, type: :hash, prefix: false do
296
+ attribute :city
297
+ end
298
+
299
+ builder.wrap :contact, prefix: :contact do
300
+ attribute :mobile
301
+ end
302
+
303
+ builder.group :tasks, prefix: :task do
304
+ attribute :title
305
+ end
306
+
307
+ expect(header).to eql(expected_header)
308
+ end
309
+ end
310
+ end
311
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::MapperRegistry do
4
+ subject(:registry) { ROM::MapperRegistry.new }
5
+
6
+ let(:user_mapper) { double('user_mapper') }
7
+ let(:index_mapper) { double('index_mapper') }
8
+
9
+ before do
10
+ registry[:users] = user_mapper
11
+ registry[:index] = index_mapper
12
+ end
13
+
14
+ describe '#by_path' do
15
+ it 'returns first matching mapper' do
16
+ mapper = registry.by_path('users')
17
+
18
+ expect(mapper).to be(user_mapper)
19
+
20
+ mapper = registry.by_path('users.index')
21
+
22
+ expect(mapper).to be(index_mapper)
23
+ end
24
+ end
25
+ end