aws-record 1.0.0.pre.8 → 1.0.0.pre.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aws-record.rb +11 -11
  3. data/lib/aws-record/record.rb +21 -0
  4. data/lib/aws-record/record/attribute.rb +35 -3
  5. data/lib/aws-record/record/attributes.rb +137 -16
  6. data/lib/aws-record/record/dirty_tracking.rb +48 -8
  7. data/lib/aws-record/record/item_operations.rb +96 -48
  8. data/lib/aws-record/record/marshalers/boolean_marshaler.rb +53 -0
  9. data/lib/aws-record/record/marshalers/date_marshaler.rb +61 -0
  10. data/lib/aws-record/record/marshalers/date_time_marshaler.rb +72 -0
  11. data/lib/aws-record/record/marshalers/float_marshaler.rb +52 -0
  12. data/lib/aws-record/record/marshalers/integer_marshaler.rb +52 -0
  13. data/lib/aws-record/record/marshalers/list_marshaler.rb +56 -0
  14. data/lib/aws-record/record/marshalers/map_marshaler.rb +56 -0
  15. data/lib/aws-record/record/marshalers/numeric_set_marshaler.rb +69 -0
  16. data/lib/aws-record/record/marshalers/string_marshaler.rb +52 -0
  17. data/lib/aws-record/record/marshalers/string_set_marshaler.rb +69 -0
  18. data/lib/aws-record/record/version.rb +1 -1
  19. metadata +12 -13
  20. data/lib/aws-record/record/attributes/boolean_marshaler.rb +0 -54
  21. data/lib/aws-record/record/attributes/date_marshaler.rb +0 -54
  22. data/lib/aws-record/record/attributes/date_time_marshaler.rb +0 -55
  23. data/lib/aws-record/record/attributes/float_marshaler.rb +0 -53
  24. data/lib/aws-record/record/attributes/integer_marshaler.rb +0 -53
  25. data/lib/aws-record/record/attributes/list_marshaler.rb +0 -66
  26. data/lib/aws-record/record/attributes/map_marshaler.rb +0 -66
  27. data/lib/aws-record/record/attributes/numeric_set_marshaler.rb +0 -72
  28. data/lib/aws-record/record/attributes/string_marshaler.rb +0 -60
  29. data/lib/aws-record/record/attributes/string_set_marshaler.rb +0 -70
@@ -21,17 +21,17 @@ module Aws
21
21
  end
22
22
 
23
23
  # Saves this instance of an item to Amazon DynamoDB. If this item is "new"
24
- # as defined by having new or altered key attributes, will attempt a
25
- # conditional
26
- # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#put_item-instance_method Aws::DynamoDB::Client#put_item}
27
- # call, which will not overwrite an existing item. If the item only has
28
- # altered non-key attributes, will perform an
29
- # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#update_item-instance_method Aws::DynamoDB::Client#update_item}
30
- # call. Uses this item instance's attributes in order to build the
31
- # request on your behalf.
24
+ # as defined by having new or altered key attributes, will attempt a
25
+ # conditional
26
+ # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#put_item-instance_method Aws::DynamoDB::Client#put_item}
27
+ # call, which will not overwrite an existing item. If the item only has
28
+ # altered non-key attributes, will perform an
29
+ # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#update_item-instance_method Aws::DynamoDB::Client#update_item}
30
+ # call. Uses this item instance's attributes in order to build the
31
+ # request on your behalf.
32
32
  #
33
33
  # You can use the +:force+ option to perform a simple put/overwrite
34
- # without conditional validation or update logic.
34
+ # without conditional validation or update logic.
35
35
  #
36
36
  # @param [Hash] opts
37
37
  # @option opts [Boolean] :force if true, will save as a put operation and
@@ -54,17 +54,17 @@ module Aws
54
54
  end
55
55
 
56
56
  # Saves this instance of an item to Amazon DynamoDB. If this item is "new"
