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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6846f3a009fa64333abe3cb2ec246739bd51eac6
4
- data.tar.gz: 9816dfbed93468cab708a9d7e62184d0ec8dd50b
3
+ metadata.gz: e7b9f2ce4f066cc2efacc2305788cb72c6e7b263
4
+ data.tar.gz: c1c76b006d57dac6e868e43109f37db79931b9b4
5
5
  SHA512:
6
- metadata.gz: 4f60333005ce4fbe58804f91b3641d7e7dee4f82cabb0ae57b1dd910f6f3364a04ab1c2145764ec94c17d75621800be1f140050507dbb8040b4bca3758ece56d
7
- data.tar.gz: de0d8e4d594f3059ff204d5b808454ae9c53df667cf2f2b4bb22be58235a7019a687daf910f36cbd5ed7c0bec54bd1f152e350540cd6c7a3816d6cefd843eb1c
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
@@ -55,6 +55,7 @@ require 'praxis-mapper/resource'
55
55
 
56
56
  require 'praxis-mapper/query/base'
57
57
  require 'praxis-mapper/query/sql'
58
+ require 'praxis-mapper/query/sequel'
58
59
 
59
60
 
60
61
  require 'praxis-mapper/config_hash'
@@ -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
- records
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).collect do |row|
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).collect do |row|
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.each do |record|
499
- if add_record(record)
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
- records_added
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
- return false if @row_keys[model][identity].has_key? key
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
@@ -422,6 +422,10 @@ module Praxis::Mapper
422
422
  end
423
423
  end
424
424
 
425
+ def _data
426
+ @data
427
+ end
428
+
425
429
  end
426
430
 
427
431
  end
@@ -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
- records = []
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
- # create model objects for each row
166
- records += _multi_get(identity, batch)
167
+ rows += _multi_get(identity, batch)
167
168
  end
168
169
 
169
- statistics[:records_loaded] += records.size
170
- records
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.collect do |row|
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
@@ -4,10 +4,6 @@
4
4
  module Praxis::Mapper
5
5
  class Model
6
6
 
7
- def _data
8
- @data
9
- end
10
-
11
7
  def save!
12
8
  @new_record = true
13
9
  unless Praxis::Mapper::IdentityMap.current.add_records([self]).include? self
@@ -1,5 +1,5 @@
1
1
  module Praxis
2
2
  module Mapper
3
- VERSION = "3.3"
3
+ VERSION = "3.4.0"
4
4
  end
5
5
  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(model, 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(rows[0..1])
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!(model).collect(&:id).should =~ rows[0..1].collect { |r| r[:id] }
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(:track) { :parent }
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 'sets the track attribute on the generated query' do
292
- Praxis::Mapper::Support::MemoryQuery.any_instance.should_receive(:multi_get).with(:id, Set.new(stage[:id])).and_return(rows[0..1])
293
- Praxis::Mapper::Support::MemoryQuery.any_instance.should_receive(:track).with(track).at_least(:once).and_call_original
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
- track_field = track
298
- query = Praxis::Mapper::Support::MemoryQuery.new(identity_map,model) do
299
- track track_field
300
- end
320
+ identity_map.all(PersonModel).should be_empty
321
+
322
+ identity_map.finalize_model!(PersonModel, query)
301
323
 
302
- identity_map.finalize_model!(model, query)
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(rows[0..1])
321
- query.should_receive(:multi_get).with(:name, Set.new(['george xvi'])).and_return([rows[2]])
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(rows[0..1])
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(person_rows)
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
- response.should have(3).items
32
+ records = query.multi_get(:id, ids)
34
33
 
35
- response.should eq(rows)
36
- #record = response.first
37
- #record.should be_kind_of(model)
34
+ records.should have(3).items
38
35
 
39
- #rows.first.each do |attribute, value|
40
- # record.send(attribute).should == value
41
- #end
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..result_size).to_a }
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).should =~ rows
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 = subject.execute
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
- subject.execute
175
- subject.statistics[:datastore_interactions].should == 1
175
+ query.execute
176
+ query.statistics[:datastore_interactions].should == 1
176
177
  end
177
178
 
178
179
  it 'times datastore interactions' do
179
- subject.execute
180
- subject.statistics[:datastore_interaction_time].should == 10
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
@@ -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
- end
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: '3.3'
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-02-11 00:00:00.000000000 Z
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