praxis-mapper 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,317 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Praxis::Mapper::Query::Base do
|
4
|
+
let(:scope) { {} }
|
5
|
+
let(:unloaded_ids) { [1, 2, 3] }
|
6
|
+
let(:connection) { double("connection") }
|
7
|
+
let(:identity_map) { double("identity_map", :scope => scope, :get_unloaded => unloaded_ids) }
|
8
|
+
|
9
|
+
let(:model) { SimpleModel }
|
10
|
+
|
11
|
+
let(:expected_ids_condition) { "id IN (#{unloaded_ids.join(", ")})" }
|
12
|
+
|
13
|
+
let(:query) { Praxis::Mapper::Query::Base.new(identity_map, model) }
|
14
|
+
subject { query }
|
15
|
+
|
16
|
+
let(:rows) { [
|
17
|
+
{:id => 1, :name => "george jr", :parent_id => 1, :description => "one"},
|
18
|
+
{:id => 2, :name => "george iii", :parent_id => 2, :description => "two"},
|
19
|
+
{:id => 3, :name => "george xvi", :parent_id => 2, :description => "three"}
|
20
|
+
] }
|
21
|
+
let(:ids) { rows.collect { |r| r[:id] } }
|
22
|
+
|
23
|
+
|
24
|
+
context "retrieving records" do
|
25
|
+
|
26
|
+
# TODO: refactor with shared_examples
|
27
|
+
context "#multi_get" do
|
28
|
+
|
29
|
+
it 'delegates to the subclass' do
|
30
|
+
query.should_receive(:_multi_get).and_return(rows)
|
31
|
+
response = query.multi_get(:id, ids)
|
32
|
+
|
33
|
+
response.should have(3).items
|
34
|
+
|
35
|
+
response.should eq(rows)
|
36
|
+
#record = response.first
|
37
|
+
#record.should be_kind_of(model)
|
38
|
+
|
39
|
+
#rows.first.each do |attribute, value|
|
40
|
+
# record.send(attribute).should == value
|
41
|
+
#end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'raises if run on a frozen query' do
|
46
|
+
query.freeze
|
47
|
+
expect { query.multi_get(:id, ids) }.to raise_error(TypeError)
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'for very large lists of values' do
|
51
|
+
let(:batch_size) { Praxis::Mapper::Query::Base::MULTI_GET_BATCH_SIZE }
|
52
|
+
|
53
|
+
|
54
|
+
let(:result_size) { (batch_size * 2.5).to_i }
|
55
|
+
|
56
|
+
let(:values) { (0..result_size).to_a }
|
57
|
+
let(:rows) { values.collect { |v| {:id => v} } }
|
58
|
+
|
59
|
+
before do
|
60
|
+
stub_const("Praxis::Mapper::Query::Base::MULTI_GET_BATCH_SIZE", 4)
|
61
|
+
|
62
|
+
rows.each_slice(batch_size) do |batch_rows|
|
63
|
+
ids = batch_rows.collect { |v| v.values }.flatten
|
64
|
+
query.should_receive(:_multi_get).with(:id, ids).and_return(batch_rows)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'batches queries and aggregates their results' do # FIXME: totally lame name for this
|
69
|
+
query.multi_get(:id, values).should =~ rows
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "#execute" do
|
75
|
+
it 'delegates to the subclass and wraps the response in model instances' do
|
76
|
+
query.should_receive(:_execute).and_return(rows)
|
77
|
+
response = query.execute
|
78
|
+
|
79
|
+
response.should have(3).items
|
80
|
+
|
81
|
+
item = response.first
|
82
|
+
item.should be_kind_of(model)
|
83
|
+
|
84
|
+
rows.first.each do |attribute, value|
|
85
|
+
item.send(attribute).should == value
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'raises if run on a frozen query' do
|
91
|
+
query.freeze
|
92
|
+
expect { query.execute }.to raise_error(TypeError)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'raises for subclass methods' do
|
98
|
+
expect { subject._multi_get(nil, nil) }.to raise_error "subclass responsibility"
|
99
|
+
expect { subject._execute }.to raise_error "subclass responsibility"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
context "the specification DSL" do
|
105
|
+
|
106
|
+
context "#select" do
|
107
|
+
it "accepts an array of symbols" do
|
108
|
+
subject.select :id, :name
|
109
|
+
subject.select.should include(:id => nil, :name => nil)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "accepts an array of strings" do
|
113
|
+
subject.select "id", "name"
|
114
|
+
subject.select.should include("id" => nil, "name" => nil)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "raises for unknown field types" do
|
118
|
+
expect { subject.select Object.new }.to raise_error
|
119
|
+
end
|
120
|
+
|
121
|
+
context "accepts an array of hashes" do
|
122
|
+
context "with strings for the field definitions" do
|
123
|
+
it "and symbols to specify the field aliases" do
|
124
|
+
definition = {:id => "IFNULL(foo,bar)", :name => "CONCAT(foo,bar)"}
|
125
|
+
subject.select definition
|
126
|
+
subject.select.should include(definition)
|
127
|
+
end
|
128
|
+
it "and strings to specify the field aliases" do
|
129
|
+
definition = {"id" => "IFNULL(foo,bar)", "name" => "CONCAT(foo,bar)"}
|
130
|
+
subject.select definition
|
131
|
+
subject.select.should include(definition)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "with symbols for the field definitions" do
|
136
|
+
it "and symbols to specify the field aliases" do
|
137
|
+
definition = {:my_id => :id, :name => :name}
|
138
|
+
subject.select :my_id => :id, :name => :name
|
139
|
+
subject.select.should include :my_id => :id, :name => :name
|
140
|
+
end
|
141
|
+
it "and strings to specify the field aliases" do
|
142
|
+
definition = {"id" => "IFNULL(foo,bar)", "name" => "CONCAT(foo,bar)"}
|
143
|
+
subject.select definition
|
144
|
+
subject.select.should include(definition)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
it "accepts an array of mixed hashes and symbols and strings" do
|
150
|
+
subject.select :id, "description", :name => "CONCAT(foo,bar)"
|
151
|
+
subject.select.should include(:id => nil, "description" => nil, :name => "CONCAT(foo,bar)")
|
152
|
+
end
|
153
|
+
|
154
|
+
it "also accepts a single symbol" do
|
155
|
+
subject.select :id
|
156
|
+
subject.select.should include(:id => nil)
|
157
|
+
end
|
158
|
+
|
159
|
+
it "also accepts a single hash" do
|
160
|
+
definition = {:id => "IFNULL(foo,bar)"}
|
161
|
+
subject.select definition
|
162
|
+
subject.select.should include(definition)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
context "with no query body" do
|
168
|
+
subject { Praxis::Mapper::Query::Base.new(identity_map, model) }
|
169
|
+
|
170
|
+
it "should be an empty nil" do
|
171
|
+
subject.select.should be_nil
|
172
|
+
end
|
173
|
+
|
174
|
+
its(:where) { should be_nil }
|
175
|
+
its(:track) { should eq(Set[]) }
|
176
|
+
|
177
|
+
context "with all the fixings" do
|
178
|
+
subject do
|
179
|
+
Praxis::Mapper::Query::Base.new(identity_map, model) do
|
180
|
+
select :id, :name
|
181
|
+
where "deployment_id=2"
|
182
|
+
track :parent
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
its(:select) { should == {:id => nil, :name => nil} }
|
188
|
+
its(:where) { should eq("deployment_id=2") }
|
189
|
+
its(:track) { should eq(Set[:parent]) }
|
190
|
+
|
191
|
+
its(:tracked_associations) { should =~ [model.associations[:parent]] }
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
context '#track' do
|
198
|
+
context 'with nested track something or another' do
|
199
|
+
subject :query do
|
200
|
+
Praxis::Mapper::Query::Base.new(identity_map, PersonModel) do
|
201
|
+
track :address do
|
202
|
+
select :id, :name
|
203
|
+
track :residents
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'saves the subcontext block' do
|
209
|
+
query.track.should have(1).item
|
210
|
+
|
211
|
+
name, tracked_address = query.track.first
|
212
|
+
name.should be(:address)
|
213
|
+
tracked_address.should be_kind_of(Proc)
|
214
|
+
end
|
215
|
+
|
216
|
+
its(:tracked_associations) { should =~ [PersonModel.associations[:address]] }
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
context 'tracking an association tracked by a context' do
|
221
|
+
subject :query do
|
222
|
+
Praxis::Mapper::Query::Base.new(identity_map, PersonModel) do
|
223
|
+
context :default
|
224
|
+
track :address do
|
225
|
+
select :id, :name
|
226
|
+
track :residents
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'retains both values' do
|
232
|
+
query.track.should have(2).item
|
233
|
+
|
234
|
+
query.track.should include(:address)
|
235
|
+
|
236
|
+
# TODO: find a better way to do this match
|
237
|
+
name, tracked_address = query.track.to_a[1]
|
238
|
+
name.should be(:address)
|
239
|
+
tracked_address.should be_kind_of(Proc)
|
240
|
+
end
|
241
|
+
|
242
|
+
its(:tracked_associations) { should =~ [PersonModel.associations[:address]] }
|
243
|
+
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context '#context' do
|
248
|
+
let(:model) { PersonModel }
|
249
|
+
|
250
|
+
subject do
|
251
|
+
Praxis::Mapper::Query::Base.new(identity_map, model) do
|
252
|
+
context :default
|
253
|
+
context :tiny
|
254
|
+
track :properties
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
its(:select) { should eq({id: nil, email: nil}) }
|
259
|
+
its(:track) { should eq(Set[:address, :properties]) }
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
context '#load' do
|
264
|
+
subject do
|
265
|
+
Praxis::Mapper::Query::Base.new(identity_map, model) do
|
266
|
+
load :address
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
its(:load) { should eq(Set[:address])}
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
context 'statistics' do
|
275
|
+
its(:statistics) { should == Hash.new }
|
276
|
+
|
277
|
+
it 'initialize new values with zero' do
|
278
|
+
subject.statistics[:execute].should == 0
|
279
|
+
end
|
280
|
+
|
281
|
+
context "#execute" do
|
282
|
+
before do
|
283
|
+
query.should_receive(:_execute).and_return(rows)
|
284
|
+
query.execute
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'tracks the number of calls' do
|
288
|
+
query.statistics[:execute].should == 1
|
289
|
+
end
|
290
|
+
it 'tracks records loaded' do
|
291
|
+
query.statistics[:records_loaded].should == rows.size
|
292
|
+
end
|
293
|
+
|
294
|
+
end
|
295
|
+
|
296
|
+
context "#multi_get" do
|
297
|
+
before do
|
298
|
+
query.should_receive(:_multi_get).with(:id, ids).and_return(rows)
|
299
|
+
query.multi_get(:id, ids)
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'tracks the number of calls' do
|
303
|
+
query.statistics[:multi_get].should == 1
|
304
|
+
end
|
305
|
+
it 'tracks records loaded' do
|
306
|
+
query.statistics[:records_loaded].should == rows.size
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
context "#raw" do
|
313
|
+
let(:model) { PersonModel }
|
314
|
+
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
# FIXME: figure out rspec's "should behave like" or whatever
|
4
|
+
|
5
|
+
describe Praxis::Mapper::Query::Sql do
|
6
|
+
let(:scope) { {} }
|
7
|
+
let(:unloaded_ids) { [1, 2, 3] }
|
8
|
+
let(:identity_map) { Praxis::Mapper::IdentityMap.setup!(scope) }
|
9
|
+
let(:connection) { identity_map.connection(:default) }
|
10
|
+
|
11
|
+
|
12
|
+
let(:expected_ids_condition) { "id IN (#{unloaded_ids.join(", ")})" }
|
13
|
+
|
14
|
+
context "without a query body" do
|
15
|
+
subject { Praxis::Mapper::Query::Sql.new(identity_map, SimpleModel) }
|
16
|
+
|
17
|
+
its(:select_clause) { should eq("SELECT *") }
|
18
|
+
its(:from_clause) { should eq("FROM `simple_model`") }
|
19
|
+
its(:where_clause) { should be_nil }
|
20
|
+
its(:limit_clause) { should be_nil }
|
21
|
+
|
22
|
+
its(:sql) { should eq("SELECT *\nFROM `simple_model`") }
|
23
|
+
|
24
|
+
context 'with an integer value in the identity map' do
|
25
|
+
let(:scope) { {:tenant => [:account_id, 71]} }
|
26
|
+
its(:where_clause) { should eq("WHERE `account_id`=71") }
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with a string value in the identity map' do
|
30
|
+
let(:scope) { {:tenant => [:account_id, '71']} }
|
31
|
+
its(:where_clause) { should eq("WHERE `account_id`='71'") }
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with a nil value in the identity map' do
|
35
|
+
let(:scope) { {:tenant => [:account_id, nil]} }
|
36
|
+
its(:where_clause) { should eq("WHERE `account_id` IS NULL") }
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
context "#raw" do
|
42
|
+
subject do
|
43
|
+
Praxis::Mapper::Query::Sql.new(identity_map, SimpleModel) do
|
44
|
+
raw "SELECT id, parent_id\nFROM table\nWHERE id=123\nGROUP BY id"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'uses the exact raw query for the final SQL statement' do
|
49
|
+
subject.sql.should eq("SELECT id, parent_id\nFROM table\nWHERE id=123\nGROUP BY id")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
context "with all the fixings" do
|
55
|
+
subject do
|
56
|
+
Praxis::Mapper::Query::Sql.new(identity_map, SimpleModel) do
|
57
|
+
select :id, :name
|
58
|
+
where "deployment_id=2"
|
59
|
+
track :parent
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "generates the correct select statement" do
|
64
|
+
str = subject.select_clause
|
65
|
+
str.should =~ /^\s*SELECT/
|
66
|
+
fields = str.gsub(/^\s*SELECT\s*/, "").split(',').map { |field| field.strip }
|
67
|
+
fields.should =~ ["id", "name"]
|
68
|
+
end
|
69
|
+
|
70
|
+
its(:where_clause) { should eq("WHERE deployment_id=2") }
|
71
|
+
|
72
|
+
it "should generate SQL by joining the select, from and where clauses with newlines" do
|
73
|
+
subject.sql.should == [subject.select_clause, subject.from_clause, subject.where_clause].compact.join("\n")
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'a scope in the identity map' do
|
77
|
+
let(:scope) { {:tenant => [:account_id, 71]} }
|
78
|
+
its(:where_clause) { should eq("WHERE `account_id`=71 AND deployment_id=2") }
|
79
|
+
|
80
|
+
context 'with a nil value' do
|
81
|
+
let(:scope) { {:deleted => [:deleted_at, nil]} }
|
82
|
+
its(:where_clause) { should eq("WHERE `deleted_at` IS NULL AND deployment_id=2") }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'an ignored scope in the identity map' do
|
87
|
+
let(:scope) { {:account => [:account_id, 71]} }
|
88
|
+
its(:where_clause) { should eq("WHERE deployment_id=2") }
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
context '#_multi_get' do
|
94
|
+
|
95
|
+
let(:ids) { [1, 2, 3] }
|
96
|
+
let(:names) { ["george xvi"] }
|
97
|
+
let(:expected_id_condition) { "(id IN (#{ids.join(", ")}))" }
|
98
|
+
let(:expected_name_condition) { "(name IN ('george xvi'))" }
|
99
|
+
|
100
|
+
let(:query) { Praxis::Mapper::Query::Sql.new(identity_map, ItemModel) }
|
101
|
+
|
102
|
+
it "constructs a where clause for Integer keys" do
|
103
|
+
query.should_receive(:where).with(expected_id_condition)
|
104
|
+
query.should_receive(:_execute)
|
105
|
+
|
106
|
+
query._multi_get :id, ids
|
107
|
+
end
|
108
|
+
|
109
|
+
it "constructs a where clause for String keys" do
|
110
|
+
query.should_receive(:where).with(expected_name_condition)
|
111
|
+
query.should_receive(:_execute)
|
112
|
+
|
113
|
+
query._multi_get :name, names
|
114
|
+
end
|
115
|
+
|
116
|
+
context "with composite identities" do
|
117
|
+
|
118
|
+
context "where each sub-key is an Integer" do
|
119
|
+
let(:ids) { [[1, 1], [2, 2], [3, 2]] }
|
120
|
+
let(:expected_composite_condition) { "(((id, parent_id) IN ((1, 1), (2, 2), (3, 2))) AND (id IN (1, 2, 3)))" }
|
121
|
+
it 'constructs a query using SQL composite constructs (parenthesis syntax)' do
|
122
|
+
query.should_receive(:where).with(expected_composite_condition)
|
123
|
+
query.should_receive(:_execute)
|
124
|
+
|
125
|
+
query._multi_get [:id, :parent_id], ids
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "where sub-keys are strings" do
|
130
|
+
let(:ids) { [[1, "george jr"], [2, "george iii"], [3, "george xvi"]] }
|
131
|
+
let(:expected_composite_condition) { "(((id, name) IN ((1, 'george jr'), (2, 'george iii'), (3, 'george xvi'))) AND (id IN (1, 2, 3)))" }
|
132
|
+
it 'quotes only the string subkeys in the constructed composite query syntax' do
|
133
|
+
query.should_receive(:where).with(expected_composite_condition)
|
134
|
+
query.should_receive(:_execute)
|
135
|
+
|
136
|
+
query._multi_get [:id, :name], ids
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
it "tracks datastore interactions" do
|
142
|
+
query._multi_get :ids, [1,2,3]
|
143
|
+
|
144
|
+
query.statistics[:datastore_interactions].should == 1
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
context '#_execute' do
|
153
|
+
let(:rows) { [
|
154
|
+
{:id => 1, :name => "foo", :parent_id => 1},
|
155
|
+
{:id => 2, :name => "bar", :parent_id => 2},
|
156
|
+
{:id => 3, :name => "snafu", :parent_id => 3}
|
157
|
+
] }
|
158
|
+
|
159
|
+
subject { Praxis::Mapper::Query::Sql.new(identity_map, SimpleModel) }
|
160
|
+
before do
|
161
|
+
connection.should_receive(:fetch).and_return(rows)
|
162
|
+
identity_map.should_receive(:connection).with(:default).and_return(connection)
|
163
|
+
|
164
|
+
Time.should_receive(:now).and_return(Time.at(0), Time.at(10))
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'wraps database results in SimpleModel instances' do
|
169
|
+
records = subject.execute
|
170
|
+
records.each { |record| record.should be_kind_of(SimpleModel) }
|
171
|
+
end
|
172
|
+
|
173
|
+
it "tracks datastore interactions" do
|
174
|
+
subject.execute
|
175
|
+
subject.statistics[:datastore_interactions].should == 1
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'times datastore interactions' do
|
179
|
+
subject.execute
|
180
|
+
subject.statistics[:datastore_interaction_time].should == 10
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Praxis::Mapper::Resource do
|
4
|
+
|
5
|
+
let(:parent_record) { ParentModel.new(id: 100, name: 'george sr') }
|
6
|
+
let(:parent_records) { [ParentModel.new(id: 101, name: "georgia"),ParentModel.new(id: 102, name: 'georgina')] }
|
7
|
+
let(:record) { SimpleModel.new(id: 103, name: 'george xvi') }
|
8
|
+
let(:model) { SimpleModel}
|
9
|
+
|
10
|
+
let(:identity_map) { Praxis::Mapper::IdentityMap.current }
|
11
|
+
|
12
|
+
before do
|
13
|
+
identity_map.add_records([parent_record]| parent_records)
|
14
|
+
identity_map.add_records([record])
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'configuration' do
|
18
|
+
subject { SimpleResource }
|
19
|
+
its(:model) { should == model }
|
20
|
+
its(:member_name) { should == 'simple_resource' }
|
21
|
+
its(:collection_name) { should == 'simple_resources' }
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
context 'retrieving resources' do
|
26
|
+
|
27
|
+
context 'getting a single resource' do
|
28
|
+
subject(:resource) { SimpleResource.get(:name => 'george xvi') }
|
29
|
+
|
30
|
+
it { should be_kind_of(SimpleResource) }
|
31
|
+
|
32
|
+
its(:record) { should be record }
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'getting multiple resources' do
|
36
|
+
subject(:resource_collection) { SimpleResource.all(:name => ["george xvi"]) }
|
37
|
+
|
38
|
+
it { should be_kind_of(Array) }
|
39
|
+
|
40
|
+
it 'fetches the models and wraps them' do
|
41
|
+
resource = resource_collection.first
|
42
|
+
resource.should be_kind_of(SimpleResource)
|
43
|
+
resource.record.should == record
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'delegating to the underlying model' do
|
50
|
+
|
51
|
+
subject { SimpleResource.new(record) }
|
52
|
+
|
53
|
+
it 'does respond_to attributes in the model' do
|
54
|
+
subject.should respond_to(:name)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'does not respond_to :id if the model does not have it' do
|
58
|
+
resource = OtherResource.new(OtherModel.new(:name => "foo"))
|
59
|
+
|
60
|
+
resource.should_not respond_to(:id)
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
it 'returns raw results for simple attributes' do
|
65
|
+
record.should_receive(:name).and_call_original
|
66
|
+
subject.name.should == "george xvi"
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'wraps model objects in Resource instances' do
|
70
|
+
record.should_receive(:parent).and_return(parent_record)
|
71
|
+
|
72
|
+
parent = subject.parent
|
73
|
+
|
74
|
+
parent.should be_kind_of(ParentResource)
|
75
|
+
parent.name.should == "george sr"
|
76
|
+
parent.record.should == parent_record
|
77
|
+
end
|
78
|
+
|
79
|
+
context "for serialized array associations" do
|
80
|
+
let(:record) { YamlArrayModel.new(:id => 1)}
|
81
|
+
|
82
|
+
subject { YamlArrayResource.new(record)}
|
83
|
+
|
84
|
+
it 'wraps arrays of model objects in an array of resource instances' do
|
85
|
+
record.should_receive(:parents).and_return(parent_records)
|
86
|
+
|
87
|
+
parents = subject.parents
|
88
|
+
parents.should have(parent_records.size).items
|
89
|
+
parents.should be_kind_of(Array)
|
90
|
+
|
91
|
+
parents.each { |parent| parent.should be_kind_of(ParentResource) }
|
92
|
+
parents.collect { |parent| parent.record }.should =~ parent_records
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'resource_delegate' do
|
98
|
+
let(:other_name) { "foo" }
|
99
|
+
let(:other_attribute) { "other value" }
|
100
|
+
let(:other_record) { OtherModel.new(:name => other_name, :other_attribute => other_attribute)}
|
101
|
+
let(:other_resource) { OtherResource.new(other_record) }
|
102
|
+
|
103
|
+
let(:record) { SimpleModel.new(id: 105, name: "george xvi", other_name: other_name) }
|
104
|
+
|
105
|
+
subject(:resource) { SimpleResource.new(record) }
|
106
|
+
|
107
|
+
before do
|
108
|
+
identity_map.add_records([other_record])
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'delegates to the target' do
|
112
|
+
resource.other_attribute.should == other_attribute
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
context "memoized resource creation" do
|
118
|
+
let(:other_name) { "foo" }
|
119
|
+
let(:other_attribute) { "other value" }
|
120
|
+
let(:other_record) { OtherModel.new(:name => other_name, :other_attribute => other_attribute)}
|
121
|
+
let(:other_resource) { OtherResource.new(other_record) }
|
122
|
+
let(:record) { SimpleModel.new(id: 105, name: "george xvi", other_name: other_name) }
|
123
|
+
|
124
|
+
before do
|
125
|
+
identity_map.add_records([other_record])
|
126
|
+
identity_map.add_records([record])
|
127
|
+
end
|
128
|
+
|
129
|
+
subject(:resource) { SimpleResource.new(record) }
|
130
|
+
|
131
|
+
it 'memoizes related resource creation' do
|
132
|
+
resource.other_resource.should be(SimpleResource.new(record).other_resource)
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
context ".wrap" do
|
139
|
+
it 'memoizes resource creation' do
|
140
|
+
SimpleResource.wrap(record).should be(SimpleResource.wrap(record))
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'works with nil resources, returning an empty set' do
|
144
|
+
wrapped_obj = SimpleResource.wrap(nil)
|
145
|
+
wrapped_obj.should be_kind_of(Array)
|
146
|
+
wrapped_obj.length.should be(0)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'works regardless of the resource class used' do
|
150
|
+
SimpleResource.wrap(record).should be(OtherResource.wrap(record))
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Praxis::Mapper do
|
4
|
+
|
5
|
+
context '.logger' do
|
6
|
+
it 'returns a default logger' do
|
7
|
+
Praxis::Mapper.logger
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'allows assignment of Praxis::Mapper logger' do
|
11
|
+
Praxis::Mapper.logger = Logger.new(STDOUT)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns the assigned logger' do
|
15
|
+
logger = Logger.new(STDOUT)
|
16
|
+
Praxis::Mapper.logger = logger
|
17
|
+
Praxis::Mapper.logger.should === logger
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|