57
- # as defined by having new or altered key attributes, will attempt a
58
- # conditional
59
- # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#put_item-instance_method Aws::DynamoDB::Client#put_item}
60
- # call, which will not overwrite an existing item. If the item only has
61
- # altered non-key attributes, will perform an
62
- # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#update_item-instance_method Aws::DynamoDB::Client#update_item}
63
- # call. Uses this item instance's attributes in order to build the
64
- # request on your behalf.
57
+ # as defined by having new or altered key attributes, will attempt a
58
+ # conditional
59
+ # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#put_item-instance_method Aws::DynamoDB::Client#put_item}
60
+ # call, which will not overwrite an existing item. If the item only has
61
+ # altered non-key attributes, will perform an
62
+ # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#update_item-instance_method Aws::DynamoDB::Client#update_item}
63
+ # call. Uses this item instance's attributes in order to build the
64
+ # request on your behalf.
65
65
  #
66
66
  # You can use the +:force+ option to perform a simple put/overwrite
67
- # without conditional validation or update logic.
67
+ # without conditional validation or update logic.
68
68
  #
69
69
  # @param [Hash] opts
70
70
  # @option opts [Boolean] :force if true, will save as a put operation and
@@ -112,12 +112,12 @@ module Aws
112
112
  if force
113
113
  dynamodb_client.put_item(
114
114
  table_name: self.class.table_name,
115
- item: build_item_for_save
115
+ item: _build_item_for_save
116
116
  )
117
117
  elsif expect_new
118
118
  put_opts = {
119
119
  table_name: self.class.table_name,
120
- item: build_item_for_save
120
+ item: _build_item_for_save
121
121
  }.merge(prevent_overwrite_expression)
122
122
  begin
123
123
  dynamodb_client.put_item(put_opts)
@@ -129,25 +129,52 @@ module Aws
129
129
  )
130
130
  end
131
131
  else
132
- dynamodb_client.update_item(
133
- table_name: self.class.table_name,
134
- key: key_values,
135
- attribute_updates: dirty_changes_for_update
132
+ update_pairs = _dirty_changes_for_update
133
+ update_tuple = self.class.send(
134
+ :_build_update_expression,
135
+ update_pairs
136
136
  )
137
+ if update_tuple
138
+ uex, exp_attr_names, exp_attr_values = update_tuple
139
+ dynamodb_client.update_item(
140
+ table_name: self.class.table_name,
141
+ key: key_values,
142
+ update_expression: uex,
143
+ expression_attribute_names: exp_attr_names,
144
+ expression_attribute_values: exp_attr_values
145
+ )
146
+ else
147
+ dynamodb_client.update_item(
148
+ table_name: self.class.table_name,
149
+ key: key_values
150
+ )
151
+ end
137
152
  end
138
153
  end
139
154
 
140
- def build_item_for_save
155
+ def _build_item_for_save
141
156
  validate_key_values
142
157
  attributes = self.class.attributes
158
+ _populate_default_values(attributes)
143
159
  @data.inject({}) do |acc, name_value_pair|
144
160
  attr_name, raw_value = name_value_pair
145
- db_name = attributes[attr_name].database_name
146
- acc[db_name] = attributes[attr_name].serialize(raw_value)
161
+ attribute = attributes[attr_name]
162
+ if !raw_value.nil? || attribute.persist_nil?
163
+ db_name = attribute.database_name
164
+ acc[db_name] = attribute.serialize(raw_value)
165
+ end
147
166
  acc
148
167
  end
149
168
  end
150
169
 
170
+ def _populate_default_values(attributes)
171
+ attributes.each do |attr_name, attribute|
172
+ if !attribute.default_value.nil? && @data[attribute.name].nil?
173
+ @data[attr_name] = attribute.default_value
174
+ end
175
+ end
176
+ end
177
+
151
178
  def key_values
152
179
  validate_key_values
153
180
  attributes = self.class.attributes
@@ -198,15 +225,10 @@ module Aws
198
225
  }
199
226
  end
200
227
 
201
- def dirty_changes_for_update
228
+ def _dirty_changes_for_update
202
229
  attributes = self.class.attributes
203
230
  ret = dirty.inject({}) do |acc, attr_name|
204
- key = attributes[attr_name].database_name
205
- value = {
206
- value: attributes[attr_name].serialize(@data[attr_name]),
207
- action: "PUT"
208
- }
209
- acc[key] = value
231
+ acc[attr_name] = @data[attr_name]
210
232
  acc
211
233
  end
212
234
  ret
@@ -289,32 +311,54 @@ module Aws
289
311
  table_name: table_name,
