dynamoid 3.2.0 → 3.6.0

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +111 -1
  3. data/README.md +580 -241
  4. data/lib/dynamoid.rb +2 -0
  5. data/lib/dynamoid/adapter.rb +15 -15
  6. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +82 -102
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +108 -0
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +29 -16
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +3 -2
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/backoff.rb +2 -2
  11. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +2 -3
  12. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/start_key.rb +2 -2
  13. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +15 -6
  14. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +15 -5
  15. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/table.rb +1 -0
  16. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/until_past_table_status.rb +5 -3
  17. data/lib/dynamoid/application_time_zone.rb +1 -0
  18. data/lib/dynamoid/associations.rb +182 -19
  19. data/lib/dynamoid/associations/association.rb +4 -2
  20. data/lib/dynamoid/associations/belongs_to.rb +2 -1
  21. data/lib/dynamoid/associations/has_and_belongs_to_many.rb +2 -1
  22. data/lib/dynamoid/associations/has_many.rb +2 -1
  23. data/lib/dynamoid/associations/has_one.rb +2 -1
  24. data/lib/dynamoid/associations/many_association.rb +65 -22
  25. data/lib/dynamoid/associations/single_association.rb +28 -1
  26. data/lib/dynamoid/components.rb +8 -3
  27. data/lib/dynamoid/config.rb +16 -3
  28. data/lib/dynamoid/config/backoff_strategies/constant_backoff.rb +1 -0
  29. data/lib/dynamoid/config/backoff_strategies/exponential_backoff.rb +1 -0
  30. data/lib/dynamoid/config/options.rb +1 -0
  31. data/lib/dynamoid/criteria.rb +2 -1
  32. data/lib/dynamoid/criteria/chain.rb +418 -46
  33. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +3 -3
  34. data/lib/dynamoid/criteria/key_fields_detector.rb +109 -32
  35. data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +3 -2
  36. data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +1 -1
  37. data/lib/dynamoid/dirty.rb +239 -32
  38. data/lib/dynamoid/document.rb +130 -251
  39. data/lib/dynamoid/dumping.rb +9 -0
  40. data/lib/dynamoid/dynamodb_time_zone.rb +1 -0
  41. data/lib/dynamoid/fields.rb +246 -20
  42. data/lib/dynamoid/finders.rb +69 -32
  43. data/lib/dynamoid/identity_map.rb +6 -0
  44. data/lib/dynamoid/indexes.rb +76 -17
  45. data/lib/dynamoid/loadable.rb +31 -0
  46. data/lib/dynamoid/log/formatter.rb +26 -0
  47. data/lib/dynamoid/middleware/identity_map.rb +1 -0
  48. data/lib/dynamoid/persistence.rb +592 -122
  49. data/lib/dynamoid/persistence/import.rb +73 -0
  50. data/lib/dynamoid/persistence/save.rb +64 -0
  51. data/lib/dynamoid/persistence/update_fields.rb +63 -0
  52. data/lib/dynamoid/persistence/upsert.rb +60 -0
  53. data/lib/dynamoid/primary_key_type_mapping.rb +1 -0
  54. data/lib/dynamoid/railtie.rb +1 -0
  55. data/lib/dynamoid/tasks.rb +3 -1
  56. data/lib/dynamoid/tasks/database.rb +1 -0
  57. data/lib/dynamoid/type_casting.rb +12 -2
  58. data/lib/dynamoid/undumping.rb +8 -0
  59. data/lib/dynamoid/validations.rb +2 -0
  60. data/lib/dynamoid/version.rb +1 -1
  61. metadata +49 -71
  62. data/.coveralls.yml +0 -1
  63. data/.document +0 -5
  64. data/.gitignore +0 -74
  65. data/.rspec +0 -2
  66. data/.rubocop.yml +0 -71
  67. data/.rubocop_todo.yml +0 -55
  68. data/.travis.yml +0 -41
  69. data/Appraisals +0 -28
  70. data/Gemfile +0 -8
  71. data/Rakefile +0 -46
  72. data/Vagrantfile +0 -29
  73. data/docker-compose.yml +0 -7
  74. data/dynamoid.gemspec +0 -57
  75. data/gemfiles/rails_4_2.gemfile +0 -11
  76. data/gemfiles/rails_5_0.gemfile +0 -10
  77. data/gemfiles/rails_5_1.gemfile +0 -10
  78. data/gemfiles/rails_5_2.gemfile +0 -10
@@ -13,6 +13,7 @@ module Dynamoid
13
13
  @identity_map ||= {}
14
14
  end
15
15
 
16
+ # @private
16
17
  def from_database(attrs = {})
17
18
  return super if identity_map_off?
18
19
 
@@ -29,6 +30,7 @@ module Dynamoid
29
30
  document
30
31
  end
31
32
 
33
+ # @private
32
34
  def find_by_id(id, options = {})
33
35
  return super if identity_map_off?
34
36
 
@@ -41,6 +43,7 @@ module Dynamoid
41
43
  identity_map[key] || super
42
44
  end
43
45
 
46
+ # @private
44
47
  def identity_map_key(attrs)
45
48
  key = attrs[hash_key].to_s
46
49
  key += "::#{attrs[range_key]}" if range_key
@@ -60,6 +63,7 @@ module Dynamoid
60
63
  self.class.identity_map
61
64
  end
62
65
 
66
+ # @private
63
67
  def save(*args)
64
68
  return super if self.class.identity_map_off?
65
69
 
@@ -69,6 +73,7 @@ module Dynamoid
69
73
  result
70
74
  end
71
75
 
76
+ # @private
72
77
  def delete
73
78
  return super if self.class.identity_map_off?
74
79
 
@@ -76,6 +81,7 @@ module Dynamoid
76
81
  super
77
82
  end
78
83
 
84
+ # @private
79
85
  def identity_map_key
80
86
  key = hash_key.to_s
81
87
  key += "::#{range_value}" if self.class.range_key
@@ -15,19 +15,43 @@ module Dynamoid
15
15
  # Defines a Global Secondary index on a table. Keys can be specified as
16
16
  # hash-only, or hash & range.
17
17
  #
