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