290
312
  key: key
291
313
  }
292
- update_expressions = []
314
+ update_tuple = _build_update_expression(opts)
315
+ unless update_tuple.nil?
316
+ uex, exp_attr_names, exp_attr_values = update_tuple
317
+ request_opts[:update_expression] = uex
318
+ request_opts[:expression_attribute_names] = exp_attr_names
319
+ request_opts[:expression_attribute_values] = exp_attr_values
320
+ end
321
+ dynamodb_client.update_item(request_opts)
322
+ end
323
+
324
+ private
325
+ def _build_update_expression(attr_value_pairs)
326
+ set_expressions = []
327
+ remove_expressions = []
293
328
  exp_attr_names = {}
294
329
  exp_attr_values = {}
295
- name_sub_token = "A"
296
- value_sub_token = "a"
297
- opts.each do |attr_sym, value|
330
+ name_sub_token = "UE_A"
331
+ value_sub_token = "ue_a"
332
+ attr_value_pairs.each do |attr_sym, value|
298
333
  name_sub = "#" + name_sub_token
299
334
  value_sub = ":" + value_sub_token
300
335
  name_sub_token = name_sub_token.succ
301
336
  value_sub_token = value_sub_token.succ
302
337
 
303
- attr_name = attributes[attr_sym].database_name
304
- update_expressions << "#{name_sub} = #{value_sub}"
338
+ attribute = attributes[attr_sym]
339
+ attr_name = attribute.database_name
305
340
  exp_attr_names[name_sub] = attr_name
306
- exp_attr_values[value_sub] = attributes[attr_sym].serialize(value)
341
+ if _update_type_remove?(attribute, value)
342
+ remove_expressions << "#{name_sub}"
343
+ else
344
+ set_expressions << "#{name_sub} = #{value_sub}"
345
+ exp_attr_values[value_sub] = attribute.serialize(value)
346
+ end
307
347
  end
308
- unless update_expressions.empty?
309
- uex = "SET " + update_expressions.join(", ")
310
- request_opts[:update_expression] = uex
311
- request_opts[:expression_attribute_names] = exp_attr_names
312
- request_opts[:expression_attribute_values] = exp_attr_values
348
+ update_expressions = []
349
+ unless set_expressions.empty?
350
+ update_expressions << "SET " + set_expressions.join(", ")
351
+ end
352
+ unless remove_expressions.empty?
353
+ update_expressions << "REMOVE " + remove_expressions.join(", ")
354
+ end
355
+ if update_expressions.empty?
356
+ nil
357
+ else
358
+ [update_expressions.join(" "), exp_attr_names, exp_attr_values]
313
359
  end
314
- dynamodb_client.update_item(request_opts)
315
360
  end
316
361
 
317
- private
318
362
  def build_item_from_resp(resp)
319
363
  record = new
320
364
  data = record.instance_variable_get("@data")
@@ -323,6 +367,10 @@ module Aws
323
367
  end
324
368
  record
325
369
  end
370
+
371
+ def _update_type_remove?(attribute, value)
372
+ value.nil? && !attribute.persist_nil?
373
+ end
326
374
  end
327
375
 
328
376
  end