18
- # @param [Hash] options options to pass for this table
19
- # @option options [Symbol] :name the name for the index; this still gets
20
- # namespaced. If not specified, will use a default name.
21
- # @option options [Symbol] :hash_key the index hash key column.
22
- # @option options [Symbol] :range_key the index range key column (if
18
+ # class Post
19
+ # include Dynamoid::Document
20
+ #
21
+ # field :category
22
+ #
23
+ # global_secondary_indexes hash_key: :category
24
+ # end
25
+ #
26
+ # The full example with all the options being specified:
27
+ #
28
+ # global_secondary_indexes hash_key: :category,
29
+ # range_key: :created_at,
30
+ # name: 'posts_category_created_at_index',
31
+ # projected_attributes: :all,
32
+ # read_capacity: 100,
33
+ # write_capacity: 20
34
+ #
35
+ # Global secondary index should be declared after fields for mentioned
36
+ # hash key and optional range key are declared (with method +field+)
37
+ #
38
+ # The only mandatory option is +hash_key+. Raises
39
+ # +Dynamoid::Errors::InvalidIndex+ exception if passed incorrect
40
+ # options.
41
+ #
42
+ # @param [Hash] options the options to pass for this table
43
+ # @option options [Symbol] name the name for the index; this still gets
44
+ # namespaced. If not specified, will use a default name.
45
+ # @option options [Symbol] hash_key the index hash key column.
46
+ # @option options [Symbol] range_key the index range key column (if
23
47
  # applicable).
24
- # @option options [Symbol, Array<Symbol>] :projected_attributes table
25
- # attributes to project for this index. Can be :keys_only, :all
48
+ # @option options [Symbol, Array<Symbol>] projected_attributes table
49
+ # attributes to project for this index. Can be +:keys_only+, +:all+
26
50
  # or an array of included fields. If not specified, defaults to
27
- # :keys_only.
28
- # @option options [Integer] :read_capacity set the read capacity for the
51
+ # +:keys_only+.
52
+ # @option options [Integer] read_capacity set the read capacity for the
29
53
  # index; does not work on existing indexes.
30
- # @option options [Integer] :write_capacity set the write capacity for
54
+ # @option options [Integer] write_capacity set the write capacity for
31
55
  # the index; does not work on existing indexes.
32
56
  def global_secondary_index(options = {})
33
57
  unless options.present?
@@ -55,14 +79,38 @@ module Dynamoid
55
79
  # Defines a local secondary index on a table. Will use the same primary
56
80
  # hash key as the table.
57
81
  #
82
+ # class Comment
83
+ # include Dynamoid::Document
84
+ #
85
+ # table hash_key: :post_id
86
+ # range :created_at, :datetime
87
+ # field :author_id
88
+ #
89
+ # local_secondary_indexes hash_key: :author_id
90
+ # end
91
+ #
92
+ # The full example with all the options being specified:
93
+ #
94
+ # local_secondary_indexes range_key: :created_at,
95
+ # name: 'posts_created_at_index',
96
+ # projected_attributes: :all
97
+ #
98
+ # Local secondary index should be declared after fields for mentioned
99
+ # hash key and optional range key are declared (with method +field+) as
100
+ # well as after +table+ method call.
101
+ #
102
+ # The only mandatory option is +range_key+. Raises
103
+ # +Dynamoid::Errors::InvalidIndex+ exception if passed incorrect
104
+ # options.
105
+ #
58
106
  # @param [Hash] options options to pass for this index.
59
- # @option options [Symbol] :name the name for the index; this still gets
107
+ # @option options [Symbol] name the name for the index; this still gets
60
108
  # namespaced. If not specified, a name is automatically generated.
61
- # @option options [Symbol] :range_key the range key column for the index.
62
- # @option options [Symbol, Array<Symbol>] :projected_attributes table
63
- # attributes to project for this index. Can be :keys_only, :all
109
+ # @option options [Symbol] range_key the range key column for the index.
110
+ # @option options [Symbol, Array<Symbol>] projected_attributes table
111
+ # attributes to project for this index. Can be +:keys_only+, +:all+
64
112
  # or an array of included fields. If not specified, defaults to
65
- # :keys_only.
113
+ # +:keys_only+.
66
114
  def local_secondary_index(options = {})
67
115
  unless options.present?
68
116
  raise Dynamoid::Errors::InvalidIndex, 'empty index definition'
@@ -94,6 +142,13 @@ module Dynamoid
94
142
  self
95
143
  end
96
144
 
145
+ # Returns an index by its hash key and optional range key.
146
+ #
147
+ # It works only for indexes without explicit name declared.
148
+ #
149
+ # @param hash [scalar] the hash key used to declare an index
150
+ # @param range [scalar] the range key used to declare an index (optional)
151
+ # @return [Dynamoid::Indexes::Index, nil] index object or nil if it isn't found
97
152
  def find_index(hash, range = nil)
98
153
  index = indexes[index_key(hash, range)]
99
154
  index
@@ -150,6 +205,10 @@ module Dynamoid
150
205
  local_secondary_indexes.merge(global_secondary_indexes)
151
206
  end
152
207
 
208
+ # Returns an array of hash keys for all the declared Glocal Secondary
209
+ # Indexes.
210
+ #
211
+ # @return [Array[String]] array of hash keys
153
212
  def indexed_hash_keys
154
213
  global_secondary_indexes.map do |_name, index|
155
214
  index.hash_key.to_s
@@ -165,8 +224,8 @@ module Dynamoid
165
224
  DEFAULT_PROJECTION_TYPE = :keys_only
166
225
 
167
226
  attr_accessor :name, :dynamoid_class, :type, :hash_key, :range_key,
168
- :hash_key_schema, :range_key_schema, :projected_attributes,
169
- :read_capacity, :write_capacity
227
+ :hash_key_schema, :range_key_schema, :projected_attributes,
228
+ :read_capacity, :write_capacity
170
229
 
171
230
  validate do
