praxis-mapper 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
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