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 +4 -4
- data/README.md +8 -2
- data/dyna_model.gemspec +1 -1
- data/lib/dyna_model/document.rb +57 -3
- data/lib/dyna_model/extensions/s3_backup.rb +1 -0
- data/lib/dyna_model/persistence.rb +8 -8
- data/lib/dyna_model/schema.rb +1 -1
- data/lib/dyna_model/table.rb +151 -66
- data/lib/dyna_model/version.rb +1 -1
- data/spec/dyna_model/persistence_spec.rb +28 -0
- data/spec/dyna_model/query_spec.rb +11 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2739ba4513b27ea83ebe29ba3de12d9a3f96f1b0
|
4
|
+
data.tar.gz: 1277e4a3d77f03f82d27a06777fa05bc1b5fb122
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/dyna_model.gemspec
CHANGED
@@ -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.
|
29
|
+
spec.add_dependency 'aws-sdk', '>= 1.39.0'
|
30
30
|
end
|
31
31
|
|
data/lib/dyna_model/document.rb
CHANGED
@@ -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
|
-
|
55
|
-
|
107
|
+
# Not implemented
|
108
|
+
#increment_optimistic_lock_value
|
109
|
+
update_storage(options)
|
56
110
|
end
|
57
111
|
end
|
58
112
|
|
@@ -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
|
|
data/lib/dyna_model/schema.rb
CHANGED
@@ -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 => "
|
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"
|
data/lib/dyna_model/table.rb
CHANGED
@@ -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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
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, "
|
225
|
-
|
226
|
-
|
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
|
-
|
237
|
-
|
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
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|
-
|
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
|
-
|
453
|
-
|
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
|
data/lib/dyna_model/version.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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.
|
124
|
+
version: 1.39.0
|
125
125
|
description: DyanmoDB ORM on AWS::Record
|
126
126
|
email:
|
127
127
|
- cary.dunn@gmail.com
|