172
231
  validate_index_type
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dynamoid
4
+ module Loadable
5
+ extend ActiveSupport::Concern
6
+
7
+ def load(attrs)
8
+ attrs.each do |key, value|
9
+ send("#{key}=", value) if respond_to?("#{key}=")
10
+ end
11
+ end
12
+
13
+ # Reload an object from the database -- if you suspect the object has changed in the datastore and you need those
14
+ # changes to be reflected immediately, you would call this method. This is a consistent read.
15
+ #
16
+ # @return [Dynamoid::Document] the document this method was called on
17
+ #
18
+ # @since 0.2.0
19
+ def reload
20
+ options = { consistent_read: true }
21
+
22
+ if self.class.range_key
23
+ options[:range_key] = range_value
24
+ end
25
+
26
+ self.attributes = self.class.find(hash_key, options).attributes
27
+ @associations.values.each(&:reset)
28
+ self
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ module Dynamoid
2
+ module Log
3
+ module Formatter
4
+
5
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Log/Formatter.html
6
+ # https://docs.aws.amazon.com/sdk-for-ruby/v2/api/Seahorse/Client/Response.html
7
+ # https://aws.amazon.com/ru/blogs/developer/logging-requests/
8
+ class Debug
9
+ def format(response)
10
+ bold = "\x1b[1m"
11
+ color = "\x1b[34m"
12
+ reset = "\x1b[0m"
13
+
14
+ [
15
+ response.context.operation.name,
16
+ "#{bold}#{color}\nRequest:\n#{reset}#{bold}",
17
+ JSON.pretty_generate(JSON.parse(response.context.http_request.body.string)),
18
+ "#{bold}#{color}\nResponse:\n#{reset}#{bold}",
19
+ JSON.pretty_generate(JSON.parse(response.context.http_response.body.string)),
20
+ reset
21
+ ].join("\n")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
+ # @private
4
5
  module Middleware
5
6
  class IdentityMap
6
7
  def initialize(app)
@@ -4,16 +4,22 @@ require 'bigdecimal'
4
4
  require 'securerandom'
5
5
  require 'yaml'
6
6
 
7
+ require 'dynamoid/persistence/import'
8
+ require 'dynamoid/persistence/update_fields'
9
+ require 'dynamoid/persistence/upsert'
10
+ require 'dynamoid/persistence/save'
11
+
7
12
  # encoding: utf-8
8
13
  module Dynamoid
9
- # Persistence is responsible for dumping objects to and marshalling objects from the datastore. It tries to reserialize
10
- # values to be of the same type as when they were passed in, based on the fields in the class.
14
+ # # Persistence is responsible for dumping objects to and marshalling objects from the datastore. It tries to reserialize
15
+ # # values to be of the same type as when they were passed in, based on the fields in the class.
11
16
  module Persistence
12
17
  extend ActiveSupport::Concern
13
18
 
14
19
  attr_accessor :new_record
15
20
  alias new_record? new_record
16
21
 
22
+ # @private
17
23
  UNIX_EPOCH_DATE = Date.new(1970, 1, 1).freeze
18
24
 
19
25
  module ClassMethods
@@ -23,15 +29,66 @@ module Dynamoid
23
29
  @table_name ||= [Dynamoid::Config.namespace.to_s, table_base_name].reject(&:empty?).join('_')
24
30
  end
25
31
 
26
- # Creates a table.
32
+ # Create a table.
33
+ #
34
+ # Uses a configuration specified in a model class (with the +table+
35
+ # method) e.g. table name, schema (hash and range keys), global and local
36
+ # secondary indexes, billing mode and write/read capacity.
37
+ #
38
+ # For instance here
39
+ #
40
+ # class User
41
+ # include Dynamoid::Document
42
+ #
43
+ # table key: :uuid
44
+ # range :last_name
45
+ #
46
+ # field :first_name
47
+ # field :last_name
48
+ # end
49
+ #
50
+ # User.create_table
51
+ #
52
+ # +create_table+ method call will create a table +dynamoid_users+ with
53
+ # hash key +uuid+ and range key +name+, DynamoDB default billing mode and
54
+ # Dynamoid default read/write capacity units (100/20).
55
+ #
56
+ # All the configuration can be overridden with +options+ argument.
57
+ #
58
+ # User.create_table(table_name: 'users', read_capacity: 200, write_capacity: 40)
59
+ #
60
+ # Dynamoid creates a table synchronously by default. DynamoDB table
61
+ # creation is an asynchronous operation and a client should wait until a
62
+ # table status changes to +ACTIVE+ and a table becomes available. That's
63
+ # why Dynamoid is polling a table status and returns results only when a
64
+ # table becomes available.
65
+ #
66
+ # Polling is configured with +Dynamoid::Config.sync_retry_max_times+ and
67
+ # +Dynamoid::Config.sync_retry_wait_seconds+ configuration options. If
68
+ # table creation takes more time than configured waiting time then
69
+ # Dynamoid stops polling and returns +true+.
27
70
  #
28
- # @param [Hash] options options to pass for table creation
29
- # @option options [Symbol] :id the id field for the table
30
- # @option options [Symbol] :table_name the actual name for the table
31
- # @option options [Integer] :read_capacity set the read capacity for the table; does not work on existing tables
32
- # @option options [Integer] :write_capacity set the write capacity for the table; does not work on existing tables
33
- # @option options [Hash] {range_key => :type} a hash of the name of the range key and a symbol of its type
34
- # @option options [Symbol] :hash_key_type the dynamo type of the hash key (:string or :number)
71
+ # In order to return back asynchronous behaviour and not to wait until a
72
+ # table is created the +sync: false+ option should be specified.
73
+ #
74
+ # User.create_table(sync: false)
75
+ #
76
+ # Subsequent method calls for the same table will be ignored.
77
+ #
78
+ # @param options [Hash]
79
+ #
80
+ # @option options [Symbol] :table_name name of the table
81
+ # @option options [Symbol] :id hash key name of the table
82
+ # @option options [Symbol] :hash_key_type Dynamoid type of the hash key - +:string+, +:integer+ or any other scalar type
83
+ # @option options [Hash] :range_key a Hash with range key name and type in format +{ <name> => <type> }+ e.g. +{ last_name: :string }+
84
+ # @option options [String] :billing_mode billing mode of a table - either +PROVISIONED+ (default) or +PAY_PER_REQUEST+ (for On-Demand Mode)
85
+ # @option options [Integer] :read_capacity read capacity units for the table; does not work on existing tables and is ignored when billing mode is +PAY_PER_REQUEST+
86
+ # @option options [Integer] :write_capacity write capacity units for the table; does not work on existing tables and is ignored when billing mode is +PAY_PER_REQUEST+
87
+ # @option options [Hash] :local_secondary_indexes
88
+ # @option options [Hash] :global_secondary_indexes
89
+ # @option options [true|false] :sync specifies should the method call be synchronous and wait until a table is completely created
90
+ #
91
+ # @return [true|false] Whether a table created successfully
35
92
  # @since 0.4.0
36
93
  def create_table(options = {})
37
94
  range_key_hash = if range_key