@@ -0,0 +1,53 @@
1
+ # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not
4
+ # use this file except in compliance with the License. A copy of the License is
5
+ # located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is distributed on
10
+ # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ # or implied. See the License for the specific language governing permissions
12
+ # and limitations under the License.
13
+
14
+ module Aws
15
+ module Record
16
+ module Marshalers
17
+
18
+ class BooleanMarshaler
19
+ def initialize(opts = {})
20
+ end
21
+
22
+ def type_cast(raw_value)
23
+ case raw_value
24
+ when nil
25
+ nil
26
+ when ''
27
+ nil
28
+ when false, 'false', '0', 0
29
+ false
30
+ else
31
+ true
32
+ end
33
+ end
34
+
35
+ def serialize(raw_value)
36
+ boolean = type_cast(raw_value)
37
+ case boolean
38
+ when nil
39
+ nil
40
+ when false
41
+ false
42
+ when true
43
+ true
44
+ else
45
+ msg = "expected a boolean value or nil, got #{boolean.class}"
46
+ raise ArgumentError, msg
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,61 @@
1
+ # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not
4
+ # use this file except in compliance with the License. A copy of the License is
5
+ # located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is distributed on
10
+ # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ # or implied. See the License for the specific language governing permissions
12
+ # and limitations under the License.
13
+
14
+ require 'date'
15
+
16
+ module Aws
17
+ module Record
18
+ module Marshalers
19
+
20
+ class DateMarshaler
21
+ def initialize(opts = {})
22
+ @formatter = opts[:formatter] || Iso8601Formatter
23
+ end
24
+
25
+ def type_cast(raw_value)
26
+ case raw_value
27
+ when nil
28
+ nil
29
+ when ''
30
+ nil
31
+ when Date
32
+ raw_value
33
+ when Integer
34
+ Date.parse(Time.at(raw_value).to_s) # assumed timestamp
35
+ else
36
+ Date.parse(raw_value.to_s) # Time, DateTime or String
37
+ end
38
+ end
39
+
40
+ def serialize(raw_value)
41
+ date = type_cast(raw_value)
42
+ if date.nil?
43
+ nil
44
+ elsif date.is_a?(Date)
45
+ @formatter.format(date)
46
+ else
47
+ raise ArgumentError, "expected a Date value or nil, got #{date.class}"
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ module Iso8601Formatter
54
+ def self.format(date)
55
+ date.iso8601
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,72 @@
1
+ # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not
4
+ # use this file except in compliance with the License. A copy of the License is
5
+ # located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is distributed on
10
+ # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ # or implied. See the License for the specific language governing permissions
12
+ # and limitations under the License.
13
+
14
+ require 'date'
15
+
16
+ module Aws
17
+ module Record
18
+ module Marshalers
19
+
20
+ class DateTimeMarshaler
21
+ def initialize(opts = {})
22
+ @formatter = opts[:formatter] || Iso8601Formatter
23
+ @use_local_time = opts[:use_local_time] ? true : false
24
+ end
25
+
26
+ def type_cast(raw_value)
27
+ value = _format(raw_value)
28
+ if !@use_local_time && value.is_a?(::DateTime)
29
+ value.new_offset(0)
30
+ else
31
+ value
32
+ end
33
+ end
34
+
35
+ def serialize(raw_value)
36
+ datetime = type_cast(raw_value)
37
+ if datetime.nil?
38
+ nil
39
+ elsif datetime.is_a?(::DateTime)
40
+ @formatter.format(datetime)
41
+ else
42
+ msg = "expected a DateTime value or nil, got #{datetime.class}"
43
+ raise ArgumentError, msg
44
+ end
45
+ end
46
+
47
+ private
48
+ def _format(raw_value)
49
+ case raw_value
50
+ when nil
51
+ nil
52
+ when ''
53
+ nil
54
+ when ::DateTime
55
+ raw_value
56
+ when Integer # timestamp
57
+ ::DateTime.parse(Time.at(raw_value).to_s)
58
+ else # Time, Date or String
59
+ ::DateTime.parse(raw_value.to_s)
60
+ end
61
+ end
62
+ end
63
+
64
+ module Iso8601Formatter
65
+ def self.format(datetime)
66
+ datetime.iso8601
67
+ end
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,52 @@
1
+ # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not
4
+ # use this file except in compliance with the License. A copy of the License is
5
+ # located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is distributed on
10
+ # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ # or implied. See the License for the specific language governing permissions
12
+ # and limitations under the License.
13
+
14
+ module Aws
15
+ module Record
16
+ module Marshalers
17
+
18
+ class FloatMarshaler
19
+ def initialize(opts = {})
20
+ end
21
+
22
+ def type_cast(raw_value)
23
+ case raw_value
24
+ when nil
25
+ nil
26
+ when ''
27
+ nil
28
+ when Float
29
+ raw_value
30
+ else
31
+ raw_value.respond_to?(:to_f) ?
32
+ raw_value.to_f :
33
+ raw_value.to_s.to_f
34
+ end
35
+ end
36
+
37
+ def serialize(raw_value)
38
+ float = type_cast(raw_value)
39
+ if float.nil?
40
+ nil
41
+ elsif float.is_a?(Float)
42
+ float
43
+ else
44
+ msg = "expected a Float value or nil, got #{float.class}"
45
+ raise ArgumentError, msg
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end