dyna_model 0.0.7 → 0.0.8

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