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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +26 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +4 -0
  5. data/CHANGELOG.md +83 -0
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +102 -0
  8. data/Guardfile +11 -0
  9. data/LICENSE +22 -0
  10. data/README.md +19 -0
  11. data/Rakefile +14 -0
  12. data/lib/praxis-mapper/config_hash.rb +40 -0
  13. data/lib/praxis-mapper/connection_manager.rb +102 -0
  14. data/lib/praxis-mapper/finalizable.rb +38 -0
  15. data/lib/praxis-mapper/identity_map.rb +532 -0
  16. data/lib/praxis-mapper/logging.rb +22 -0
  17. data/lib/praxis-mapper/model.rb +430 -0
  18. data/lib/praxis-mapper/query/base.rb +213 -0
  19. data/lib/praxis-mapper/query/sql.rb +183 -0
  20. data/lib/praxis-mapper/query_statistics.rb +46 -0
  21. data/lib/praxis-mapper/resource.rb +226 -0
  22. data/lib/praxis-mapper/support/factory_girl.rb +104 -0
  23. data/lib/praxis-mapper/support/memory_query.rb +34 -0
  24. data/lib/praxis-mapper/support/memory_repository.rb +44 -0
  25. data/lib/praxis-mapper/support/schema_dumper.rb +66 -0
  26. data/lib/praxis-mapper/support/schema_loader.rb +56 -0
  27. data/lib/praxis-mapper/support.rb +2 -0
  28. data/lib/praxis-mapper/version.rb +5 -0
  29. data/lib/praxis-mapper.rb +60 -0
  30. data/praxis-mapper.gemspec +38 -0
  31. data/spec/praxis-mapper/connection_manager_spec.rb +117 -0
  32. data/spec/praxis-mapper/identity_map_spec.rb +905 -0
  33. data/spec/praxis-mapper/logging_spec.rb +9 -0
  34. data/spec/praxis-mapper/memory_repository_spec.rb +56 -0
  35. data/spec/praxis-mapper/model_spec.rb +389 -0
  36. data/spec/praxis-mapper/query/base_spec.rb +317 -0
  37. data/spec/praxis-mapper/query/sql_spec.rb +184 -0
  38. data/spec/praxis-mapper/resource_spec.rb +154 -0
  39. data/spec/praxis_mapper_spec.rb +21 -0
  40. data/spec/spec_fixtures.rb +12 -0
  41. data/spec/spec_helper.rb +63 -0
  42. data/spec/support/spec_models.rb +215 -0
  43. data/spec/support/spec_resources.rb +39 -0
  44. 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
@@ -0,0 +1,12 @@
1
+ # A test model with an example generator
2
+ class SimpleModel < Praxis::Mapper::Model
3
+ def self.generate
4
+ data = {
5
+ :id => /\d{10}/.gen.to_i,
6
+ :name => /\w+/.gen,
7
+ :parent_id => /\d{10}/.gen.to_i
8
+ }
9
+
10
+ self.new(data)
11
+ end
12
+ end