aws-record 1.0.0.pre.4 → 1.0.0.pre.5

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: 102f5cb13fee3789030badc882c2d171ddb800e8
4
- data.tar.gz: e4bad779188f59b940492fb2644636901826f41e
3
+ metadata.gz: e3fb756f0dd0d63461d393e36ec6059482648f79
4
+ data.tar.gz: 8f3ea1c9ea27cf57f7a2aa26d0096d62e8c6f4cf
5
5
  SHA512:
6
- metadata.gz: a3ef6f71866145568e25d6cac31ebd31c33ad37bfa776ae648e4a0424f69e2e7c6fa46b8d22e5b0c5d5231f1fbde98d1460e5f83e88a812ea85dd4d87e3d3b68
7
- data.tar.gz: 9cb2cf7a7644a0a6754c7e32452dbf5b83e422e6d7e94ca8a5c0442aa1532ac81ed47519bb00175a1a5e8107f193c1612a0a2ebd3df66a046763daa6c51378a5
6
+ metadata.gz: bda87438c533121bb4cc80f2e7dca54f2a0757f613d4993790c1519733e99c6c3d3df2c54e72c14c24173d8c424dc6368be0ede2b27d86b417847be3152c8fd8
7
+ data.tar.gz: 2b9a63d40bb40daa48dee6ec115950fe8c7ea85a0c43b80ed7dc64d12936eb7e5b5504343ffbea7b8a26cb446bc37e5241b497452a9a3022902e6817be8cb2e4
@@ -13,6 +13,17 @@
13
13
 
14
14
  module Aws
15
15
  module Record
16
+ # @!parse extend RecordClassMethods
17
+ # @!parse include Attributes
18
+ # @!parse extend Attributes::ClassMethods
19
+ # @!parse include ItemOperations
20
+ # @!parse extend ItemOperations::ItemOperationsClassMethods
21
+ # @!parse include Query
22
+ # @!parse extend Query::QueryClassMethods
23
+ # @!parse include SecondaryIndexes
24
+ # @!parse extend SecondaryIndexes::SecondaryIndexesClassMethods
25
+ # @!parse include DirtyTracking
26
+ # @!parse extend DirtyTracking::DirtyTrackingClassMethods
16
27
 
17
28
  # Usage of {Aws::Record} requires only that you include this module. This
18
29
  # method will then pull in the other default modules.
@@ -60,7 +71,7 @@ module Aws
60
71
  if @table_name
61
72
  @table_name
62
73
  else
63
- @table_name = self.name
74
+ @table_name = self.name.split("::").join("_")
64
75
  end
65
76
  end
66
77
 
@@ -21,8 +21,8 @@ module Aws
21
21
  # safe to use as a method.
22
22
  # @param [Hash] options
23
23
  # @option options [Marshaler] :marshaler The marshaler for this attribute.
24
- # So long as you provide a marshaler which implements `#type_cast` and
25
- # `#serialize` that consume raw values as expected, you can bring your
24
+ # So long as you provide a marshaler which implements +#type_cast+ and
25
+ # +#serialize+ that consume raw values as expected, you can bring your
26
26
  # own marshaler type.
27
27
  # @option options [Array] :validators An array of validator classes that
28
28
  # will be run when an attribute is checked for validity.
@@ -54,8 +54,8 @@ module Aws
54
54
  # @param [Symbol] name Name of this attribute. It should be a name that
55
55
  # is safe to use as a method.
56
56
  # @param [Marshaler] marshaler The marshaler for this attribute. So long
57
- # as you provide a marshaler which implements `#type_cast` and
58
- # `#serialize` that consume raw values as expected, you can bring your
57
+ # as you provide a marshaler which implements +#type_cast+ and
58
+ # +#serialize+ that consume raw values as expected, you can bring your
59
59
  # own marshaler type. Convenience methods will provide this for you.
60
60
  # @param [Hash] opts
61
61
  # @option opts [Array] :validators An array of validator classes that
@@ -269,7 +269,7 @@ module Aws
269
269
 
270
270
  # Define a numeric set attribute for your model.
271
271
  #
272
- # Numeric sets are homogeneous sets, containing only strings. Note that
272
+ # Numeric sets are homogeneous sets, containing only numbers. Note that
273
273
  # empty sets cannot be persisted to DynamoDB. Empty sets are valid for
274
274
  # aws-record items, but they will not be persisted as sets. nil values
275
275
  # from your table, or a lack of value from your table, will be treated
@@ -284,7 +284,7 @@ module Aws
284
284
  # the hash key for the table.
285
285
  # @option opts [Boolean] :range_key Set to true if this attribute is
286
286
  # the range key for the table.
