praxis-mapper 3.3 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -3
- data/lib/praxis-mapper.rb +1 -0
- data/lib/praxis-mapper/identity_map.rb +19 -42
- data/lib/praxis-mapper/model.rb +4 -0
- data/lib/praxis-mapper/query/base.rb +15 -8
- data/lib/praxis-mapper/query/sequel.rb +100 -0
- data/lib/praxis-mapper/query/sql.rb +3 -0
- data/lib/praxis-mapper/resource.rb +0 -18
- data/lib/praxis-mapper/support/factory_girl.rb +0 -4
- data/lib/praxis-mapper/version.rb +1 -1
- data/spec/praxis-mapper/identity_map_spec.rb +62 -28
- data/spec/praxis-mapper/query/base_spec.rb +11 -10
- data/spec/praxis-mapper/query/sequel_spec.rb +70 -0
- data/spec/praxis-mapper/query/sql_spec.rb +14 -6
- data/spec/praxis-mapper/resource_spec.rb +0 -2
- data/spec/support/spec_models.rb +24 -3
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7b9f2ce4f066cc2efacc2305788cb72c6e7b263
|
4
|
+
data.tar.gz: c1c76b006d57dac6e868e43109f37db79931b9b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f4e0bc3f8e16c1b73a6268c5f73b4c57a692aefaa600f5113280b44f37c7543535be23717b90ca5e79c99e9fa7ade59e4205a829c1035ffd9f0b023409df882
|
7
|
+
data.tar.gz: ca517a3d90bbf750aacc028bd6a1b6f0ee22bb82c632a1777933ca68e2714a73f7e64311d337f975a7d4b02c1fe35fbca02a6eccc1bf182d8d716203027c6a79
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
## next
|
4
4
|
|
5
|
+
## 3.4.0
|
6
|
+
|
7
|
+
* Add preliminary Sequel-centric `Query::Sequel` class that exposes a a Sequel dataset with an interface compatible with other `Query` types.
|
8
|
+
* Removed `member_name` and `collection_name` methods from `Resource`, as well as the `type` alias.
|
9
|
+
* Fixed `IdentityMap#load` to return the records from the identity map corresponding to rows returned from the database. This fixes a bug where loading rows already existing in the identity map would return new record objects rather than the objects in the map.
|
10
|
+
|
5
11
|
## 3.3.0
|
6
12
|
|
7
13
|
* Tightened up handling of missing records in `IdentityMap#all` and `Resource.wrap`. They now will never return `nil` values.
|
@@ -9,7 +15,7 @@
|
|
9
15
|
|
10
16
|
## 3.2.0
|
11
17
|
|
12
|
-
* Add `Resource.decorate` for extending/overriding methods on Resource associations. See `PersonResource` in [spec_resources.rb](spec/support/spec_resources.rb) for usage.
|
18
|
+
* Add `Resource.decorate` for extending/overriding methods on Resource associations. See `PersonResource` in [spec_resources.rb](spec/support/spec_resources.rb) for usage.
|
13
19
|
|
14
20
|
## 3.1.2
|
15
21
|
|
@@ -18,7 +24,7 @@
|
|
18
24
|
|
19
25
|
## 3.1
|
20
26
|
|
21
|
-
* Begin migration to Sequel for `Query::Sql`.
|
27
|
+
* Begin migration to Sequel for `Query::Sql`.
|
22
28
|
* `#_execute` uses it for a database-agnostic adapter for running raw SQL.
|
23
29
|
* `#_multi_get` uses it generate a datbase-agnostic where clause
|
24
30
|
* Added accessor generation for `one_to_many` associations in resources.
|
@@ -29,7 +35,7 @@
|
|
29
35
|
* Added `Model#identities` to get a hash of identities and values for a record.
|
30
36
|
* Auto-generated attribute accessors on `Blueprint` do not load (coerce) the value using the attribute. Ensure values passed in already have the appropriate types. Blueprint attributes will still be wrapped properly, however.
|
31
37
|
* Performance and memory use optimizations.
|
32
|
-
* `IdentityMap#load` now supports eagerly-loading associated records in a query, and supports the full set of options on the inner query, including
|
38
|
+
* `IdentityMap#load` now supports eagerly-loading associated records in a query, and supports the full set of options on the inner query, including
|
33
39
|
* Tracked `:one_to_many` associations now support where clauses. Using `where` clauses when tracking other association types is not supported and will raise an exception.
|
34
40
|
|
35
41
|
|
data/lib/praxis-mapper.rb
CHANGED
@@ -107,6 +107,7 @@ module Praxis::Mapper
|
|
107
107
|
@secondary_indexes = Hash.new
|
108
108
|
end
|
109
109
|
|
110
|
+
|
110
111
|
def load(model, &block)
|
111
112
|
raise "Can't load unfinalized model #{model}" unless model.finalized?
|
112
113
|
|
@@ -119,7 +120,7 @@ module Praxis::Mapper
|
|
119
120
|
end
|
120
121
|
|
121
122
|
records = query.execute
|
122
|
-
add_records(records)
|
123
|
+
im_records = add_records(records)
|
123
124
|
|
124
125
|
# TODO: refactor this to better-hide queries?
|
125
126
|
query.freeze
|
@@ -127,7 +128,7 @@ module Praxis::Mapper
|
|
127
128
|
|
128
129
|
subload(model, query,records)
|
129
130
|
|
130
|
-
|
131
|
+
im_records
|
131
132
|
end
|
132
133
|
|
133
134
|
def stage_for!(spec, records)
|
@@ -164,11 +165,7 @@ module Praxis::Mapper
|
|
164
165
|
new_query_class = @connection_manager.repository(associated_model.repository_name)[:query]
|
165
166
|
new_query = new_query_class.new(self,associated_model, &block)
|
166
167
|
|
167
|
-
new_records = new_query.multi_get(key, values)
|
168
|
-
m = spec[:model].new(row)
|
169
|
-
m._query = new_query
|
170
|
-
m
|
171
|
-
end
|
168
|
+
new_records = new_query.multi_get(key, values)
|
172
169
|
|
173
170
|
self.queries[associated_model].add(new_query)
|
174
171
|
|
@@ -239,7 +236,7 @@ module Praxis::Mapper
|
|
239
236
|
non_identities.each do |key|
|
240
237
|
values = @staged[model].delete(key)
|
241
238
|
|
242
|
-
rows = query.multi_get(key, values, select: model.identities)
|
239
|
+
rows = query.multi_get(key, values, select: model.identities, raw: true)
|
243
240
|
rows.each do |row|
|
244
241
|
model.identities.each do |identity|
|
245
242
|
if identity.kind_of? Array
|
@@ -259,18 +256,12 @@ module Praxis::Mapper
|
|
259
256
|
next if values.empty?
|
260
257
|
|
261
258
|
query.where = nil # clear out any where clause from non-identity
|
262
|
-
records = query.multi_get(identity_name, values)
|
263
|
-
m = model.new(row)
|
264
|
-
m._query = query
|
265
|
-
m
|
266
|
-
end
|
267
|
-
|
268
|
-
add_records(records)
|
259
|
+
records = query.multi_get(identity_name, values)
|
269
260
|
|
270
261
|
# TODO: refactor this to better-hide queries?
|
271
262
|
self.queries[model].add(query)
|
272
263
|
|
273
|
-
results.merge(records)
|
264
|
+
results.merge(add_records(records))
|
274
265
|
|
275
266
|
# add nil records for records that were not found by the multi_get
|
276
267
|
missing_keys = self.get_staged(model,identity_name)
|
@@ -389,22 +380,6 @@ module Praxis::Mapper
|
|
389
380
|
@connection_manager.checkout(name)
|
390
381
|
end
|
391
382
|
|
392
|
-
|
393
|
-
def <<(record)
|
394
|
-
model = record.class
|
395
|
-
|
396
|
-
@rows[model] << record
|
397
|
-
record.identity_map = self
|
398
|
-
|
399
|
-
model.identities.each do |identity|
|
400
|
-
key = record.send(identity)
|
401
|
-
|
402
|
-
get_staged(model, identity).delete(key)
|
403
|
-
@row_keys[model][identity][key] = record
|
404
|
-
end
|
405
|
-
end
|
406
|
-
|
407
|
-
|
408
383
|
def extract_keys(field, records)
|
409
384
|
row_keys = []
|
410
385
|
if field.kind_of?(Array) # composite identities
|
@@ -463,9 +438,7 @@ module Praxis::Mapper
|
|
463
438
|
|
464
439
|
|
465
440
|
def add_records(records)
|
466
|
-
return if records.empty?
|
467
|
-
|
468
|
-
records_added = Array.new
|
441
|
+
return [] if records.empty?
|
469
442
|
|
470
443
|
to_stage = Hash.new do |hash,staged_model|
|
471
444
|
hash[staged_model] = Hash.new do |identities, identity_name|
|
@@ -495,28 +468,30 @@ module Praxis::Mapper
|
|
495
468
|
|
496
469
|
end
|
497
470
|
|
498
|
-
records.
|
499
|
-
|
500
|
-
records_added << record
|
501
|
-
end
|
471
|
+
im_records = records.collect do |record|
|
472
|
+
add_record(record)
|
502
473
|
end
|
503
474
|
|
504
475
|
to_stage.each do |model_to_stage, data|
|
505
476
|
stage(model_to_stage, data)
|
506
477
|
end
|
507
478
|
|
508
|
-
|
479
|
+
im_records
|
509
480
|
end
|
510
481
|
|
511
482
|
|
483
|
+
# return the record provided (if added to the identity map)
|
484
|
+
# or return the corresponding record if it was already present
|
512
485
|
def add_record(record)
|
513
486
|
model = record.class
|
514
487
|
record.identities.each do |identity, key|
|
515
488
|
# FIXME: Should we be overwriting (possibly) a "nil" value from before?
|
516
489
|
# (due to that row not being found by a previous query)
|
517
490
|
# (That'd be odd since that means we tried to load that same identity)
|
518
|
-
|
519
|
-
|
491
|
+
if (existing = @row_keys[model][identity][key])
|
492
|
+
# FIXME: should merge record into existing to add any additional fields
|
493
|
+
return existing
|
494
|
+
end
|
520
495
|
|
521
496
|
get_staged(model, identity).delete(key)
|
522
497
|
@row_keys[model][identity][key] = record
|
@@ -527,6 +502,8 @@ module Praxis::Mapper
|
|
527
502
|
record
|
528
503
|
end
|
529
504
|
|
505
|
+
alias_method :<<, :add_record
|
506
|
+
|
530
507
|
def query_statistics
|
531
508
|
QueryStatistics.new(queries)
|
532
509
|
end
|
data/lib/praxis-mapper/model.rb
CHANGED
@@ -148,30 +148,34 @@ module Praxis::Mapper
|
|
148
148
|
#
|
149
149
|
# @param identity [Symbol|Array] a simple or composite key for this model
|
150
150
|
# @param values [Array] list of identifier values (ideally a sorted set)
|
151
|
+
# @param select [Array] list of field names to select
|
152
|
+
# @param raw [Boolean] return raw hashes instead of models (default false)
|
151
153
|
# @return [Array] list of matching records, wrapped as models
|
152
|
-
def multi_get(identity, values, select: nil)
|
154
|
+
def multi_get(identity, values, select: nil, raw: false)
|
153
155
|
if self.frozen?
|
154
156
|
raise TypeError.new "can not reuse a frozen query"
|
155
157
|
end
|
156
158
|
|
157
159
|
statistics[:multi_get] += 1
|
158
160
|
|
159
|
-
|
161
|
+
rows = []
|
160
162
|
|
161
163
|
original_select = @select
|
162
164
|
self.select *select.flatten.uniq if select
|
163
165
|
|
164
166
|
values.each_slice(MULTI_GET_BATCH_SIZE) do |batch|
|
165
|
-
|
166
|
-
records += _multi_get(identity, batch)
|
167
|
+
rows += _multi_get(identity, batch)
|
167
168
|
end
|
168
169
|
|
169
|
-
statistics[:records_loaded] +=
|
170
|
-
|
170
|
+
statistics[:records_loaded] += rows.size
|
171
|
+
|
172
|
+
return rows if raw
|
173
|
+
to_records(rows)
|
171
174
|
ensure
|
172
175
|
@select = original_select unless self.frozen?
|
173
176
|
end
|
174
177
|
|
178
|
+
|
175
179
|
# Executes assembled read query and returns all matching records.
|
176
180
|
#
|
177
181
|
# @return [Array] list of matching records, wrapped as models
|
@@ -184,14 +188,17 @@ module Praxis::Mapper
|
|
184
188
|
rows = _execute
|
185
189
|
|
186
190
|
statistics[:records_loaded] += rows.size
|
187
|
-
rows
|
191
|
+
to_records(rows)
|
192
|
+
end
|
193
|
+
|
194
|
+
def to_records(rows)
|
195
|
+
rows.collect do |row|
|
188
196
|
m = model.new(row)
|
189
197
|
m._query = self
|
190
198
|
m
|
191
199
|
end
|
192
200
|
end
|
193
201
|
|
194
|
-
|
195
202
|
# Subclasses Must Implement
|
196
203
|
def _multi_get(identity, values)
|
197
204
|
raise "subclass responsibility"
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
module Praxis::Mapper
|
4
|
+
module Query
|
5
|
+
|
6
|
+
# Sequel-centric query class
|
7
|
+
class Sequel < Base
|
8
|
+
|
9
|
+
|
10
|
+
def initialize(identity_map, model, &block)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def dataset
|
15
|
+
ds = connection[model.table_name.to_sym]
|
16
|
+
|
17
|
+
# TODO: support column aliases
|
18
|
+
if @select
|
19
|
+
ds = ds.select(*@select.keys)
|
20
|
+
end
|
21
|
+
|
22
|
+
if @where
|
23
|
+
ds = ds.where(@where)
|
24
|
+
end
|
25
|
+
|
26
|
+
if @limit
|
27
|
+
ds = ds.limit(@limit)
|
28
|
+
end
|
29
|
+
|
30
|
+
ds
|
31
|
+
end
|
32
|
+
|
33
|
+
# Executes a 'SELECT' statement.
|
34
|
+
#
|
35
|
+
# @param identity [Symbol|Array] a simple or composite key for this model
|
36
|
+
# @param values [Array] list of identifier values (ideally a sorted set)
|
37
|
+
# @return [Array] SQL result set
|
38
|
+
#
|
39
|
+
# @example numeric key
|
40
|
+
# _multi_get(:id, [1, 2])
|
41
|
+
# @example string key
|
42
|
+
# _multi_get(:uid, ['foo', 'bar'])
|
43
|
+
# @example composite key (possibly a combination of numeric and string keys)
|
44
|
+
# _multi_get([:cloud_id, :account_id], [['foo1', 'bar1'], ['foo2', 'bar2']])
|
45
|
+
def _multi_get(identity, values)
|
46
|
+
ds = self.dataset.where(identity => values)
|
47
|
+
_execute(ds)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Executes this SQL statement.
|
51
|
+
# Does not perform any validation of the statement before execution.
|
52
|
+
#
|
53
|
+
# @return [Array] result-set
|
54
|
+
def _execute(ds=nil)
|
55
|
+
Praxis::Mapper.logger.debug "SQL:\n#{self.describe}\n"
|
56
|
+
self.statistics[:datastore_interactions] += 1
|
57
|
+
start_time = Time.now
|
58
|
+
|
59
|
+
rows = if @raw_query
|
60
|
+
unless ds.nil?
|
61
|
+
warn 'WARNING: Query::Sequel#_execute ignoring passed dataset due to previously-specified raw SQL'
|
62
|
+
end
|
63
|
+
connection.run(@raw_query).to_a
|
64
|
+
else
|
65
|
+
(ds || self.dataset).to_a
|
66
|
+
end
|
67
|
+
|
68
|
+
self.statistics[:datastore_interaction_time] += (Time.now - start_time)
|
69
|
+
return rows
|
70
|
+
end
|
71
|
+
|
72
|
+
# @see #sql
|
73
|
+
def describe
|
74
|
+
self.sql
|
75
|
+
end
|
76
|
+
|
77
|
+
# Constructs a raw SQL statement.
|
78
|
+
# No validation is performed here (security risk?).
|
79
|
+
#
|
80
|
+
# @param sql_text a custom SQL query
|
81
|
+
#
|
82
|
+
def raw(sql_text)
|
83
|
+
@raw_query = sql_text
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return [String] raw or assembled SQL statement
|
87
|
+
def sql
|
88
|
+
if @raw_query
|
89
|
+
@raw_query
|
90
|
+
else
|
91
|
+
dataset.sql
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -75,6 +75,9 @@ module Praxis::Mapper
|
|
75
75
|
self.statistics[:datastore_interactions] += 1
|
76
76
|
start_time = Time.now
|
77
77
|
|
78
|
+
if @where && @raw_query
|
79
|
+
warn 'WARNING: Query::Sql#_execute ignoring requested `where` clause due to specified raw SQL'
|
80
|
+
end
|
78
81
|
rows = connection.fetch(self.sql).to_a
|
79
82
|
|
80
83
|
self.statistics[:datastore_interaction_time] += (Time.now - start_time)
|
@@ -259,23 +259,5 @@ module Praxis::Mapper
|
|
259
259
|
end
|
260
260
|
end
|
261
261
|
|
262
|
-
def self.member_name
|
263
|
-
@_member_name ||= self.name.split("::").last.underscore
|
264
|
-
end
|
265
|
-
|
266
|
-
def self.collection_name
|
267
|
-
@_collection_name ||= self.member_name.pluralize
|
268
|
-
end
|
269
|
-
|
270
|
-
def member_name
|
271
|
-
self.class.member_name
|
272
|
-
end
|
273
|
-
|
274
|
-
alias :type :member_name
|
275
|
-
|
276
|
-
def collection_name
|
277
|
-
self.class.collection_name
|
278
|
-
end
|
279
|
-
|
280
262
|
end
|
281
263
|
end
|
@@ -14,6 +14,7 @@ describe Praxis::Mapper::IdentityMap do
|
|
14
14
|
{:id => 3, :name => "george xvi", :parent_id => 2, :description => "three"}
|
15
15
|
|
16
16
|
]}
|
17
|
+
let(:records) { rows.collect { |row| m = model.new(row); m._query = record_query; m } }
|
17
18
|
|
18
19
|
let(:person_rows) {[
|
19
20
|
{id: 1, email: "one@example.com", address_id: 1, prior_address_ids:JSON.dump([2,3])},
|
@@ -23,7 +24,7 @@ describe Praxis::Mapper::IdentityMap do
|
|
23
24
|
{id: 5, email: "five@example.com", address_id: 3, prior_address_ids: nil}
|
24
25
|
|
25
26
|
]}
|
26
|
-
|
27
|
+
let(:person_records) { person_rows.collect { |row| m = PersonModel.new(row); m._query = record_query; m } }
|
27
28
|
let(:address_rows) {[
|
28
29
|
{id: 1, owner_id: 1, state: 'OR'},
|
29
30
|
{id: 2, owner_id: 3, state: 'CA'},
|
@@ -108,6 +109,24 @@ describe Praxis::Mapper::IdentityMap do
|
|
108
109
|
|
109
110
|
end
|
110
111
|
|
112
|
+
|
113
|
+
context 'loading a record that has previously been loaded' do
|
114
|
+
before do
|
115
|
+
identity_map.load PersonModel do
|
116
|
+
where id: 1
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'returns the original object' do
|
121
|
+
existing_person = identity_map.get PersonModel, id: 1
|
122
|
+
people = identity_map.load PersonModel
|
123
|
+
people.find { |p| p.id == 1 }.should be(existing_person)
|
124
|
+
|
125
|
+
existing_people_ids = identity_map.all(PersonModel).collect(&:object_id)
|
126
|
+
people.collect(&:object_id).should =~ existing_people_ids
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
111
130
|
context 'where :staged' do
|
112
131
|
after do
|
113
132
|
identity_map.load AddressModel do
|
@@ -267,39 +286,43 @@ describe Praxis::Mapper::IdentityMap do
|
|
267
286
|
|
268
287
|
|
269
288
|
context "#finalize_model!" do
|
289
|
+
let(:record_query) { nil }
|
270
290
|
|
271
291
|
context 'with values staged for a single identity' do
|
272
292
|
let(:stage) { {:id => [1,2] } }
|
273
293
|
|
274
294
|
before do
|
275
|
-
identity_map.stage(
|
295
|
+
identity_map.stage(PersonModel, stage)
|
276
296
|
end
|
277
297
|
|
278
298
|
it "does a multi-get for the unloaded ids and returns the query results" do
|
279
299
|
Praxis::Mapper::Support::MemoryQuery.any_instance.
|
280
300
|
should_receive(:multi_get).
|
281
301
|
with(:id, Set.new(stage[:id])).
|
282
|
-
and_return(
|
302
|
+
and_return(person_records[0..1])
|
283
303
|
Praxis::Mapper::Support::MemoryQuery.any_instance.should_receive(:freeze)
|
284
304
|
|
285
|
-
identity_map.finalize_model!(
|
305
|
+
identity_map.finalize_model!(PersonModel).collect(&:id).should =~ person_rows[0..1].collect { |r| r[:id] }
|
286
306
|
end
|
287
307
|
|
288
308
|
context 'tracking associations' do
|
289
|
-
let(:
|
309
|
+
let(:query) do
|
310
|
+
Praxis::Mapper::Support::MemoryQuery.new(identity_map,PersonModel) do
|
311
|
+
track :address
|
312
|
+
end
|
313
|
+
end
|
314
|
+
let(:record_query) { query }
|
290
315
|
|
291
|
-
it '
|
292
|
-
|
293
|
-
|
294
|
-
Praxis::Mapper::Support::MemoryQuery.any_instance.should_receive(:track).at_least(:once).and_call_original
|
295
|
-
Praxis::Mapper::Support::MemoryQuery.any_instance.should_receive(:freeze)
|
316
|
+
it 'stages asssociated identities' do
|
317
|
+
query.should_receive(:multi_get).with(:id, Set.new(stage[:id])).and_return(person_records[0..1])
|
318
|
+
query.should_receive(:freeze)
|
296
319
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
end
|
320
|
+
identity_map.all(PersonModel).should be_empty
|
321
|
+
|
322
|
+
identity_map.finalize_model!(PersonModel, query)
|
301
323
|
|
302
|
-
identity_map.
|
324
|
+
identity_map.all(PersonModel).should =~ person_records[0..1]
|
325
|
+
identity_map.get_staged(AddressModel, :id).should eq(Set[*person_records[0..1].collect(&:address_id)])
|
303
326
|
end
|
304
327
|
end
|
305
328
|
|
@@ -317,8 +340,8 @@ describe Praxis::Mapper::IdentityMap do
|
|
317
340
|
query = Praxis::Mapper::Support::MemoryQuery.new(identity_map, model)
|
318
341
|
Praxis::Mapper::Support::MemoryQuery.stub(:new).and_return(query)
|
319
342
|
|
320
|
-
query.should_receive(:multi_get).with(:id, Set.new(stage[:id])).and_return(
|
321
|
-
query.should_receive(:multi_get).with(:name, Set.new(['george xvi'])).and_return([
|
343
|
+
query.should_receive(:multi_get).with(:id, Set.new(stage[:id])).and_return(records[0..1])
|
344
|
+
query.should_receive(:multi_get).with(:name, Set.new(['george xvi'])).and_return([records[2]])
|
322
345
|
query.should_receive(:freeze).once
|
323
346
|
|
324
347
|
expected = rows.collect { |r| r[:id] }
|
@@ -335,7 +358,7 @@ describe Praxis::Mapper::IdentityMap do
|
|
335
358
|
before do
|
336
359
|
identity_map.stage(model, stage)
|
337
360
|
|
338
|
-
Praxis::Mapper::Support::MemoryQuery.any_instance.should_receive(:multi_get).with(:id, Set.new(stage[:id])).and_return(
|
361
|
+
Praxis::Mapper::Support::MemoryQuery.any_instance.should_receive(:multi_get).with(:id, Set.new(stage[:id])).and_return(records[0..1])
|
339
362
|
Praxis::Mapper::Support::MemoryQuery.any_instance.should_receive(:freeze)
|
340
363
|
end
|
341
364
|
|
@@ -362,9 +385,9 @@ describe Praxis::Mapper::IdentityMap do
|
|
362
385
|
Praxis::Mapper::Support::MemoryQuery.stub(:new).and_return(query)
|
363
386
|
|
364
387
|
query.should_receive(:multi_get).
|
365
|
-
with(:owner_id, Set.new(stage[:owner_id]), select: [:id]).ordered.and_return(owner_id_response)
|
388
|
+
with(:owner_id, Set.new(stage[:owner_id]), select: [:id], raw: true).ordered.and_return(owner_id_response)
|
366
389
|
query.should_receive(:multi_get).
|
367
|
-
with(:id, Set.new(stage[:id])).ordered.and_return(
|
390
|
+
with(:id, Set.new(stage[:id])).ordered.and_return(person_records)
|
368
391
|
|
369
392
|
query.should_receive(:freeze)
|
370
393
|
end
|
@@ -558,6 +581,25 @@ describe Praxis::Mapper::IdentityMap do
|
|
558
581
|
|
559
582
|
context "#add_records" do
|
560
583
|
|
584
|
+
context 'with records already existing in the identity_map' do
|
585
|
+
|
586
|
+
before do
|
587
|
+
identity_map.load PersonModel
|
588
|
+
end
|
589
|
+
|
590
|
+
it 'returns the existing records instead of the ones passed in' do
|
591
|
+
person_row = person_rows.first
|
592
|
+
person_record = PersonModel.new(person_row)
|
593
|
+
|
594
|
+
existing_person = identity_map.get(PersonModel, id: person_record.id )
|
595
|
+
|
596
|
+
add_result = identity_map.add_records([person_record])
|
597
|
+
|
598
|
+
add_result.first.should be(existing_person)
|
599
|
+
add_result.first.should_not be(person_record)
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
561
603
|
|
562
604
|
context "with a tracked many_to_one association " do
|
563
605
|
before do
|
@@ -784,14 +826,6 @@ describe Praxis::Mapper::IdentityMap do
|
|
784
826
|
end
|
785
827
|
|
786
828
|
context '#row_by_key' do
|
787
|
-
let(:stage) { {:id => [1,2,3,4]} }
|
788
|
-
before do
|
789
|
-
Praxis::Mapper::Support::MemoryQuery.any_instance.should_receive(:multi_get).with(:id, Set.new(stage[:id])).and_return(rows)
|
790
|
-
Praxis::Mapper::Support::MemoryQuery.any_instance.should_receive(:freeze)
|
791
|
-
identity_map.stage(model,stage)
|
792
|
-
identity_map.finalize_model!(model)
|
793
|
-
end
|
794
|
-
|
795
829
|
it 'raises UnloadedRecordException for unknown records' do
|
796
830
|
expect { identity_map.row_by_key(model,:id, 5) }.to raise_error(Praxis::Mapper::IdentityMap::UnloadedRecordException)
|
797
831
|
end
|
@@ -28,17 +28,14 @@ describe Praxis::Mapper::Query::Base do
|
|
28
28
|
|
29
29
|
it 'delegates to the subclass' do
|
30
30
|
query.should_receive(:_multi_get).and_return(rows)
|
31
|
-
response = query.multi_get(:id, ids)
|
32
31
|
|
33
|
-
|
32
|
+
records = query.multi_get(:id, ids)
|
34
33
|
|
35
|
-
|
36
|
-
#record = response.first
|
37
|
-
#record.should be_kind_of(model)
|
34
|
+
records.should have(3).items
|
38
35
|
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
records.zip(rows).each do |record, row|
|
37
|
+
record._data.should be(row)
|
38
|
+
end
|
42
39
|
|
43
40
|
end
|
44
41
|
|
@@ -53,7 +50,7 @@ describe Praxis::Mapper::Query::Base do
|
|
53
50
|
|
54
51
|
let(:result_size) { (batch_size * 2.5).to_i }
|
55
52
|
|
56
|
-
let(:values) { (0
|
53
|
+
let(:values) { (0...result_size).to_a }
|
57
54
|
let(:rows) { values.collect { |v| {:id => v} } }
|
58
55
|
|
59
56
|
before do
|
@@ -66,7 +63,11 @@ describe Praxis::Mapper::Query::Base do
|
|
66
63
|
end
|
67
64
|
|
68
65
|
it 'batches queries and aggregates their results' do # FIXME: totally lame name for this
|
69
|
-
query.multi_get(:id, values)
|
66
|
+
records = query.multi_get(:id, values)
|
67
|
+
records.size.should eq(result_size)
|
68
|
+
records.zip(rows) do |record,row|
|
69
|
+
record._data.should be(row)
|
70
|
+
end
|
70
71
|
end
|
71
72
|
end
|
72
73
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Praxis::Mapper::Query::Sequel do
|
4
|
+
let(:scope) { {} }
|
5
|
+
let(:identity_map) { Praxis::Mapper::IdentityMap.setup!(scope) }
|
6
|
+
let(:connection) { identity_map.connection(:sql) }
|
7
|
+
|
8
|
+
context "without a query body" do
|
9
|
+
subject { Praxis::Mapper::Query::Sequel.new(identity_map, ItemModel) }
|
10
|
+
|
11
|
+
its(:sql) { should eq("SELECT * FROM items") }
|
12
|
+
end
|
13
|
+
|
14
|
+
subject(:query) do
|
15
|
+
Praxis::Mapper::Query::Sequel.new(identity_map, ItemModel) do
|
16
|
+
select :id
|
17
|
+
select :name
|
18
|
+
where name: 'something'
|
19
|
+
limit 10
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
its(:select) { should eq({:id=>nil, :name=>nil}) }
|
25
|
+
its(:where) { should eq({name: 'something'}) }
|
26
|
+
its(:limit) { should eq(10) }
|
27
|
+
|
28
|
+
its(:sql) { should eq("SELECT id, name FROM items WHERE (name = 'something') LIMIT 10")}
|
29
|
+
|
30
|
+
context 'multi_get' do
|
31
|
+
it 'runs the correct sql' do
|
32
|
+
connection.sqls.should be_empty
|
33
|
+
query.multi_get(:id, [1,2])
|
34
|
+
connection.sqls.should eq(["SELECT id, name FROM items WHERE ((name = 'something') AND (id IN (1, 2))) LIMIT 10"])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'execute' do
|
39
|
+
it 'runs the correct sql' do
|
40
|
+
connection.sqls.should be_empty
|
41
|
+
query.execute
|
42
|
+
connection.sqls.should eq(["SELECT id, name FROM items WHERE (name = 'something') LIMIT 10"])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
context 'with raw sql queries' do
|
48
|
+
subject(:query) do
|
49
|
+
Praxis::Mapper::Query::Sequel.new(identity_map, ItemModel) do
|
50
|
+
raw 'select something from somewhere limit a-few'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
its(:sql) { should eq('select something from somewhere limit a-few') }
|
55
|
+
it 'uses the raw query when executed' do
|
56
|
+
connection.sqls.should be_empty
|
57
|
+
query.execute
|
58
|
+
connection.sqls.should eq(["select something from somewhere limit a-few"])
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'warns in _execute if a dataset is passed' do
|
62
|
+
connection.sqls.should be_empty
|
63
|
+
query.should_receive(:warn).with("WARNING: Query::Sequel#_execute ignoring passed dataset due to previously-specified raw SQL")
|
64
|
+
query._execute(query.dataset)
|
65
|
+
connection.sqls.should eq(["select something from somewhere limit a-few"])
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -48,6 +48,7 @@ describe Praxis::Mapper::Query::Sql do
|
|
48
48
|
it 'uses the exact raw query for the final SQL statement' do
|
49
49
|
subject.sql.should eq("SELECT id, parent_id\nFROM table\nWHERE id=123\nGROUP BY id")
|
50
50
|
end
|
51
|
+
|
51
52
|
end
|
52
53
|
|
53
54
|
|
@@ -156,7 +157,7 @@ describe Praxis::Mapper::Query::Sql do
|
|
156
157
|
{:id => 3, :name => "snafu", :parent_id => 3}
|
157
158
|
] }
|
158
159
|
|
159
|
-
subject { Praxis::Mapper::Query::Sql.new(identity_map, SimpleModel) }
|
160
|
+
subject(:query) { Praxis::Mapper::Query::Sql.new(identity_map, SimpleModel) }
|
160
161
|
before do
|
161
162
|
connection.should_receive(:fetch).and_return(rows)
|
162
163
|
identity_map.should_receive(:connection).with(:default).and_return(connection)
|
@@ -166,18 +167,25 @@ describe Praxis::Mapper::Query::Sql do
|
|
166
167
|
end
|
167
168
|
|
168
169
|
it 'wraps database results in SimpleModel instances' do
|
169
|
-
records =
|
170
|
+
records = query.execute
|
170
171
|
records.each { |record| record.should be_kind_of(SimpleModel) }
|
171
172
|
end
|
172
173
|
|
173
174
|
it "tracks datastore interactions" do
|
174
|
-
|
175
|
-
|
175
|
+
query.execute
|
176
|
+
query.statistics[:datastore_interactions].should == 1
|
176
177
|
end
|
177
178
|
|
178
179
|
it 'times datastore interactions' do
|
179
|
-
|
180
|
-
|
180
|
+
query.execute
|
181
|
+
query.statistics[:datastore_interaction_time].should == 10
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'warns when if a where clause and raw sql are used together' do
|
185
|
+
query.should_receive(:warn).with("WARNING: Query::Sql#_execute ignoring requested `where` clause due to specified raw SQL")
|
186
|
+
query.where 'id=1'
|
187
|
+
query.raw 'select * from stuff'
|
188
|
+
query.execute
|
181
189
|
end
|
182
190
|
|
183
191
|
end
|
@@ -17,8 +17,6 @@ describe Praxis::Mapper::Resource do
|
|
17
17
|
context 'configuration' do
|
18
18
|
subject { SimpleResource }
|
19
19
|
its(:model) { should == model }
|
20
|
-
its(:member_name) { should == 'simple_resource' }
|
21
|
-
its(:collection_name) { should == 'simple_resources' }
|
22
20
|
end
|
23
21
|
|
24
22
|
context 'retrieving resources' do
|
data/spec/support/spec_models.rb
CHANGED
@@ -129,7 +129,7 @@ class PersonModel < Praxis::Mapper::Model
|
|
129
129
|
model AddressModel
|
130
130
|
key :address_id # people.address_id
|
131
131
|
end
|
132
|
-
|
132
|
+
|
133
133
|
one_to_many :properties do
|
134
134
|
model AddressModel
|
135
135
|
primary_key :id #people.id
|
@@ -206,10 +206,31 @@ class AddressModel < Praxis::Mapper::Model
|
|
206
206
|
|
207
207
|
end
|
208
208
|
|
209
|
+
|
210
|
+
|
209
211
|
class ItemModel < Praxis::Mapper::Model
|
210
212
|
table_name 'items'
|
211
213
|
repository_name :sql
|
212
|
-
|
214
|
+
|
215
|
+
identity :id
|
216
|
+
|
217
|
+
one_to_many :parts do
|
218
|
+
model PartModel
|
219
|
+
key :item_id # parts.item_id
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
class PartModel < Praxis::Mapper::Model
|
226
|
+
table_name 'parts'
|
227
|
+
repository_name :sql
|
228
|
+
|
213
229
|
identity :id
|
214
230
|
|
215
|
-
|
231
|
+
many_to_one :item do
|
232
|
+
model ItemModel
|
233
|
+
key :item_id # parts.item_id
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: praxis-mapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josep M. Blanquer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-04-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: randexp
|
@@ -232,6 +232,7 @@ files:
|
|
232
232
|
- lib/praxis-mapper/logging.rb
|
233
233
|
- lib/praxis-mapper/model.rb
|
234
234
|
- lib/praxis-mapper/query/base.rb
|
235
|
+
- lib/praxis-mapper/query/sequel.rb
|
235
236
|
- lib/praxis-mapper/query/sql.rb
|
236
237
|
- lib/praxis-mapper/query_statistics.rb
|
237
238
|
- lib/praxis-mapper/resource.rb
|
@@ -249,6 +250,7 @@ files:
|
|
249
250
|
- spec/praxis-mapper/memory_repository_spec.rb
|
250
251
|
- spec/praxis-mapper/model_spec.rb
|
251
252
|
- spec/praxis-mapper/query/base_spec.rb
|
253
|
+
- spec/praxis-mapper/query/sequel_spec.rb
|
252
254
|
- spec/praxis-mapper/query/sql_spec.rb
|
253
255
|
- spec/praxis-mapper/resource_spec.rb
|
254
256
|
- spec/praxis_mapper_spec.rb
|
@@ -287,6 +289,7 @@ test_files:
|
|
287
289
|
- spec/praxis-mapper/memory_repository_spec.rb
|
288
290
|
- spec/praxis-mapper/model_spec.rb
|
289
291
|
- spec/praxis-mapper/query/base_spec.rb
|
292
|
+
- spec/praxis-mapper/query/sequel_spec.rb
|
290
293
|
- spec/praxis-mapper/query/sql_spec.rb
|
291
294
|
- spec/praxis-mapper/resource_spec.rb
|
292
295
|
- spec/praxis_mapper_spec.rb
|