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

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