@@ -41,6 +98,7 @@ module Dynamoid
41
98
  options = {
42
99
  id: hash_key,
43
100
  table_name: table_name,
101
+ billing_mode: capacity_mode,
44
102
  write_capacity: write_capacity,
45
103
  read_capacity: read_capacity,
46
104
  range_key: range_key_hash,
@@ -49,77 +107,297 @@ module Dynamoid
49
107
  global_secondary_indexes: global_secondary_indexes.values
50
108
  }.merge(options)
51
109
 
52
- Dynamoid.adapter.create_table(options[:table_name], options[:id], options)
110
+ created_successfuly = Dynamoid.adapter.create_table(options[:table_name], options[:id], options)
111
+
112
+ if created_successfuly && self.options[:expires]
113
+ attribute = self.options[:expires][:field]
114
+ Dynamoid.adapter.update_time_to_live(table_name: table_name, attribute: attribute)
115
+ end
53
116
  end
54
117
 
55
- # Deletes the table for the model
118
+ # Deletes the table for the model.
119
+ #
120
+ # Dynamoid deletes a table asynchronously and doesn't wait until a table
121
+ # is deleted completely.
122
+ #
123
+ # Subsequent method calls for the same table will be ignored.
56
124
  def delete_table
57
125
  Dynamoid.adapter.delete_table(table_name)
58
126
  end
59
127
 
128
+ # @private
60
129
  def from_database(attrs = {})
61
- clazz = choose_right_class(attrs)
62
- attrs_undumped = Undumping.undump_attributes(attrs, clazz.attributes)
63
- clazz.new(attrs_undumped).tap { |r| r.new_record = false }
130
+ klass = choose_right_class(attrs)
131
+ attrs_undumped = Undumping.undump_attributes(attrs, klass.attributes)
132
+ klass.new(attrs_undumped).tap { |r| r.new_record = false }
64
133
  end
65
134
 
66
- # Creates several models at once.
67
- # Neither callbacks nor validations run.
68
- # It works efficiently because of using BatchWriteItem.
135
+ # Create several models at once.
69
136
  #
70
- # Returns array of models
137
+ # users = User.import([{ name: 'a' }, { name: 'b' }])
71
138
  #
72
- # Uses backoff specified by `Dynamoid::Config.backoff` config option
139
+ # +import+ is a relatively low-level method and bypasses some
140
+ # mechanisms like callbacks and validation.
73
141
  #
74
- # @param [Array<Hash>] items
142
+ # It sets timestamp fields +created_at+ and +updated_at+ if they are
143
+ # blank. It sets a hash key field as well if it's blank. It expects that
144
+ # the hash key field is +string+ and sets a random UUID value if the field
145
+ # value is blank. All the field values are type casted to the declared
146
+ # types.
75
147
  #
76
- # @example
77
- # User.import([{ name: 'a' }, { name: 'b' }])
78
- def import(objects)
79
- documents = objects.map do |attrs|
80
- attrs = attrs.symbolize_keys
148
+ # It works efficiently and uses the `BatchWriteItem` operation. In order
149
+ # to cope with throttling it uses a backoff strategy if it's specified with
150
+ # `Dynamoid::Config.backoff` configuration option.
151
+ #
152
+ # Because of the nature of DynamoDB and its limits only 25 models can be
153
+ # saved at once. So multiple HTTP requests can be sent to DynamoDB.
154
+ #
155
+ # @param array_of_attributes [Array<Hash>]
156
+ # @return [Array] Created models
157
+ def import(array_of_attributes)
158
+ Import.call(self, array_of_attributes)
159
+ end
81
160
 
82
- if Dynamoid::Config.timestamps
83
- time_now = DateTime.now.in_time_zone(Time.zone)
84
- attrs[:created_at] ||= time_now
85
- attrs[:updated_at] ||= time_now
86
- end
161
+ # Create a model.
162
+ #
163
+ # Initializes a new model and immediately saves it to DynamoDB.
164
+ #
165
+ # User.create(first_name: 'Mark', last_name: 'Tyler')
166
+ #
167
+ # Accepts both Hash and Array of Hashes and can create several models.
168
+ #
169
+ # User.create([{ first_name: 'Alice' }, { first_name: 'Bob' }])
170
+ #
171
+ # Creates a model and pass it into a block to set other attributes.
172
+ #
173
+ # User.create(first_name: 'Mark') do |u|
174
+ # u.age = 21
175
+ # end
176
+ #
177
+ # Validates model and runs callbacks.
178
+ #
179
+ # @param attrs [Hash|Array[Hash]] Attributes of the models
180
+ # @param block [Proc] Block to process a document after initialization
181
+ # @return [Dynamoid::Document] The created document
182
+ # @since 0.2.0
183
+ def create(attrs = {}, &block)
184
+ if attrs.is_a?(Array)
185
+ attrs.map { |attr| create(attr, &block) }
186
+ else
187
+ build(attrs, &block).tap(&:save)
188
+ end
189
+ end
87
190
 
88
- build(attrs).tap do |item|
89
- item.hash_key = SecureRandom.uuid if item.hash_key.blank?
90
- end
191
+ # Create a model.
192
+ #
193
+ # Initializes a new object and immediately saves it to the Dynamoid.
194
+ # Raises an exception +Dynamoid::Errors::DocumentNotValid+ if validation
195
+ # failed. Accepts both Hash and Array of Hashes and can create several
196
+ # models.
197
+ #
198
+ # @param attrs [Hash|Array[Hash]] Attributes with which to create the object.
199
+ # @param block [Proc] Block to process a document after initialization
200
+ # @return [Dynamoid::Document] The created document
201
+ # @since 0.2.0
202
+ def create!(attrs = {}, &block)
203
+ if attrs.is_a?(Array)
204
+ attrs.map { |attr| create!(attr, &block) }
205
+ else
206
+ build(attrs, &block).tap(&:save!)
91
207
  end
208
+ end
92
209
 
93
- if Dynamoid.config.backoff
94
- backoff = nil
210
+ # Update document with provided attributes.
211
+ #
212
+ # Instantiates document and saves changes. Runs validations and
213
+ # callbacks. Don't save changes if validation fails.
214
+ #
215
+ # User.update('1', age: 26)
216
+ #
217
+ # If range key is declared for a model it should be passed as well:
218
+ #
219
+ # User.update('1', 'Tylor', age: 26)
220
+ #
221
+ # @param hash_key [Scalar value] hash key
222
+ # @param range_key_value [Scalar value] range key (optional)
223
+ # @param attrs [Hash]
224
+ # @return [Dynamoid::Document] Updated document
225
+ def update(hash_key, range_key_value = nil, attrs)
226
+ model = find(hash_key, range_key: range_key_value, consistent_read: true)
227
+ model.update_attributes(attrs)
228
+ model
229
+ end
95
230
 
