praxis-mapper 3.3 → 3.4.0
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 +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
|