dyna_model 0.0.7 → 0.0.8

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: 4e9716917403f2d28b549c5e9e9f1a7e144a2082
4
- data.tar.gz: 779225e3e1a8ac16912436c339ee88595fa135f1
3
+ metadata.gz: 2739ba4513b27ea83ebe29ba3de12d9a3f96f1b0
4
+ data.tar.gz: 1277e4a3d77f03f82d27a06777fa05bc1b5fb122
5
5
  SHA512:
6
- metadata.gz: aeb08a60336433d6c658d672ea18ae44ea191d3807a93d4d29d8282c24c9f7052510f05fe6661dd8b9cefeed7b06b20819c59fa8470e028c478109030d14a196
7
- data.tar.gz: c10bc62289f546099b233261b47a3b450a0957bac194e0c1642e757932d8ea0db309cff1ba32c71032d3e60eef977d087ac102294594c27bb7833cb5a167a10a
6
+ metadata.gz: 4eee37ed2c9f653ed8ef651ff4c1e72625e8dbc480a75299752d0d236377ee2b69537f7e21b0a6ebfb278631c223d2e0cde07947b6bdf7b17736b3a7982a6792
7
+ data.tar.gz: f3518b6a0546434f24f54fb57cad8a1ba73cbcc6fd6720ade4d25b3d529b85662261732f5f7b86f90e2d6bbd8f65f0d909286e8a0de06abe805d4af3dbe8c9e0
data/README.md CHANGED
@@ -7,6 +7,13 @@ AWS DynamoDB ORM for Rails based on AWS::Record in the aws-sdk gem. Still a work
7
7
  gem 'dyna_model'
8
8
  ```
9
9
 
10
+ ## Supports
11
+ * Range Querying
12
+ * Scans
13
+ * Local Secondary Indexes
14
+ * Global Secondary Indexes
15
+ * Query Filtering
16
+
10
17
  ## Sample Model
11
18
  ```
12
19
  class Dude
@@ -70,10 +77,9 @@ Dude.create_table
70
77
  Dude.delete_table
71
78
 
72
79
  # Rake tasks
73
- rake ddb:create CLASS=Dude
74
80
  rake ddb:create CLASS=all
75
- rake ddb:destroy CLASS=Dude
76
81
  rake ddb:destroy CLASS=all