96
- array = documents.map do |d|
97
- Dumping.dump_attributes(d.attributes, attributes)
98
- end
231
+ # Update document with provided attributes.
232
+ #
233
+ # Instantiates document and saves changes. Runs validations and
234
+ # callbacks.
235
+ #
236
+ # User.update!('1', age: 26)
237
+ #
238
+ # If range key is declared for a model it should be passed as well:
239
+ #
240
+ # User.update('1', 'Tylor', age: 26)
241
+ #
242
+ # Raises +Dynamoid::Errors::DocumentNotValid+ exception if validation fails.
243
+ #
244
+ # @param hash_key [Scalar value] hash key
245
+ # @param range_key_value [Scalar value] range key (optional)
246
+ # @param attrs [Hash]
247
+ # @return [Dynamoid::Document] Updated document
248
+ def update!(hash_key, range_key_value = nil, attrs)
249
+ model = find(hash_key, range_key: range_key_value, consistent_read: true)
250
+ model.update_attributes!(attrs)
251
+ model
252
+ end
99
253
 
100
- Dynamoid.adapter.batch_write_item(table_name, array) do |has_unprocessed_items|
101
- if has_unprocessed_items
102
- backoff ||= Dynamoid.config.build_backoff
103
- backoff.call
104
- else
105
- backoff = nil
106
- end
107
- end
254
+ # Update document.
255
+ #
256
+ # Doesn't run validations and callbacks.
257
+ #
258
+ # User.update_fields('1', age: 26)
259
+ #
260
+ # If range key is declared for a model it should be passed as well:
261
+ #
262
+ # User.update_fields('1', 'Tylor', age: 26)
263
+ #
264
+ # Can make a conditional update so a document will be updated only if it
265
+ # meets the specified conditions. Conditions can be specified as a +Hash+
266
+ # with +:if+ key:
267
+ #
268
+ # User.update_fields('1', { age: 26 }, if: { version: 1 })
269
+ #
270
+ # Here +User+ model has an integer +version+ field and the document will
271
+ # be updated only if the +version+ attribute currently has value 1.
272
+ #
273
+ # If a document with specified hash and range keys doesn't exist or
274
+ # conditions were specified and failed the method call returns +nil+.
275
+ #
276
+ # +update_fields+ uses the +UpdateItem+ operation so it saves changes and
277
+ # loads an updated document back with one HTTP request.
278
+ #
279
+ # @param hash_key_value [Scalar value] hash key
280
+ # @param range_key_value [Scalar value] range key (optional)
281
+ # @param attrs [Hash]
282
+ # @param conditions [Hash] (optional)
283
+ # @return [Dynamoid::Document|nil] Updated document
284
+ def update_fields(hash_key_value, range_key_value = nil, attrs = {}, conditions = {})
285
+ optional_params = [range_key_value, attrs, conditions].compact
286
+ if optional_params.first.is_a?(Hash)
287
+ range_key_value = nil
288
+ attrs, conditions = optional_params[0..1]
108
289
  else
109
- array = documents.map do |d|
110
- Dumping.dump_attributes(d.attributes, attributes)
111
- end
290
+ range_key_value = optional_params.first
291
+ attrs, conditions = optional_params[1..2]
292
+ end
293
+
294
+ UpdateFields.call(self,
295
+ partition_key: hash_key_value,
296
+ sort_key: range_key_value,
297
+ attributes: attrs,
298
+ conditions: conditions)
299
+ end
112
300
 
113
- Dynamoid.adapter.batch_write_item(table_name, array)
301
+ # Update an existing document or create a new one.
302
+ #
303
+ # If a document with specified hash and range keys doesn't exist it
304
+ # creates a new document with specified attributes. Doesn't run
305
+ # validations and callbacks.
306
+ #
307
+ # User.upsert('1', age: 26)
308
+ #
309
+ # If range key is declared for a model it should be passed as well:
310
+ #
311
+ # User.upsert('1', 'Tylor', age: 26)
312
+ #
313
+ # Can make a conditional update so a document will be updated only if it
314
+ # meets the specified conditions. Conditions can be specified as a +Hash+
315
+ # with +:if+ key:
316
+ #
317
+ # User.upsert('1', { age: 26 }, if: { version: 1 })
318
+ #
319
+ # Here +User+ model has an integer +version+ field and the document will
320
+ # be updated only if the +version+ attribute currently has value 1.
321
+ #
322
+ # If conditions were specified and failed the method call returns +nil+.
323
+ #
324
+ # +upsert+ uses the +UpdateItem+ operation so it saves changes and loads
325
+ # an updated document back with one HTTP request.
326
+ #
327
+ # @param hash_key_value [Scalar value] hash key
328
+ # @param range_key_value [Scalar value] range key (optional)
329
+ # @param attrs [Hash]
330
+ # @param conditions [Hash] (optional)
331
+ # @return [Dynamoid::Document|nil] Updated document
332
+ def upsert(hash_key_value, range_key_value = nil, attrs = {}, conditions = {})
333
+ optional_params = [range_key_value, attrs, conditions].compact
334
+ if optional_params.first.is_a?(Hash)
335
+ range_key_value = nil
336
+ attrs, conditions = optional_params[0..1]
337
+ else
338
+ range_key_value = optional_params.first
339
+ attrs, conditions = optional_params[1..2]
114
340
  end
115
341
 
