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 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