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,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
|