116
- documents.each { |d| d.new_record = false }
117
- documents
342
+ Upsert.call(self,
343
+ partition_key: hash_key_value,
344
+ sort_key: range_key_value,
345
+ attributes: attrs,
346
+ conditions: conditions)
347
+ end
348
+
349
+ # Increase a numeric field by specified value.
350
+ #
351
+ # User.inc('1', age: 2)
352
+ #
353
+ # Can update several fields at once.
354
+ #
355
+ # User.inc('1', age: 2, version: 1)
356
+ #
357
+ # If range key is declared for a model it should be passed as well:
358
+ #
359
+ # User.inc('1', 'Tylor', age: 2)
360
+ #
361
+ # Uses efficient low-level +UpdateItem+ operation and does only one HTTP
362
+ # request.
363
+ #
364
+ # Doesn't run validations and callbacks. Doesn't update +created_at+ and
365
+ # +updated_at+ as well.
366
+ #
367
+ # @param hash_key_value [Scalar value] hash key
368
+ # @param range_key_value [Scalar value] range key (optional)
369
+ # @param counters [Hash] value to increase by
370
+ def inc(hash_key_value, range_key_value = nil, counters)
371
+ options = if range_key
372
+ value_casted = TypeCasting.cast_field(range_key_value, attributes[range_key])
373
+ value_dumped = Dumping.dump_field(value_casted, attributes[range_key])
374
+ { range_key: value_dumped }
375
+ else
376
+ {}
377
+ end
378
+
379
+ Dynamoid.adapter.update_item(table_name, hash_key_value, options) do |t|
380
+ counters.each do |k, v|
381
+ value_casted = TypeCasting.cast_field(v, attributes[k])
382
+ value_dumped = Dumping.dump_field(value_casted, attributes[k])
383
+
384
+ t.add(k => value_dumped)
385
+ end
386
+ end
118
387
  end
119
388
  end
120
389
 
121
- # Set updated_at and any passed in field to current DateTime. Useful for things like last_login_at, etc.
390
+ # Update document timestamps.
122
391
  #
392
+ # Set +updated_at+ attribute to current DateTime.
393
+ #
394
+ # post.touch
395
+ #
396
+ # Can update another field in addition with the same timestamp if it's name passed as argument.
397
+ #
398
+ # user.touch(:last_login_at)
399
+ #
400
+ # @param name [Symbol] attribute name to update (optional)
123
401
  def touch(name = nil)
124
402
  now = DateTime.now
125
403
  self.updated_at = now
@@ -127,43 +405,109 @@ module Dynamoid
127
405
  save
128
406
  end
129
407
 
130
- # Is this object persisted in the datastore? Required for some ActiveModel integration stuff.
408
+ # Is this object persisted in DynamoDB?
409
+ #
410
+ # user = User.new
411
+ # user.persisted? # => false
412
+ #
413
+ # user.save
414
+ # user.persisted? # => true
131
415
  #
416
+ # @return [true|false]
132
417
  # @since 0.2.0
133
418
  def persisted?
134
- !new_record?
419
+ !(new_record? || @destroyed)
135
420
  end
136
421
 
137
- # Run the callbacks and then persist this object in the datastore.
422
+ # Create new model or persist changes.
423
+ #
424
+ # Run the validation and callbacks. Returns +true+ if saving is successful
425
+ # and +false+ otherwise.
426
+ #
427
+ # user = User.new
428
+ # user.save # => true
429
+ #
430
+ # user.age = 26
431
+ # user.save # => true
432
+ #
433
+ # Validation can be skipped with +validate: false+ option:
434
+ #
435
+ # user = User.new(age: -1)
436
+ # user.save(validate: false) # => true
437
+ #
438
+ # +save+ by default sets timestamps attributes - +created_at+ and
439
+ # +updated_at+ when creates new model and updates +updated_at+ attribute
440
+ # when update already existing one.
138
441
  #
442
+ # Changing +updated_at+ attribute at updating a model can be skipped with
443
+ # +touch: false+ option:
444
+ #
445
+ # user.save(touch: false)
446
+ #
447
+ # If a model is new and hash key (+id+ by default) is not assigned yet
448
+ # it was assigned implicitly with random UUID value.
449
+ #
450
+ # If +lock_version+ attribute is declared it will be incremented. If it's blank then it will be initialized with 1.
451
+ #
452
+ # +save+ method call raises +Dynamoid::Errors::RecordNotUnique+ exception
453
+ # if primary key (hash key + optional range key) already exists in a
454
+ # table.
455
+ #
456
+ # +save+ method call raises +Dynamoid::Errors::StaleObjectError+ exception
457
+ # if there is +lock_version+ attribute and the document in a table was
458
+ # already changed concurrently and +lock_version+ was consequently
459
+ # increased.
460
+ #
461
+ # When a table is not created yet the first +save+ method call will create
462
+ # a table. It's useful in test environment to avoid explicit table
463
+ # creation.
464
+ #
465
+ # @param options [Hash] (optional)
466
+ # @option options [true|false] :validate validate a model or not - +true+ by default (optional)
467
+ # @option options [true|false] :touch update tiemstamps fields or not - +true+ by default (optional)
468
+ # @return [true|false] Whether saving successful or not
139
469
  # @since 0.2.0
140
- def save(_options = {})
141
- self.class.create_table
470
+ def save(options = {})
471
+ self.class.create_table(sync: true)
142
472
 
143
- if new_record?
144
- conditions = { unless_exists: [self.class.hash_key] }
145
- conditions[:unless_exists] << range_key if range_key
473
+ @_touch_record = options[:touch]
146
474
 
147
- run_callbacks(:create) { persist(conditions) }
475
+ if new_record?
476
+ run_callbacks(:create) do
477
+ run_callbacks(:save) do
478
+ Save.call(self)
479
+ end
480
+ end
148
481
  else
149
- persist
482
+ run_callbacks(:save) do
483
+ Save.call(self)
484
+ end
150
485
  end
151
486
  end
152
487
 
153
- # Updates multiple attributes at once, saving the object once the updates are complete.
488
+ # Update multiple attributes at once, saving the object once the updates
489
+ # are complete. Returns +true+ if saving is successful and +false+
490
+ # otherwise.
154
491
  #
155
- # @param [Hash] attributes a hash of attributes to update
492
+ # user.update_attributes(age: 27, last_name: 'Tylor')
156
493
  #
494
+ # @param attributes [Hash] a hash of attributes to update
495
+ # @return [true|false] Whether updating successful or not
157
496
  # @since 0.2.0
158
497
  def update_attributes(attributes)
159
498
  attributes.each { |attribute, value| write_attribute(attribute, value) }
160
499
  save
161
500
  end
162
501
 
163
- # Updates multiple attributes at once, saving the object once the updates are complete.
164
- # Raises a Dynamoid::Errors::DocumentNotValid exception if there is vaidation and it fails.
502
+ # Update multiple attributes at once, saving the object once the updates
503
+ # are complete.
165
504
  #