287
- def string_set_attr(name, opts = {})
287
+ def numeric_set_attr(name, opts = {})
288
288
  opts[:dynamodb_type] = "NS"
289
289
  attr(name, Attributes::NumericSetMarshaler, opts)
290
290
  end
@@ -15,10 +15,14 @@ module Aws
15
15
  module Record
16
16
  module Errors
17
17
 
18
- class KeyMissing < RuntimeError; end
18
+ class RecordError < RuntimeError; end
19
+
20
+ class KeyMissing < RecordError; end
21
+ class NotFound < RecordError; end
22
+ class ItemAlreadyExists < RecordError; end
23
+
19
24
  class NameCollision < RuntimeError; end
20
25
  class ReservedName < RuntimeError; end
21
- class NotFound < RuntimeError; end
22
26
  class InvalidModel < RuntimeError; end
23
27
  class TableDoesNotExist < RuntimeError; end
24
28
 
@@ -12,17 +12,18 @@ module Aws
12
12
 
13
13
  def each(&block)
14
14
  return enum_for(:each) unless block_given?
15
- unless @result
16
- @result = @client.send(@search_method, @search_params)
17
- end
18
- @result.each_page do |page|
19
- items = _build_items_from_response(page.items, @model)
20
- items.each do |item|
15
+ items.each_page do |page|
16
+ items_array = _build_items_from_response(page.items, @model)
17
+ items_array.each do |item|
21
18
  yield item
22
19
  end
23
20
  end
24
21
  end
25
22
 
23
+ def empty?
24
+ items.empty?
25
+ end
26
+
26
27
  private
27
28
  def _build_items_from_response(items, model)
28
29
  ret = []
@@ -37,6 +38,10 @@ module Aws
37
38
  ret
38
39
  end
39
40
 
41
+ def items
42
+ @_items ||= @client.send(@search_method, @search_params)
43
+ end
44
+
40
45
  end
41
46
  end
42
47
  end
@@ -18,20 +18,96 @@ module Aws
18
18
  # @api private
19
19
  def self.included(sub_class)
20
20
  sub_class.extend(ItemOperationsClassMethods)
21
+ sub_class.instance_variable_set("@errors", [])
21
22
  end
22
23
 
23
- # Saves this instance of an item to Amazon DynamoDB using the
24
- # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#put_item-instance_method Aws::DynamoDB::Client#put_item}
25
- # API. Uses this item instance's attributes in order to build the request
26
- # on your behalf.
24
+ # Saves this instance of an item to Amazon DynamoDB. If this item is "new"
25
+ # as defined by having new or altered key attributes, will attempt a
26
+ # conditional
27
+ # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#put_item-instance_method Aws::DynamoDB::Client#put_item}
28
+ # call, which will not overwrite an existing item. If the item only has
29
+ # altered non-key attributes, will perform an
30
+ # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#update_item-instance_method Aws::DynamoDB::Client#update_item}
31
+ # call. Uses this item instance's attributes in order to build the
32
+ # request on your behalf.
27
33
  #
34
+ # You can use the +:force+ option to perform a simple put/overwrite
35
+ # without conditional validation or update logic.
36
+ #
37
+ # @param [Hash] opts
38
+ # @option opts [Boolean] :force if true, will save as a put operation and
39
+ # overwrite any existing item on the remote end. Otherwise, and by
40
+ # default, will either perform a conditional put or an update call.
28
41
  # @raise [Aws::Record::Errors::KeyMissing] if a required key attribute
29
42
  # does not have a value within this item instance.
