praxis-mapper 3.1.1
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 +7 -0
- data/.gitignore +26 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +83 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +102 -0
- data/Guardfile +11 -0
- data/LICENSE +22 -0
- data/README.md +19 -0
- data/Rakefile +14 -0
- data/lib/praxis-mapper/config_hash.rb +40 -0
- data/lib/praxis-mapper/connection_manager.rb +102 -0
- data/lib/praxis-mapper/finalizable.rb +38 -0
- data/lib/praxis-mapper/identity_map.rb +532 -0
- data/lib/praxis-mapper/logging.rb +22 -0
- data/lib/praxis-mapper/model.rb +430 -0
- data/lib/praxis-mapper/query/base.rb +213 -0
- data/lib/praxis-mapper/query/sql.rb +183 -0
- data/lib/praxis-mapper/query_statistics.rb +46 -0
- data/lib/praxis-mapper/resource.rb +226 -0
- data/lib/praxis-mapper/support/factory_girl.rb +104 -0
- data/lib/praxis-mapper/support/memory_query.rb +34 -0
- data/lib/praxis-mapper/support/memory_repository.rb +44 -0
- data/lib/praxis-mapper/support/schema_dumper.rb +66 -0
- data/lib/praxis-mapper/support/schema_loader.rb +56 -0
- data/lib/praxis-mapper/support.rb +2 -0
- data/lib/praxis-mapper/version.rb +5 -0
- data/lib/praxis-mapper.rb +60 -0
- data/praxis-mapper.gemspec +38 -0
- data/spec/praxis-mapper/connection_manager_spec.rb +117 -0
- data/spec/praxis-mapper/identity_map_spec.rb +905 -0
- data/spec/praxis-mapper/logging_spec.rb +9 -0
- data/spec/praxis-mapper/memory_repository_spec.rb +56 -0
- data/spec/praxis-mapper/model_spec.rb +389 -0
- data/spec/praxis-mapper/query/base_spec.rb +317 -0
- data/spec/praxis-mapper/query/sql_spec.rb +184 -0
- data/spec/praxis-mapper/resource_spec.rb +154 -0
- data/spec/praxis_mapper_spec.rb +21 -0
- data/spec/spec_fixtures.rb +12 -0
- data/spec/spec_helper.rb +63 -0
- data/spec/support/spec_models.rb +215 -0
- data/spec/support/spec_resources.rb +39 -0
- metadata +298 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Praxis::Mapper::Support::MemoryRepository do
|
4
|
+
|
5
|
+
subject(:repository) { Praxis::Mapper::Support::MemoryRepository.new }
|
6
|
+
|
7
|
+
let(:simple_rows) {[
|
8
|
+
{:id => 1, :name => "george jr", :parent_id => 1, :description => "one"},
|
9
|
+
{:id => 2, :name => "george iii", :parent_id => 2, :description => "two"},
|
10
|
+
{:id => 3, :name => "george xvi", :parent_id => 2, :description => "three"}
|
11
|
+
|
12
|
+
]}
|
13
|
+
|
14
|
+
let(:person_rows) {[
|
15
|
+
{id: 1, email: "one@example.com", address_id: 1},
|
16
|
+
{id: 2, email: "two@example.com", address_id: 2},
|
17
|
+
{id: 3, email: "three@example.com", address_id: 2}
|
18
|
+
|
19
|
+
]}
|
20
|
+
|
21
|
+
before do
|
22
|
+
repository.insert(:simple_model, simple_rows)
|
23
|
+
repository.insert(:people, person_rows)
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'insert' do
|
27
|
+
|
28
|
+
it 'adds the records to the right repository collection' do
|
29
|
+
simple_rows.each do |simple_row|
|
30
|
+
repository.collection(:simple_model).should include(simple_row)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with a Model class' do
|
35
|
+
let(:row) { {id: 4, name: "bob", parent_id: 5, description: "four"} }
|
36
|
+
it 'adds the records to Model.table_name collection' do
|
37
|
+
repository.insert(SimpleModel, [row])
|
38
|
+
repository.all(:simple_model, id: 4).should =~ [row]
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
context 'all' do
|
46
|
+
|
47
|
+
it 'retrieves all matching records' do
|
48
|
+
repository.all(:simple_model, parent_id: 2).should =~ simple_rows[1..2]
|
49
|
+
repository.all(SimpleModel, parent_id: 2).should =~ simple_rows[1..2]
|
50
|
+
|
51
|
+
repository.all(:simple_model, id: 1, parent_id: 2).should be_empty
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,389 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
describe Praxis::Mapper::Model do
|
5
|
+
subject(:model) { SimpleModel }
|
6
|
+
|
7
|
+
|
8
|
+
its(:excluded_scopes) { should =~ [:account] }
|
9
|
+
its(:repository_name) { should == :default }
|
10
|
+
its(:identities) { should =~ [:id, :name] }
|
11
|
+
its(:table_name) { should == "simple_model" }
|
12
|
+
its(:associations) { should have(2).items }
|
13
|
+
|
14
|
+
let(:id) { /\d{10}/.gen.to_i }
|
15
|
+
let(:name) { /\w+/.gen }
|
16
|
+
let(:parent_id) { /\d{10}/.gen.to_i }
|
17
|
+
let(:data) do
|
18
|
+
{ :id => id,
|
19
|
+
:name => name,
|
20
|
+
:parent_id => parent_id
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:person_rows) {[
|
25
|
+
{id: 1, email: "one@example.com", address_id: 1, prior_address_ids: JSON.dump([2])},
|
26
|
+
{id: 2, email: "two@example.com", address_id: 2, prior_address_ids: JSON.dump([])},
|
27
|
+
{id: 3, email: "three@example.com", address_id: 2, prior_address_ids: JSON.dump([])}
|
28
|
+
]}
|
29
|
+
|
30
|
+
let(:person_records) { person_rows.collect { |r| PersonModel.new(r) } }
|
31
|
+
|
32
|
+
let(:address_rows) { [{id: 1, owner_id: 1},{id: 2, owner_id: 3}] }
|
33
|
+
let(:address_records) { address_rows.collect { |r| AddressModel.new(r) } }
|
34
|
+
|
35
|
+
let(:composite_id_rows) { [
|
36
|
+
{id:1, type:"foo", state:"running"},
|
37
|
+
{id:2, type:"bar", state:"terminated"}
|
38
|
+
]}
|
39
|
+
|
40
|
+
let(:composite_id_records) { composite_id_rows.collect {|r| CompositeIdModel.new(r) } }
|
41
|
+
|
42
|
+
let(:composite_array_rows) { [
|
43
|
+
{id:1, type: 'CompositeArrayModel', composite_array_keys: JSON.dump([[1, "foo"], [2,"bar"]])},
|
44
|
+
{id:2, type: 'CompositeArrayModel', composite_array_keys: JSON.dump([[1, "foo"]])}
|
45
|
+
]}
|
46
|
+
|
47
|
+
let(:composite_array_records) { composite_array_rows.collect {|r| CompositeArrayModel.new(r) } }
|
48
|
+
|
49
|
+
|
50
|
+
let(:identity_map) { Praxis::Mapper::IdentityMap.current }
|
51
|
+
|
52
|
+
|
53
|
+
context "new-style associations" do
|
54
|
+
subject(:person_model) { PersonModel }
|
55
|
+
its(:associations) { should have(3).items }
|
56
|
+
|
57
|
+
its(:associations) { should include(:properties) }
|
58
|
+
its(:associations) { should include(:address) }
|
59
|
+
its(:associations) { should include(:prior_addresses) }
|
60
|
+
|
61
|
+
it 'finalizes properly' do
|
62
|
+
person_model.associations[:properties].should == {
|
63
|
+
model: AddressModel,
|
64
|
+
key: :owner_id,
|
65
|
+
primary_key: :id,
|
66
|
+
type: :one_to_many
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
context "record finders" do
|
73
|
+
|
74
|
+
it 'have .get' do
|
75
|
+
identity_map.should_receive(:get).with(SimpleModel, :id => id)
|
76
|
+
SimpleModel.get(:id => id)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'have .all' do
|
80
|
+
identity_map.should_receive(:all).with(SimpleModel, :id => id)
|
81
|
+
SimpleModel.all(:id => id)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
context "with a record" do
|
87
|
+
|
88
|
+
subject { SimpleModel.new(data) }
|
89
|
+
|
90
|
+
its(:id) { should == id }
|
91
|
+
its(:name) { should == name }
|
92
|
+
its(:parent_id) { should == parent_id }
|
93
|
+
its(:_resource) { should be_nil }
|
94
|
+
|
95
|
+
before do
|
96
|
+
identity_map.add_records([subject])
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
context 'creating accessors' do
|
101
|
+
|
102
|
+
context 'for identities and attributes' do
|
103
|
+
let(:model) do
|
104
|
+
Class.new(Praxis::Mapper::Model) do
|
105
|
+
identity :id
|
106
|
+
end
|
107
|
+
end
|
108
|
+
before do
|
109
|
+
model.finalize!
|
110
|
+
end
|
111
|
+
subject { model.new(data) }
|
112
|
+
|
113
|
+
it 'eagerly defines accessors for identities'do
|
114
|
+
|
115
|
+
subject.methods.should include(:id)
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'lazily defines accessors for other attributes' do
|
119
|
+
subject.methods.should_not include(:name)
|
120
|
+
subject.name
|
121
|
+
subject.methods.should include(:name)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'for associations' do
|
126
|
+
context 'that are many_to_one' do
|
127
|
+
|
128
|
+
it { should respond_to(:parent) }
|
129
|
+
|
130
|
+
it 'retrieves related records' do
|
131
|
+
parent_record = double("parent_record")
|
132
|
+
|
133
|
+
identity_map.should_receive(:get).with(ParentModel, :id => parent_id).and_return(parent_record)
|
134
|
+
subject.parent.should == parent_record
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'where the source_key value is nil' do
|
138
|
+
subject { SimpleModel.new(data.merge(:parent_id=>nil)) }
|
139
|
+
it 'returns nil' do
|
140
|
+
Praxis::Mapper::IdentityMap.current.should_not_receive(:get)
|
141
|
+
subject.parent.should be_nil
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'that involve a serialized array' do
|
148
|
+
|
149
|
+
before do
|
150
|
+
identity_map.add_records(person_records)
|
151
|
+
identity_map.add_records(address_records)
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'array_to_many' do
|
155
|
+
subject(:person_record) { person_records[0] }
|
156
|
+
its(:prior_addresses) { should =~ address_records[1..1] }
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'many_to_array' do
|
160
|
+
subject(:address_record) { address_records[1] }
|
161
|
+
its(:prior_residents) { should =~ person_records[0..0] }
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'that involve a serialized array with composite keys' do
|
167
|
+
|
168
|
+
before do
|
169
|
+
identity_map.add_records(composite_id_records)
|
170
|
+
identity_map.add_records(composite_array_records)
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'array_to_many' do
|
174
|
+
subject(:composite_array_record) { composite_array_records[0] }
|
175
|
+
its(:composite_id_models) { should =~ composite_id_records }
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'many_to_array' do
|
179
|
+
subject(:composite_id_record) { composite_id_records[0] }
|
180
|
+
its(:composite_array_models) { should =~ composite_array_records }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
context 'that are one_to_many' do
|
185
|
+
subject(:address_record) { address_records.first }
|
186
|
+
|
187
|
+
before do
|
188
|
+
identity_map.add_records(person_records)
|
189
|
+
identity_map.add_records(address_records)
|
190
|
+
end
|
191
|
+
|
192
|
+
it { should respond_to(:owner) }
|
193
|
+
it { should respond_to(:residents) }
|
194
|
+
|
195
|
+
its(:owner) { should be(person_records[0]) }
|
196
|
+
its(:residents) { should =~ person_records[0..0] }
|
197
|
+
|
198
|
+
context 'for a composite key' do
|
199
|
+
|
200
|
+
let(:other_records) {[
|
201
|
+
OtherModel.new(id:10, composite_id:1, composite_type:"foo", name:"something"),
|
202
|
+
OtherModel.new(id:11, composite_id:1, composite_type:"foo", name:"nothing"),
|
203
|
+
OtherModel.new(id:12, composite_id:1, composite_type:"bar", name:"nothing")
|
204
|
+
]}
|
205
|
+
|
206
|
+
subject(:composite_record) { composite_id_records.first }
|
207
|
+
|
208
|
+
before do
|
209
|
+
identity_map.add_records(composite_id_records)
|
210
|
+
identity_map.add_records(other_records)
|
211
|
+
end
|
212
|
+
|
213
|
+
its(:other_models) { should =~ other_records[0..1] }
|
214
|
+
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'does respond_to attributes in the underlying record' do
|
227
|
+
subject.should respond_to(:id)
|
228
|
+
subject.should respond_to(:name)
|
229
|
+
subject.should respond_to(:parent_id)
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'does not respond_to attributes not in the underlying record' do
|
233
|
+
subject.should_not respond_to(:foo)
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'raises NoMethodError for undefined attributes' do
|
237
|
+
expect { subject.foo }.to raise_error(NoMethodError)
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
context 'supports composite identities' do
|
243
|
+
subject { CompositeIdModel }
|
244
|
+
its(:identities) { should =~ [[:id, :type]] }
|
245
|
+
end
|
246
|
+
|
247
|
+
#TODO: Refactor these cases...yaml and json serialization are exactly the same test...except for using YAML or JSON class
|
248
|
+
context 'serialized attributes' do
|
249
|
+
context 'yaml attributes' do
|
250
|
+
let(:model) { YamlArrayModel }
|
251
|
+
let(:names) { ["george jr", "george iii"] }
|
252
|
+
let(:parent_ids) { [1,2] }
|
253
|
+
|
254
|
+
let(:record) {
|
255
|
+
model.new(:id => 1,
|
256
|
+
:parent_ids => YAML.dump(parent_ids),
|
257
|
+
:names => YAML.dump(names)
|
258
|
+
)
|
259
|
+
}
|
260
|
+
|
261
|
+
|
262
|
+
it 'de-serializes in the accessor' do
|
263
|
+
record.should respond_to(:names)
|
264
|
+
record.names.should =~ names
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'memoizes the de-serialization in the accessor' do
|
268
|
+
YAML.should_receive(:load).with(YAML.dump(names)).once.and_call_original
|
269
|
+
2.times { record.names }
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'defines an accessor for the raw yaml string' do
|
273
|
+
record.should respond_to(:_raw_names)
|
274
|
+
record._raw_names.should == YAML.dump(names)
|
275
|
+
end
|
276
|
+
|
277
|
+
context "with a nil value" do
|
278
|
+
let(:record) {
|
279
|
+
model.new(:id => 1,
|
280
|
+
:parent_ids => nil,
|
281
|
+
:names => nil
|
282
|
+
)
|
283
|
+
}
|
284
|
+
|
285
|
+
it 'returns nil if no default value was specified' do
|
286
|
+
record.names.should be_nil
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'returns the default value if specified' do
|
290
|
+
record.parent_ids.should == []
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
296
|
+
|
297
|
+
context 'json attributes' do
|
298
|
+
let(:model) { JsonArrayModel }
|
299
|
+
let(:names) { ["george jr", "george iii"] }
|
300
|
+
let(:parent_ids) { [1,2] }
|
301
|
+
|
302
|
+
let(:record) {
|
303
|
+
model.new(:id => 1,
|
304
|
+
:parent_ids => JSON.dump(parent_ids),
|
305
|
+
:names => JSON.dump(names)
|
306
|
+
)
|
307
|
+
}
|
308
|
+
|
309
|
+
|
310
|
+
it 'de-serializes in the accessor' do
|
311
|
+
record.should respond_to(:names)
|
312
|
+
record.names.should =~ names
|
313
|
+
end
|
314
|
+
|
315
|
+
it 'memoizes the de-serialization in the accessor' do
|
316
|
+
JSON.should_receive(:load).with(JSON.dump(names)).once.and_call_original
|
317
|
+
2.times { record.names }
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'defines an accessor for the raw yaml string' do
|
321
|
+
record.should respond_to(:_raw_names)
|
322
|
+
record._raw_names.should == JSON.dump(names)
|
323
|
+
end
|
324
|
+
|
325
|
+
context "with a nil value" do
|
326
|
+
let(:record) {
|
327
|
+
model.new(:id => 1,
|
328
|
+
:parent_ids => nil,
|
329
|
+
:names => nil
|
330
|
+
)
|
331
|
+
}
|
332
|
+
|
333
|
+
it 'returns nil if no default value was specified' do
|
334
|
+
record.names.should be_nil
|
335
|
+
end
|
336
|
+
|
337
|
+
it 'returns the default value if specified' do
|
338
|
+
record.parent_ids.should == []
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
context '.context' do
|
347
|
+
let(:model_class) { PersonModel }
|
348
|
+
|
349
|
+
it 'works' do
|
350
|
+
PersonModel.contexts[:default][:select].should be_empty
|
351
|
+
PersonModel.contexts[:default][:track].should eq [:address]
|
352
|
+
end
|
353
|
+
|
354
|
+
context 'with nested tracks' do
|
355
|
+
it 'works too' do
|
356
|
+
name, block = PersonModel.contexts[:addresses][:track][0]
|
357
|
+
name.should be(:address)
|
358
|
+
block.should be_kind_of(Proc)
|
359
|
+
Praxis::Mapper::ConfigHash.from(&block).to_hash.should eq({context: :default})
|
360
|
+
|
361
|
+
PersonModel.contexts[:addresses][:track][1].should be(:prior_addresses)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|
366
|
+
|
367
|
+
context '#inspect' do
|
368
|
+
subject(:inspectable) { PersonModel.new( person_rows.first ) }
|
369
|
+
its(:inspect){ should =~ /@data: /}
|
370
|
+
its(:inspect){ should =~ /@deserialized_data: /}
|
371
|
+
its(:inspect){ should_not =~ /@query: /}
|
372
|
+
its(:inspect){ should_not =~ /@identity_map: /}
|
373
|
+
end
|
374
|
+
|
375
|
+
context '#identities' do
|
376
|
+
context 'with simple keys' do
|
377
|
+
subject(:record) { person_records.first }
|
378
|
+
|
379
|
+
its(:identities) { should eq(id: record.id, email: record.email)}
|
380
|
+
end
|
381
|
+
|
382
|
+
context 'with composite keys' do
|
383
|
+
subject(:record) { composite_id_records.first }
|
384
|
+
its(:identities) { should eq({[:id, :type] => [record.id, record.type]})}
|
385
|
+
end
|
386
|
+
|
387
|
+
end
|
388
|
+
|
389
|
+
end
|