166
- # @param [Hash] attributes a hash of attributes to update
505
+ # user.update_attributes!(age: 27, last_name: 'Tylor')
506
+ #
507
+ # Raises a +Dynamoid::Errors::DocumentNotValid+ exception if some vaidation
508
+ # fails.
509
+ #
510
+ # @param attributes [Hash] a hash of attributes to update
167
511
  def update_attributes!(attributes)
168
512
  attributes.each { |attribute, value| write_attribute(attribute, value) }
169
513
  save!
@@ -171,20 +515,68 @@ module Dynamoid
171
515
 
172
516
  # Update a single attribute, saving the object afterwards.
173
517
  #
174
- # @param [Symbol] attribute the attribute to update
175
- # @param [Object] value the value to assign it
518
+ # Returns +true+ if saving is successful and +false+ otherwise.
519
+ #
520
+ # user.update_attribute(:last_name, 'Tylor')
176
521
  #
522
+ # @param attribute [Symbol] attribute name to update
523
+ # @param value [Object] the value to assign it
524
+ # @return [Dynamoid::Document] self
177
525
  # @since 0.2.0
178
526
  def update_attribute(attribute, value)
179
527
  write_attribute(attribute, value)
180
528
  save
181
529
  end
182
530
 
531
+ # Update a model.
532
+ #
533
+ # Runs validation and callbacks. Reloads all attribute values.
534
+ #
535
+ # Accepts mandatory block in order to specify operations which will modify
536
+ # attributes. Supports following operations: +add+, +delete+ and +set+.
537
+ #
538
+ # Operation +add+ just adds a value for numeric attributes and join
539
+ # collections if attribute is a collection (one of +array+, +set+ or
540
+ # +map+).
541
+ #
542
+ # user.update do |t|
543
+ # t.add(age: 1, followers_count: 5)
544
+ # t.add(hobbies: ['skying', 'climbing'])
545
+ # end
546
+ #
547
+ # Operation +delete+ is applied to collection attribute types and
548
+ # substructs one collection from another.
549
+ #
550
+ # user.update do |t|
551
+ # t.delete(hobbies: ['skying'])
552
+ # end
553
+ #
554
+ # Operation +set+ just changes an attribute value:
555
+ #
556
+ # user.update do |t|
557
+ # t.set(age: 21)
558
+ # end
559
+ #
560
+ # All the operations works like +ADD+, +DELETE+ and +PUT+ actions supported
561
+ # by +AttributeUpdates+
562
+ # {parameter}[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LegacyConditionalParameters.AttributeUpdates.html]
563
+ # of +UpdateItem+ operation.
183
564
  #
184
- # update!() will increment the lock_version if the table has the column, but will not check it. Thus, a concurrent save will
185
- # never cause an update! to fail, but an update! may cause a concurrent save to fail.
565
+ # Can update a model conditionaly:
186
566
  #
567
+ # user.update(if: { age: 20 }) do |t|
568
+ # t.add(age: 1)
569
+ # end
187
570
  #
571
+ # If a document doesn't meet conditions it raises
572
+ # +Dynamoid::Errors::StaleObjectError+ exception.
573
+ #
574
+ # It will increment the +lock_version+ attribute if a table has the column,
575
+ # but will not check it. Thus, a concurrent +save+ call will never cause an
576
+ # +update!+ to fail, but an +update!+ may cause a concurrent +save+ to
577
+ # fail.
578
+ #
579
+ # @param conditions [Hash] Conditions on model attributes to make a conditional update (optional)
188
580
  def update!(conditions = {})
189
581
  run_callbacks(:update) do
190
582
  options = range_key ? { range_key: Dumping.dump_field(read_attribute(range_key), self.class.attributes[range_key]) } : {}
@@ -208,6 +600,54 @@ module Dynamoid
208
600
  end
209
601
  end
210
602
 
603
+ # Update a model.
604
+ #
605
+ # Runs validation and callbacks. Reloads all attribute values.
606
+ #
607
+ # Accepts mandatory block in order to specify operations which will modify
608
+ # attributes. Supports following operations: +add+, +delete+ and +set+.
609
+ #
610
+ # Operation +add+ just adds a value for numeric attributes and join
611
+ # collections if attribute is a collection (one of +array+, +set+ or
612
+ # +map+).
613
+ #
614
+ # user.update do |t|
615
+ # t.add(age: 1, followers_count: 5)
616
+ # t.add(hobbies: ['skying', 'climbing'])
617
+ # end
618
+ #
619
+ # Operation +delete+ is applied to collection attribute types and
620
+ # substructs one collection from another.
621
+ #
622
+ # user.update do |t|
623
+ # t.delete(hobbies: ['skying'])
624
+ # end
625
+ #
626
+ # Operation +set+ just changes an attribute value:
627
+ #
628
+ # user.update do |t|
629
+ # t.set(age: 21)
630
+ # end
631
+ #
632
+ # All the operations works like +ADD+, +DELETE+ and +PUT+ actions supported
633
+ # by +AttributeUpdates+
634
+ # {parameter}[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LegacyConditionalParameters.AttributeUpdates.html]
635
+ # of +UpdateItem+ operation.
636
+ #
637
+ # Can update a model conditionaly:
638
+ #
639
+ # user.update(if: { age: 20 }) do |t|
640
+ # t.add(age: 1)
641
+ # end
642
+ #
643
+ # If a document doesn't meet conditions it just returns +false+. Otherwise it returns +true+.
644
+ #
645
+ # It will increment the +lock_version+ attribute if a table has the column,
646
+ # but will not check it. Thus, a concurrent +save+ call will never cause an
647
+ # +update!+ to fail, but an +update!+ may cause a concurrent +save+ to
648
+ # fail.
649
+ #
650
+ # @param conditions [Hash] Conditions on model attributes to make a conditional update (optional)
211
651
  def update(conditions = {}, &block)
212
652
  update!(conditions, &block)
213
653
  true
@@ -215,51 +655,117 @@ module Dynamoid
215
655
  false
216
656
  end
217
657
 
218
- # Initializes attribute to zero if nil and adds the value passed as by (default is 1).
219
- # Only makes sense for number-based attributes. Returns self.
658
+ # Change numeric attribute value.
659
+ #
660
+ # Initializes attribute to zero if +nil+ and adds the specified value (by
661
+ # default is 1). Only makes sense for number-based attributes.
662
+ #
663
+ # user.increment(:followers_count)
664
+ # user.increment(:followers_count, 2)
665
+ #
666
+ # @param attribute [Symbol] attribute name
667
+ # @param by [Numeric] value to add (optional)
668
+ # @return [Dynamoid::Document] self
220
669
  def increment(attribute, by = 1)