30
- def save
31
- dynamodb_client.put_item(
32
- table_name: self.class.table_name,
33
- item: build_item_for_save
34
- )
43
+ # @raise [Aws::Record::Errors::ItemAlreadyExists] if a conditional put
44
+ # fails because the item exists on the remote end.
45
+ def save!(opts = {})
46
+ force = opts[:force]
47
+ expect_new = expect_new_item?
48
+ if force
49
+ dynamodb_client.put_item(
50
+ table_name: self.class.table_name,
51
+ item: build_item_for_save
52
+ )
53
+ elsif expect_new
54
+ put_opts = {
55
+ table_name: self.class.table_name,
56
+ item: build_item_for_save
57
+ }.merge(prevent_overwrite_expression)
58
+ begin
59
+ dynamodb_client.put_item(put_opts)
60
+ rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
61
+ raise Errors::ItemAlreadyExists.new(
62
+ "Conditional #put_item call failed, an item with the same key"\
63
+ " already exists in the table. Either load and update this"\
64
+ " item, or include the :force option to clobber the remote"\
65
+ " item."
66
+ )
67
+ end
68
+ else
69
+ dynamodb_client.update_item(
70
+ table_name: self.class.table_name,
71
+ key: key_values,
72
+ attribute_updates: dirty_changes_for_update
73
+ )
74
+ end
75
+ end
76
+
77
+ # Saves this instance of an item to Amazon DynamoDB. If this item is "new"
78
+ # as defined by having new or altered key attributes, will attempt a
79
+ # conditional
80
+ # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#put_item-instance_method Aws::DynamoDB::Client#put_item}
81
+ # call, which will not overwrite an existing item. If the item only has
82
+ # altered non-key attributes, will perform an
83
+ # {http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#update_item-instance_method Aws::DynamoDB::Client#update_item}
84
+ # call. Uses this item instance's attributes in order to build the
85
+ # request on your behalf.
86
+ #
87
+ # You can use the +:force+ option to perform a simple put/overwrite
88
+ # without conditional validation or update logic.
89
+ #
90
+ # In the case where persistence fails, will populate the +errors+ array
91
+ # with any generated error messages, and will cause +#valid?+ to return
92
+ # false until there is a successful save.
93
+ #
94
+ # @param [Hash] opts
95
+ # @option opts [Boolean] :force if true, will save as a put operation and
96
+ # overwrite any existing item on the remote end. Otherwise, and by
97
+ # default, will either perform a conditional put or an update call.
98
+ def save(opts = {})
99
+ result = save!(opts)
100
+ errors.clear
101
+ result
102
+ rescue Errors::RecordError => e
103
+ errors << e.message
104
+ false
105
+ end
106
+
107
+ # Checks if the record is a valid record. +false+ if most recent +#save+
108
+ # call raised errors, or if there are missing keys. +true+ otherwise.
109
+ def valid?
110
+ errors.empty? && missing_key_values.empty?
35
111
  end
36
112
 
37
113
  # Deletes the item instance that matches the key values of this item
@@ -46,6 +122,10 @@ module Aws
46
122
  true
47
123
  end
48
124
 
125
+ def errors
126
+ self.class.instance_variable_get("@errors")
127
+ end
128
+
49
129
  private
50
130
  def build_item_for_save
51
131
  validate_key_values
@@ -84,6 +164,44 @@ module Aws
84
164
  end
85
165
  end
86
166
 
167
+ def expect_new_item?
168
+ # Algorithm: Are keys dirty? If so, we treat as new.
169
+ self.class.keys.any? do |_, attr_name|
170
+ attribute_dirty?(attr_name)
171
+ end
172
+ end
173
+
174
+ def prevent_overwrite_expression
175
+ conditions = []
176
+ expression_attribute_names = {}
177
+ # Hash Key
178
+ conditions << "attribute_not_exists(#H)"
179
+ expression_attribute_names["#H"] = self.class.hash_key.database_name
180
+ # Range Key
181
+ if self.class.range_key
182
+ conditions << "attribute_not_exists(#R)"
183
+ expression_attribute_names["#R"] = self.class.range_key.database_name
184
+ end
185
+ {
186
+ condition_expression: conditions.join(" and "),
187
+ expression_attribute_names: expression_attribute_names
188
+ }
189
+ end
190
+
191
+ def dirty_changes_for_update
192
+ attributes = self.class.attributes
193
+ ret = dirty.inject({}) do |acc, attr_name|
194
+ key = attributes[attr_name].database_name
195
+ value = {
196
+ value: attributes[attr_name].serialize(@data[attr_name]),
197
+ action: "PUT"
198
+ }
199
+ acc[key] = value
200
+ acc
201
+ end
202
+ ret
203
+ end
204
+
87
205
  module ItemOperationsClassMethods
88
206
 
89
207
  # @example Usage Example
@@ -92,7 +210,7 @@ module Aws
92
210
  # integer_attr :id, hash_key: true
93
211
  # string_attr :name, range_key: true
94
212
  # end
95
- #
213
+ #
96
214
  # MyModel.find(id: 1, name: "First")
97
215
  #
98
216
  # @param [Hash] opts attribute-value pairs for the key you wish to
@@ -13,6 +13,6 @@
13
13
 
14
14
  module Aws
15
15
  module Record
16
- VERSION = "1.0.0.pre.4"
16
+ VERSION = '1.0.0.pre.5'
17
17
  end
18
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-record
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.4
4
+ version: 1.0.0.pre.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amazon Web Services
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-11 00:00:00.000000000 Z
11
+ date: 2016-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-resources
@@ -73,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
73
  version: 1.3.1
74
74
  requirements: []
75
75
  rubyforge_project:
76
- rubygems_version: 2.4.8
76
+ rubygems_version: 2.5.1
77
77
  signing_key:
78
78
  specification_version: 4
79
79
  summary: AWS Record library for Amazon DynamoDB