rom 0.4.2 → 0.5.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.
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