221
670
  self[attribute] ||= 0
222
671
  self[attribute] += by
223
672
  self
224
673
  end
225
674
 
226
- # Wrapper around increment that saves the record.
227
- # Returns true if the record could be saved.
675
+ # Change numeric attribute value and save a model.
676
+ #
677
+ # Initializes attribute to zero if +nil+ and adds the specified value (by
678
+ # default is 1). Only makes sense for number-based attributes.
679
+ #
680
+ # user.increment!(:followers_count)
681
+ # user.increment!(:followers_count, 2)
682
+ #
683
+ # Returns +true+ if a model was saved and +false+ otherwise.
684
+ #
685
+ # @param attribute [Symbol] attribute name
686
+ # @param by [Numeric] value to add (optional)
687
+ # @return [true|false] whether saved model successfully
228
688
  def increment!(attribute, by = 1)
229
689
  increment(attribute, by)
230
690
  save
231
691
  end
232
692
 
233
- # Initializes attribute to zero if nil and subtracts the value passed as by (default is 1).
234
- # Only makes sense for number-based attributes. Returns self.
693
+ # Change numeric attribute value.
694
+ #
695
+ # Initializes attribute to zero if +nil+ and subtracts the specified value
696
+ # (by default is 1). Only makes sense for number-based attributes.
697
+ #
698
+ # user.decrement(:followers_count)
699
+ # user.decrement(:followers_count, 2)
700
+ #
701
+ # @param attribute [Symbol] attribute name
702
+ # @param by [Numeric] value to subtract (optional)
703
+ # @return [Dynamoid::Document] self
235
704
  def decrement(attribute, by = 1)
236
705
  self[attribute] ||= 0
237
706
  self[attribute] -= by
238
707
  self
239
708
  end
240
709
 
241
- # Wrapper around decrement that saves the record.
242
- # Returns true if the record could be saved.
710
+ # Change numeric attribute value and save a model.
711
+ #
712
+ # Initializes attribute to zero if +nil+ and subtracts the specified value
713
+ # (by default is 1). Only makes sense for number-based attributes.
714
+ #
715
+ # user.decrement!(:followers_count)
716
+ # user.decrement!(:followers_count, 2)
717
+ #
718
+ # Returns +true+ if a model was saved and +false+ otherwise.
719
+ #
720
+ # @param attribute [Symbol] attribute name
721
+ # @param by [Numeric] value to subtract (optional)
722
+ # @return [true|false] whether saved model successfully
243
723
  def decrement!(attribute, by = 1)
244
724
  decrement(attribute, by)
245
725
  save
246
726
  end
247
727
 
248
- # Delete this object, but only after running callbacks for it.
728
+ # Delete a model.
729
+ #
730
+ # Runs callbacks.
731
+ #
732
+ # Supports optimistic locking with the +lock_version+ attribute and doesn't
733
+ # delete a model if it's already changed.
249
734
  #
735
+ # Returns +true+ if deleted successfully and +false+ otherwise.
736
+ #
737
+ # @return [true|false] whether deleted successfully
250
738
  # @since 0.2.0
251
739
  def destroy
252
740
  ret = run_callbacks(:destroy) do
253
741
  delete
254
742
  end
743
+
744
+ @destroyed = true
745
+
255
746
  ret == false ? false : self
256
747
  end
257
748
 
749
+ # Delete a model.
750
+ #
751
+ # Runs callbacks.
752
+ #
753
+ # Supports optimistic locking with the +lock_version+ attribute and doesn't
754
+ # delete a model if it's already changed.
755
+ #
756
+ # Raises +Dynamoid::Errors::RecordNotDestroyed+ exception if model deleting
757
+ # failed.
258
758
  def destroy!
259
759
  destroy || (raise Dynamoid::Errors::RecordNotDestroyed, self)
260
760
  end
261
761
 
262
- # Delete this object from the datastore.
762
+ # Delete a model.
763
+ #
764
+ # Supports optimistic locking with the +lock_version+ attribute and doesn't
765
+ # delete a model if it's already changed.
766
+ #
767
+ # Raises +Dynamoid::Errors::StaleObjectError+ exception if cannot delete a
768
+ # model.
263
769
  #
264
770
  # @since 0.2.0
265
771
  def delete
@@ -276,48 +782,12 @@ module Dynamoid
276
782
  end
277
783
  options[:conditions] = conditions
278
784
  end
785
+
786
+ @destroyed = true
787
+
279
788
  Dynamoid.adapter.delete(self.class.table_name, hash_key, options)
280
789
  rescue Dynamoid::Errors::ConditionalCheckFailedException
281
790
  raise Dynamoid::Errors::StaleObjectError.new(self, 'delete')
282
791
  end
283
-
284
- private
285
-
286
- # Persist the object into the datastore. Assign it an id first if it doesn't have one.
287
- #
288
- # @since 0.2.0
289
- def persist(conditions = nil)
290
- run_callbacks(:save) do
291
- self.hash_key = SecureRandom.uuid if hash_key.blank?
292
-
293
- # Add an exists check to prevent overwriting existing records with new ones
294
- if new_record?
295
- conditions ||= {}
296
- (conditions[:unless_exists] ||= []) << self.class.hash_key
297
- end
298
-
299
- # Add an optimistic locking check if the lock_version column exists
300
- if self.class.attributes[:lock_version]
301
- conditions ||= {}
302
- self.lock_version = (lock_version || 0) + 1
303
- # Uses the original lock_version value from ActiveModel::Dirty in case user changed lock_version manually
304
- (conditions[:if] ||= {})[:lock_version] = changes[:lock_version][0] if changes[:lock_version][0]
305
- end
306
-
307
- attributes_dumped = Dumping.dump_attributes(attributes, self.class.attributes)
308
-
309
- begin
310
- Dynamoid.adapter.write(self.class.table_name, attributes_dumped, conditions)
311
- @new_record = false
312
- true
313
- rescue Dynamoid::Errors::ConditionalCheckFailedException => e
314
- if new_record?
315
- raise Dynamoid::Errors::RecordNotUnique.new(e, self)
316
- else
317
- raise Dynamoid::Errors::StaleObjectError.new(self, 'persist')
318
- end
319
- end
320
- end
321
- end
322
792
  end
323
793
  end