82
+ rake ddb:resize CLASS=all
77
83
  ```
78
84
 
79
85
  ## Elasticsearch::Model compatible adapter
@@ -26,6 +26,6 @@ Gem::Specification.new do |spec|
26
26
  spec.add_dependency 'activesupport', '>= 4.0.0'
27
27
  spec.add_dependency 'activemodel', '>= 4.0.0'
28
28
  spec.add_dependency 'rails', '>= 4.0.0'
29
- spec.add_dependency 'aws-sdk', '>= 1.38.0'
29
+ spec.add_dependency 'aws-sdk', '>= 1.39.0'
30
30
  end
31
31
 
@@ -44,15 +44,69 @@ module DynaModel
44
44
  end
45
45
  end
46
46
 
47
+ # OVERRIDE
48
+ # https://github.com/aws/aws-sdk-ruby/blob/master/lib/aws/record/abstract_base.rb#L132
49
+ # pass options for 'expected'
50
+ public
51
+ def save opts = {}
52
+ if valid?(opts)
53
+ write_response = persisted? ? update(opts) : create(opts)
54
+ clear_changes!
55
+ if opts[:return_values] && opts[:return_values] != :none
56
+ # return the ReturnValues if the user wants them
57
+ write_response
58
+ else
59
+ true
60
+ end
61
+ else
62
+ false
63
+ end
64
+ end
65
+
66
+ # OVERRIDE
67
+ # https://github.com/aws/aws-sdk-ruby/blob/master/lib/aws/record/abstract_base.rb#L176
68
+ # and pass options for 'expected'
69
+ public
70
+ def delete(options={})
71
+ if persisted?
72
+ if deleted?
73
+ raise 'unable to delete, this object has already been deleted'
74
+ else
75
+ resp = delete_storage(options)
76
+ @_deleted = true
77
+ resp
78
+ end
79
+ else
80
+ raise 'unable to delete, this object has not been saved yet'
81
+ end
82
+ end
83
+ alias_method :destroy, :delete
84
+
85
+ # OVERRIDE
86
+ # https://github.com/aws/aws-sdk-ruby/blob/master/lib/aws/record/abstract_base.rb#L265
87
+ # pass options for 'expected'
88
+ private
89
+ def create(options={})
90
+ # Not implemented
91
+ #populate_id
92
+ touch_timestamps('created_at', 'updated_at')
93
+ # Not implemented
94
+ #increment_optimistic_lock_value
95
+ @_persisted = true
96
+ create_storage(options)
97
+ end
98
+
47
99
  # OVERRIDE
48
100
  # https://github.com/aws/aws-sdk-ruby/blob/master/lib/aws/record/abstract_base.rb#L273
49
101
  # AWS::Record::AbstractBase to trigger update even without changes (for callbacks etc)
102
+ # and pass options for 'expected'
50
103
  private
51
- def update
104
+ def update(options={})
52
105
  #return unless changed?
53
106
  touch_timestamps('updated_at')
54
- increment_optimistic_lock_value
55
- update_storage
107
+ # Not implemented
108
+ #increment_optimistic_lock_value
109
+ update_storage(options)
56
110
  end
57
111
  end
58
112
 
@@ -1,3 +1,4 @@
1
+ # TODO: S3 key schema that allows for timestamp sorting
1
2
  #
2
3
  # Persist DynaModel records for a particular model to S3 for extra backup.
3
4
  #
@@ -15,16 +15,16 @@ module DynaModel
15
15
  end
16
16
 
17
17
  private
18
- def create_storage
18
+ def create_storage(options={})
19
19
  run_callbacks :save do
20
20
  run_callbacks :create do
21
- self.class.dynamo_db_table.write(serialize_attributes)
21
+ self.class.dynamo_db_table.write(serialize_attributes, options)
22
22
  end
23
23
  end
24
24
  end
25
25
 
26
26
  private
27
- def update_storage
27
+ def update_storage(options={})
28
28
  # Only enumerating dirty (i.e. changed) attributes. Empty
29
29
  # (nil and empty set) values are deleted, the others are replaced.
30
30
  run_callbacks :save do
@@ -40,21 +40,21 @@ module DynaModel
40
40
  end
41
41
  end
42
42
 
43
- self.class.dynamo_db_table.write(attr_updates, {
43
+ self.class.dynamo_db_table.write(attr_updates, options.merge({
44
44
  update_item: dynamo_db_item_key_values,
45
45
  shard_name: self.shard
46
- })
46
+ }))
47
47
  end
48
48
  end
49
49
  end
50
50
 
51
51
  private
52
- def delete_storage
52
+ def delete_storage(options={})
53
53
  run_callbacks :destroy do
54
- self.class.dynamo_db_table.delete_item(
54
+ self.class.dynamo_db_table.delete_item(options.merge(
55
55
  delete_item: dynamo_db_item_key_values,
56
56
  shard_name: self.shard
57
- )
57
+ ))
58
58
  end
59
59
  end
60
60
 
@@ -19,7 +19,7 @@ module DynaModel
19
19
  AWS::Record::Attributes::StringAttr => "S",
20
20
  AWS::Record::Attributes::IntegerAttr => "N",
21
21
  AWS::Record::Attributes::FloatAttr => "N",
22
- AWS::Record::Attributes::BooleanAttr => "S",
22
+ AWS::Record::Attributes::BooleanAttr => "N",
23
23
  AWS::Record::Attributes::DateTimeAttr => "S",
24
24
  AWS::Record::Attributes::DateAttr => "S",
25
25
  AWS::Record::Attributes::SerializedAttr => "B"
@@ -18,6 +18,20 @@ module DynaModel
18
18
  ns: "NS"
19
19
  }
20
20
 
21
+ RETURN_VALUES = {
22
+ none: "NONE",
23
+ all_old: "ALL_OLD",
24
+ updated_old: "UPDATED_OLD",
25
+ all_new: "ALL_NEW",
26
+ updated_new: "UPDATED_NEW"
27
+ }
28
+
29
+ RETURN_VALUES_UPDATE_ONLY = [
30
+ :updated_old,
31
+ :all_new,
32
+ :updated_new
33
+ ]
34
+
21
35
  QUERY_SELECT = {
22
36
  all: "ALL_ATTRIBUTES",
23
37
  projected: "ALL_PROJECTED_ATTRIBUTES",
@@ -42,6 +56,11 @@ module DynaModel
42
56
  in: "IN"
43
57
  }
44
58
 
59
+ CONDITIONAL_OPERATOR = {
60
+ and: "AND",
61
+ or: "OR"
62
+ }
63
+
45
64
  COMPARISON_OPERATOR_SCAN_ONLY = [
46
65
  :ne,
47
66
  :not_null,
@@ -51,24 +70,19 @@ module DynaModel
51
70
  :in
52
71
  ]
53
72
 
54
- class << self
55
-
56
-
57
- end
58
-
59
- def self.type_from_value(value)
60
- case
61
- when value.kind_of?(AWS::DynamoDB::Binary) then :b
62
- when value.respond_to?(:to_str) then :s
63
- when value.kind_of?(Numeric) then :n
64
- else
65
- raise ArgumentError, "unsupported attribute type #{value.class}"
66
- end
73
+ def self.type_from_value(value)
74
+ case
75
+ when value.kind_of?(AWS::DynamoDB::Binary) then :b
76
+ when value.respond_to?(:to_str) then :s
77
+ when value.kind_of?(Numeric) then :n
78
+ else
79
+ raise ArgumentError, "unsupported attribute type #{value.class}"
67
80
  end
81
+ end
68
82
 
69
- def self.attr_with_type(attr_name, value)
70
- { attr_name => { TYPE_INDICATOR[type_from_value(value)] => value.to_s } }
71
- end
83
+ def self.attr_with_type(attr_name, value)
84
+ { attr_name => { TYPE_INDICATOR[type_from_value(value)] => value.to_s } }
85
+ end
72
86
 
73
87
  def initialize(model)
74
88
  @model = model
@@ -199,6 +213,7 @@ module DynaModel
199
213
  #AWS::DynamoDB::Errors::ValidationException: ALL_PROJECTED_ATTRIBUTES can be used only when Querying using an IndexName
200
214
  #options[:limit] ||= 10
201
215
  #options[:exclusive_start_key]
216
+ #options[:query_filter]
202
217
 
203
218
  key_conditions = {}
204
219
  gsi = nil
@@ -221,35 +236,33 @@ module DynaModel
221
236
  }
222
237
 
223
238
  if options[:range]
224
- raise ArgumentError, "Expected a 2 element Hash for :range (ex {:age.gt => 13})" unless options[:range].is_a?(Hash) && options[:range].keys.size == 1 && options[:range].keys.first.is_a?(String)
225
- range_key_name, comparison_operator = options[:range].keys.first.split(".")
226
- raise ArgumentError, "Comparison operator must be one of (#{(COMPARISON_OPERATOR.keys - COMPARISON_OPERATOR_SCAN_ONLY).join(", ")})" unless COMPARISON_OPERATOR.keys.include?(comparison_operator.to_sym)
227
- range_key = @range_keys.find{|k| k[:attribute_name] == range_key_name}
239
+ raise ArgumentError, "Table does not use a range key in its schema!" if @range_keys.blank?
240
+ attr_with_condition_hash = self.attr_with_condition(options[:range])
241
+ range_key = @range_keys.find{|k| k[:attribute_name] == attr_with_condition_hash.keys.first}
228
242
  raise ArgumentError, ":range key must be a valid Range attribute" unless range_key
229
- raise ArgumentError, ":range key must be a Range if using the operator BETWEEN" if comparison_operator == "between" && !options[:range].values.first.is_a?(Range)
230
243
 
231
244
  if range_key.has_key?(:index_name) # Local/Global Secondary Index
232
245
  options[:index_name] = range_key[:index_name]
233
246
  query_request[:index_name] = range_key[:index_name]
234
247
  end
235
248
 
236
- range_value = options[:range].values.first
237
- range_attribute_list = []
238
- if comparison_operator == "between"
239
- range_attribute_list << { range_key[:attribute_type] => range_value.min }
240
- range_attribute_list << { range_key[:attribute_type] => range_value.max }
241
- else
242
- # TODO - support Binary?
243
- range_attribute_list = [{ range_key[:attribute_type] => range_value.to_s }]
244
- end
249
+ key_conditions.merge!(attr_with_condition_hash)
250
+ end
245
251
 
246
- key_conditions.merge!({
247
- range_key[:attribute_name] => {
248
- attribute_value_list: range_attribute_list,
249
- comparison_operator: COMPARISON_OPERATOR[comparison_operator.to_sym]
250
- }
251
- })
252
+ query_filter = {}
253
+ conditional_operator = nil
254
+ if options[:query_filter]
255
+ raise ArgumentError, ":query_filter must be a hash" unless options[:query_filter].is_a?(Hash)
256
+ options[:query_filter].each_pair do |k,v|
257
+ query_filter.merge!(self.attr_with_condition({ k => v}))
258
+ end
259
+ if options[:conditional_operator]
260
+ raise ArgumentError, ":condition_operator invalid! Must be one of (#{CONDITIONAL_OPERATOR.keys.join(", ")})" unless CONDITIONAL_OPERATOR[options[:conditional_operator]]
261
+ conditional_operator = CONDITIONAL_OPERATOR[options[:conditional_operator]]
262
+ end
252
263
  end
264
+ query_request.merge!(query_filter: query_filter) unless query_filter.blank?
265
+ query_request.merge!(conditional_operator: conditional_operator) unless conditional_operator.blank? || query_filter.blank?
253
266
 
254
267
  if options[:global_secondary_index] # Override index_name if using GSI
255
268
  # You can only select projected attributes from a GSI
@@ -315,8 +328,22 @@ module DynaModel
315
328
 
316
329
  def write(attributes, options={})
317
330
  options[:return_consumed_capacity] ||= :none
331
+ options[:return_values] ||= :none
318
332
  options[:update_item] = false unless options[:update_item]
319
333
 
334
+ expected = {}
335
+ conditional_operator = nil
336
+ if options[:expected]
337
+ raise ArgumentError, ":expected must be a hash" unless options[:expected].is_a?(Hash)
338
+ options[:expected].each_pair do |k,v|
339
+ expected.merge!(self.attr_with_condition({ k => v}))
340
+ end
341
+ if options[:conditional_operator]
342
+ raise ArgumentError, ":condition_operator invalid! Must be one of (#{CONDITIONAL_OPERATOR.keys.join(", ")})" unless CONDITIONAL_OPERATOR[options[:conditional_operator]]
343
+ conditional_operator = CONDITIONAL_OPERATOR[options[:conditional_operator]]
344
+ end
345
+ end
346
+
320
347
  if options[:update_item]
321
348
  # UpdateItem
322
349
  key_request = {
@@ -346,12 +373,16 @@ module DynaModel
346
373
  })
347
374
  end
348
375
  end
376
+ raise ArgumentError, ":return_values must be one of (#{RETURN_VALUES.keys.join(", ")})" unless RETURN_VALUES[options[:return_values]]
349
377
  update_item_request = {
350
378
  table_name: @model.dynamo_db_table_name(options[:shard_name]),
351
379
  key: key_request,
352
380
  attribute_updates: attrs_to_update,
353
- return_consumed_capacity: RETURNED_CONSUMED_CAPACITY[options[:return_consumed_capacity]]
381
+ return_consumed_capacity: RETURNED_CONSUMED_CAPACITY[options[:return_consumed_capacity]],
382
+ return_values: RETURN_VALUES[options[:return_values]]
354
383
  }
384
+ update_item_request.merge!(expected: expected) unless expected.blank?
385
+ update_item_request.merge!(conditional_operator: conditional_operator) unless conditional_operator.blank? || expected.blank?
355
386
  @model.dynamo_db_client.update_item(update_item_request)
356
387
  else
357
388
  # PutItem
@@ -360,22 +391,30 @@ module DynaModel
360
391
  next if v.blank? # If empty string or nil, skip...
361
392
  items.merge!(self.class.attr_with_type(k,v))
362
393
  end
394
+ raise ArgumentError, ":return_values must be one of (#{(RETURN_VALUES.keys - RETURN_VALUES_UPDATE_ONLY).join(", ")})" unless RETURN_VALUES[options[:return_values]] && !RETURN_VALUES_UPDATE_ONLY.include?(options[:return_values])
363
395
  put_item_request = {
364
396
  table_name: @model.dynamo_db_table_name(options[:shard_name]),
365
397
  item: items,
366
- return_consumed_capacity: RETURNED_CONSUMED_CAPACITY[options[:return_consumed_capacity]]
398
+ return_consumed_capacity: RETURNED_CONSUMED_CAPACITY[options[:return_consumed_capacity]],
399
+ return_values: RETURN_VALUES[options[:return_values]]
367
400
  }
401
+ put_item_request.merge!(expected: expected) unless expected.blank?
402
+ put_item_request.merge!(conditional_operator: conditional_operator) unless conditional_operator.blank? || expected.blank?
368
403
  @model.dynamo_db_client.put_item(put_item_request)
369
404
  end
370
405
  end
371
406
 
372
407
  def delete_item(options={})
373
408
  raise ":delete_item => {...key_values...} required" unless options[:delete_item].present?
409
+ options[:return_consumed_capacity] ||= :none
410
+ options[:return_values] ||= :none
411
+ raise ArgumentError, ":return_values must be one of (#{(RETURN_VALUES.keys - RETURN_VALUES_UPDATE_ONLY).join(", ")})" unless RETURN_VALUES[options[:return_values]] && !RETURN_VALUES_UPDATE_ONLY.include?(options[:return_values])
374
412
  key_request = {
375
413
  @hash_key[:attribute_name] => {
376
414
  @hash_key[:attribute_type] => options[:delete_item][:hash_value].to_s
377
415
  }
378
416
  }
417
+
379
418
  if @primary_range_key
380
419
  raise ArgumentError, "range_key was not provided to the delete_item command" if options[:delete_item][:range_value].blank?
381
420
  key_request.merge!({
@@ -384,10 +423,28 @@ module DynaModel
384
423
  }
385
424
  })
386
425
  end
426
+
427
+ expected = {}
428
+ conditional_operator = nil
429
+ if options[:expected]
430
+ raise ArgumentError, ":expected must be a hash" unless options[:expected].is_a?(Hash)
431
+ options[:expected].each_pair do |k,v|
432
+ expected.merge!(self.attr_with_condition({ k => v}))
433
+ end
434
+ if options[:conditional_operator]
435
+ raise ArgumentError, ":condition_operator invalid! Must be one of (#{CONDITIONAL_OPERATOR.keys.join(", ")})" unless CONDITIONAL_OPERATOR[options[:conditional_operator]]
436
+ conditional_operator = CONDITIONAL_OPERATOR[options[:conditional_operator]]
437
+ end
438
+ end
439
+
387
440
  delete_item_request = {
388
441
  table_name: @model.dynamo_db_table_name(options[:shard_name]),
389
- key: key_request
442
+ key: key_request,
443
+ return_consumed_capacity: RETURNED_CONSUMED_CAPACITY[options[:return_consumed_capacity]],
444
+ return_values: RETURN_VALUES[options[:return_values]]
390
445
  }
446
+ delete_item_request.merge!(expected: expected) unless expected.blank?
447
+ delete_item_request.merge!(conditional_operator: conditional_operator) unless conditional_operator.blank? || expected.blank?
391
448
  @model.dynamo_db_client.delete_item(delete_item_request)
392
449
  end
393
450
 
@@ -420,40 +477,68 @@ module DynaModel
420
477
 
421
478
  # :scan_filter => { :name.begins_with => "a" }
422
479
  scan_filter = {}
480
+ conditional_operator = nil
423
481
  if options[:scan_filter].present?
424
482
  options[:scan_filter].each_pair.each do |k,v|
425
- # Hard to validate attribute types here, so infer by type sent and assume the user knows their own attrs
426
- key_name, comparison_operator = k.split(".")
427
- raise ArgumentError, "Comparison operator must be one of (#{COMPARISON_OPERATOR.keys.join(", ")})" unless COMPARISON_OPERATOR.keys.include?(comparison_operator.to_sym)
428
- raise ArgumentError, "scan_filter value must be a Range if using the operator BETWEEN" if comparison_operator == "between" && !v.is_a?(Range)
429
- raise ArgumentError, "scan_filter value must be a Array if using the operator IN" if comparison_operator == "in" && !v.is_a?(Array)
430
-
431
- attribute_value_list = []
432
- if comparison_operator == "in"
433
- v.each do |in_v|
434
- attribute_value_list << self.class.attr_with_type(key_name, in_v).values.last
435
- end
436
- elsif comparison_operator == "between"
437
- attribute_value_list << self.class.attr_with_type(key_name, v.min).values.last
438
- attribute_value_list << self.class.attr_with_type(key_name, v.max).values.last
439
- else
440
- attribute_value_list << self.class.attr_with_type(key_name, v).values.last
441
- end
442
- scan_filter.merge!({
443
- key_name => {
444
- comparison_operator: COMPARISON_OPERATOR[comparison_operator.to_sym],
445
- attribute_value_list: attribute_value_list
446
- }
447
- })
483
+ scan_filter.merge!(self.attr_with_condition({ k => v}))
448
484
  end
449
- scan_request.merge!(scan_filter: scan_filter)
450
485
  end
451
-
452
- scan_request.merge!({ segment: options[:segment].to_i }) if options[:segment].present?
453
- scan_request.merge!({ total_segments: options[:total_segments].to_i }) if options[:total_segments].present?
486
+ if options[:conditional_operator]
487
+ raise ArgumentError, ":condition_operator invalid! Must be one of (#{CONDITIONAL_OPERATOR.keys.join(", ")})" unless CONDITIONAL_OPERATOR[options[:conditional_operator]]
488
+ conditional_operator = CONDITIONAL_OPERATOR[options[:conditional_operator]]
489
+ end
490
+ scan_request.merge!(scan_filter: scan_filter) unless scan_filter.blank?
491
+ scan_request.merge!(conditional_operator: conditional_operator) unless conditional_operator.blank? || scan_filter.blank?
492
+ scan_request.merge!(segment: options[:segment].to_i) if options[:segment].present?
493
+ scan_request.merge!(total_segments: options[:total_segments].to_i) if options[:total_segments].present?
454
494
 
455
495
  @model.dynamo_db_client.scan(scan_request)
456
496
  end
457
497
 
498
+ protected
499
+
500
+ # {:name.eq => "cary"}
501
+ #
502
+ # return:
503
+ # {
504
+ # "name" => {
505
+ # attribute_value_list: [
506
+ # "S" => "cary"
507
+ # ],
508
+ # comparison_operator: "EQ"
509
+ # }
510
+ # }
511
+ def attr_with_condition(attr_conditional)
512
+ raise ArgumentError, "Expected a 2 element Hash for each :query_filter (ex {:age.gt => 13})" unless attr_conditional.is_a?(Hash) && attr_conditional.keys.size == 1 && attr_conditional.keys.first.is_a?(String)
513
+ attr_name, comparison_operator = attr_conditional.keys.first.split(".")
514
+ raise ArgumentError, "Comparison operator must be one of (#{(COMPARISON_OPERATOR.keys - COMPARISON_OPERATOR_SCAN_ONLY).join(", ")})" unless COMPARISON_OPERATOR.keys.include?(comparison_operator.to_sym)
515
+ attr_key = @model.attributes[attr_name]
516
+ raise ArgumentError, "#{attr_name} not a valid attribute" unless attr_key
517
+ attr_type = @model.attribute_type_indicator(attr_key)
518
+ raise ArgumentError, "#{attr_name} key must be a Range if using the operator BETWEEN" if comparison_operator == "between" && !attr_conditional.values.first.is_a?(Range)
519
+ raise ArgumentError, ":query_filter value must be an Array if using the operator IN" if comparison_operator == "in" && !attr_conditional.values.first.is_a?(Array)
520
+
521
+ attr_value = attr_conditional.values.first
522
+
523
+ attribute_value_list = []
524
+ if comparison_operator == "in"
525
+ attr_conditional.values.first.each do |in_v|
526
+ attribute_value_list << { attr_type => in_v.to_s }
527
+ end
528
+ elsif comparison_operator == "between"
529
+ attribute_value_list << { attr_type => attr_value.min.to_s }
530
+ attribute_value_list << { attr_type => attr_value.max.to_s }
531
+ else
532
+ attribute_value_list = [{ attr_type => attr_value.to_s }]
533
+ end
534
+
535
+ attribute_comparison_hash = {
536
+ comparison_operator: COMPARISON_OPERATOR[comparison_operator.to_sym]
537
+ }
538
+ attribute_comparison_hash.merge!(attribute_value_list: attribute_value_list) unless %w(null not_null).include?(comparison_operator)
539
+
540
+ { attr_name => attribute_comparison_hash }
541
+ end
542
+
458
543
  end
459
544
  end
@@ -1,3 +1,3 @@
1
1
  module DynaModel
2
- VERSION = "0.0.7"
2
+ VERSION = "0.0.8"
3
3
  end
@@ -3,6 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3
3
  describe "DynaModel::Persistence" do
4
4
 
5
5
  before do
6
+ User.delete_table
6
7
  User.create_table
7
8
  @user = User.new
8
9
  @user_attrs = {
@@ -78,4 +79,31 @@ describe "DynaModel::Persistence" do
78
79
  User.read("hash", 3).should be_nil
79
80
  end
80
81
 
82
+ it 'should respect :expected in a save' do
83
+ user = User.new(@user_attrs)
84
+
85
+ expect {
86
+ user.save
87
+ }.not_to raise_error
88
+
89
+ expect {
90
+ user.save(expected: {:name.eq => "wrongname"})
91
+ }.to raise_error(AWS::DynamoDB::Errors::ConditionalCheckFailedException)
92
+
93
+ user.save(expected: {:name.eq => "Kate"}, return_values: :all_old).should be_a AWS::Core::Response
94
+ end
95
+
96
+ it 'should respect :expected in a destroy' do
97
+ user = User.new(@user_attrs)
98
+ user.save
99
+
100
+ expect {
101
+ user.delete(expected: {:name.eq => "wrongname"})
102
+ }.to raise_error(AWS::DynamoDB::Errors::ConditionalCheckFailedException)
103
+
104
+ expect {
105
+ user.delete(expected: {:name.eq => "Kate"})
106
+ }.not_to raise_error
107
+ end
108
+
81
109
  end
@@ -80,6 +80,17 @@ describe "DynaModel::Query" do
80
80
  users.first.name.should == "Cary"
81
81
  end
82
82
 
83
+ it 'should read_range with :query_filter' do
84
+ @user = User.create(@user_attrs)
85
+ @user2 = User.create(@user2_attrs)
86
+ users = User.read_range("Dunn", query_filter: {:name.eq => "Cary"})
87
+ users.length.should == 1
88
+ users.first.name.should == "Cary"
89
+ users = User.read_range("Dunn", query_filter: {:name.in => ["Cary"]})
90
+ users.length.should == 1
91
+ users.first.name.should == "Cary"
92
+ end
93
+
83
94
  it 'should read_first' do
84
95
  @user = User.create(@user_attrs)
85
96
  User.read_first("Dunn", order: :asc).name.should == "Kate"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dyna_model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cary Dunn
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-24 00:00:00.000000000 Z
11
+ date: 2014-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - ">="
116
116
  - !ruby/object:Gem::Version
117
- version: 1.38.0
117
+ version: 1.39.0
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
- version: 1.38.0
124
+ version: 1.39.0
125
125
  description: DyanmoDB ORM on AWS::Record
126
126
  email:
127
127
  - cary.dunn@gmail.com