dynamoid 1.3.4 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +3 -0
  4. data/.travis.yml +37 -7
  5. data/Appraisals +11 -0
  6. data/CHANGELOG.md +115 -2
  7. data/Gemfile +2 -0
  8. data/LICENSE.txt +18 -16
  9. data/README.md +253 -34
  10. data/Rakefile +0 -24
  11. data/Vagrantfile +1 -1
  12. data/docker-compose.yml +7 -0
  13. data/dynamoid.gemspec +4 -4
  14. data/gemfiles/rails_4_0.gemfile +3 -3
  15. data/gemfiles/rails_4_1.gemfile +3 -3
  16. data/gemfiles/rails_4_2.gemfile +3 -3
  17. data/gemfiles/rails_5_0.gemfile +2 -1
  18. data/gemfiles/rails_5_1.gemfile +8 -0
  19. data/gemfiles/rails_5_2.gemfile +8 -0
  20. data/lib/dynamoid.rb +31 -31
  21. data/lib/dynamoid/adapter.rb +14 -10
  22. data/lib/dynamoid/adapter_plugin/aws_sdk_v2.rb +188 -100
  23. data/lib/dynamoid/associations.rb +21 -12
  24. data/lib/dynamoid/associations/association.rb +19 -3
  25. data/lib/dynamoid/associations/belongs_to.rb +26 -16
  26. data/lib/dynamoid/associations/has_and_belongs_to_many.rb +0 -16
  27. data/lib/dynamoid/associations/has_many.rb +2 -17
  28. data/lib/dynamoid/associations/has_one.rb +0 -14
  29. data/lib/dynamoid/associations/many_association.rb +19 -6
  30. data/lib/dynamoid/associations/single_association.rb +25 -7
  31. data/lib/dynamoid/config.rb +37 -18
  32. data/lib/dynamoid/config/backoff_strategies/constant_backoff.rb +11 -0
  33. data/lib/dynamoid/config/backoff_strategies/exponential_backoff.rb +25 -0
  34. data/lib/dynamoid/config/options.rb +1 -1
  35. data/lib/dynamoid/criteria/chain.rb +48 -32
  36. data/lib/dynamoid/dirty.rb +23 -4
  37. data/lib/dynamoid/document.rb +88 -5
  38. data/lib/dynamoid/errors.rb +4 -1
  39. data/lib/dynamoid/fields.rb +6 -6
  40. data/lib/dynamoid/finders.rb +42 -12
  41. data/lib/dynamoid/identity_map.rb +0 -1
  42. data/lib/dynamoid/indexes.rb +41 -54
  43. data/lib/dynamoid/persistence.rb +151 -40
  44. data/lib/dynamoid/railtie.rb +1 -1
  45. data/lib/dynamoid/validations.rb +4 -3
  46. data/lib/dynamoid/version.rb +1 -1
  47. metadata +18 -29
  48. data/gemfiles/rails_4_0.gemfile.lock +0 -150
  49. data/gemfiles/rails_4_1.gemfile.lock +0 -154
  50. data/gemfiles/rails_4_2.gemfile.lock +0 -175
  51. data/gemfiles/rails_5_0.gemfile.lock +0 -180
@@ -23,7 +23,7 @@ module Dynamoid #:nodoc:
23
23
  field :created_at, :datetime
24
24
  field :updated_at, :datetime
25
25
 
26
- field :id #Default primary key
26
+ field :id # Default primary key
27
27
  end
28
28
 
29
29
  module ClassMethods
@@ -50,7 +50,7 @@ module Dynamoid #:nodoc:
50
50
  Dynamoid.logger.warn("Field type :float, which you declared for '#{name}', is deprecated in favor of :number.")
51
51
  type = :number
52
52
  end
53
- self.attributes = attributes.merge(name => {:type => type}.merge(options))
53
+ self.attributes = attributes.merge(name => {type: type}.merge(options))
54
54
 
55
55
  generated_methods.module_eval do
56
56
  define_method(named) { read_attribute(named) }
@@ -67,13 +67,13 @@ module Dynamoid #:nodoc:
67
67
  end
68
68
  end
69
69
 
70
- def range(name, type = :string)
71
- field(name, type)
70
+ def range(name, type = :string, options = {})
71
+ field(name, type, options)
72
72
  self.range_key = name
73
73
  end
74
74
 
75
75
  def table(options)
76
- #a default 'id' column is created when Dynamoid::Document is included
76
+ # a default 'id' column is created when Dynamoid::Document is included
77
77
  unless(attributes.has_key? hash_key)
78
78
  remove_field :id
79
79
  field(hash_key)
@@ -82,7 +82,7 @@ module Dynamoid #:nodoc:
82
82
 
83
83
  def remove_field(field)
84
84
  field = field.to_sym
85
- attributes.delete(field) or raise "No such field"
85
+ attributes.delete(field) or raise 'No such field'
86
86
 
87
87
  generated_methods.module_eval do
88
88
  remove_method field
@@ -36,15 +36,27 @@ module Dynamoid
36
36
  ids = Array(ids.flatten.uniq)
37
37
  if ids.count == 1
38
38
  result = self.find_by_id(ids.first, options)
39
+ if result.nil?
40
+ message = "Couldn't find #{self.name} with '#{self.hash_key}'=#{ids[0]}"
41
+ raise Errors::RecordNotFound.new(message)
42
+ end
39
43
  expects_array ? Array(result) : result
40
44
  else
41
- find_all(ids)
45
+ result = find_all(ids)
46
+ if result.size != ids.size
47
+ message = "Couldn't find all #{self.name.pluralize} with '#{self.hash_key}': (#{ids.join(', ')}) "
48
+ message << "(found #{result.size} results, but was looking for #{ids.size})"
49
+ raise Errors::RecordNotFound.new(message)
50
+ end
51
+ result
42
52
  end
43
53
  end
44
54
 
45
- # Return objects found by the given array of ids, either hash keys, or hash/range key combinations using BatchGet.
55
+ # Return objects found by the given array of ids, either hash keys, or hash/range key combinations using BatchGetItem.
46
56
  # Returns empty array if no results found.
47
57
  #
58
+ # Uses backoff specified by `Dynamoid::Config.backoff` config option
59
+ #
48
60
  # @param [Array<ID>] ids
49
61
  # @param [Hash] options: Passed to the underlying query.
50
62
  #
@@ -55,8 +67,26 @@ module Dynamoid
55
67
  # find all the tweets using hash key and range key with consistent read
56
68
  # Tweet.find_all([['1', 'red'], ['1', 'green']], :consistent_read => true)
57
69
  def find_all(ids, options = {})
58
- items = Dynamoid.adapter.read(self.table_name, ids, options)
59
- items ? items[self.table_name].map{|i| from_database(i)} : []
70
+ results = unless Dynamoid.config.backoff
71
+ items = Dynamoid.adapter.read(self.table_name, ids, options)
72
+ items ? items[self.table_name] : []
73
+ else
74
+ items = []
75
+ backoff = nil
76
+ Dynamoid.adapter.read(self.table_name, ids, options) do |hash, has_unprocessed_items|
77
+ items += hash[self.table_name]
78
+
79
+ if has_unprocessed_items
80
+ backoff ||= Dynamoid.config.build_backoff
81
+ backoff.call
82
+ else
83
+ backoff = nil
84
+ end
85
+ end
86
+ items
87
+ end
88
+
89
+ results ? results.map {|i| from_database(i) } : []
60
90
  end
61
91
 
62
92
  # Find one object directly by id.
@@ -80,7 +110,7 @@ module Dynamoid
80
110
  # @param [String/Number] range_key of the object to find
81
111
  #
82
112
  def find_by_composite_key(hash_key, range_key, options = {})
83
- find_by_id(hash_key, options.merge({:range_key => range_key}))
113
+ find_by_id(hash_key, options.merge(range_key: range_key))
84
114
  end
85
115
 
86
116
  # Find all objects by hash and range keys.
@@ -105,7 +135,7 @@ module Dynamoid
105
135
  # @return [Array] an array of all matching items
106
136
  #
107
137
  def find_all_by_composite_key(hash_key, options = {})
108
- Dynamoid.adapter.query(self.table_name, options.merge({hash_value: hash_key})).collect do |item|
138
+ Dynamoid.adapter.query(self.table_name, options.merge(hash_value: hash_key)).collect do |item|
109
139
  from_database(item)
110
140
  end
111
141
  end
@@ -138,9 +168,9 @@ module Dynamoid
138
168
 
139
169
  if range_key_field
140
170
  range_key_field = range_key_field.to_s
141
- range_key_op = "eq"
142
- if range_key_field.include?(".")
143
- range_key_field, range_key_op = range_key_field.split(".", 2)
171
+ range_key_op = 'eq'
172
+ if range_key_field.include?('.')
173
+ range_key_field, range_key_op = range_key_field.split('.', 2)
144
174
  end
145
175
  range_op_mapped = RANGE_MAP.fetch(range_key_op)
146
176
  end
@@ -151,9 +181,9 @@ module Dynamoid
151
181
 
152
182
  # query
153
183
  opts = {
154
- :hash_key => hash_key_field.to_s,
155
- :hash_value => hash_key_value,
156
- :index_name => index.name,
184
+ hash_key: hash_key_field.to_s,
185
+ hash_value: hash_key_value,
186
+ index_name: index.name,
157
187
  }
158
188
  if range_key_field
159
189
  opts[:range_key] = range_key_field
@@ -80,7 +80,6 @@ module Dynamoid
80
80
  super
81
81
  end
82
82
 
83
-
84
83
  def identity_map_key
85
84
  key = hash_key.to_s
86
85
  if self.class.range_key
@@ -39,8 +39,8 @@ module Dynamoid
39
39
  end
40
40
 
41
41
  index_opts = {
42
- :read_capacity => Dynamoid::Config.read_capacity,
43
- :write_capacity => Dynamoid::Config.write_capacity
42
+ read_capacity: Dynamoid::Config.read_capacity,
43
+ write_capacity: Dynamoid::Config.write_capacity
44
44
  }.merge(options)
45
45
 
46
46
  index_opts[:dynamoid_class] = self
@@ -52,7 +52,6 @@ module Dynamoid
52
52
  self
53
53
  end
54
54
 
55
-
56
55
  # Defines a local secondary index on a table. Will use the same primary
57
56
  # hash key as the table.
58
57
  #
@@ -83,11 +82,10 @@ module Dynamoid
83
82
  ' must use a different :range_key than the primary key')
84
83
  end
85
84
 
86
- index_opts = options.merge({
87
- :dynamoid_class => self,
88
- :type => :local_secondary,
89
- :hash_key => primary_hash_key
90
- })
85
+ index_opts = options.merge(
86
+ dynamoid_class: self,
87
+ type: :local_secondary,
88
+ hash_key: primary_hash_key)
91
89
 
92
90
  index = Dynamoid::Indexes::Index.new(index_opts)
93
91
  key = index_key(primary_hash_key, index_range_key)
@@ -95,13 +93,11 @@ module Dynamoid
95
93
  self
96
94
  end
97
95
 
98
-
99
96
  def find_index(hash, range=nil)
100
97
  index = self.indexes[index_key(hash, range)]
101
98
  index
102
99
  end
103
100
 
104
-
105
101
  # Returns true iff the provided hash[,range] key combo is a local
106
102
  # secondary index.
107
103
  #
@@ -113,7 +109,6 @@ module Dynamoid
113
109
  self.local_secondary_indexes[index_key(hash, range)].present?
114
110
  end
115
111
 
116
-
117
112
  # Returns true iff the provided hash[,range] key combo is a global
118
113
  # secondary index.
119
114
  #
@@ -125,7 +120,6 @@ module Dynamoid
125
120
  self.global_secondary_indexes[index_key(hash, range)].present?
126
121
  end
127
122
 
128
-
129
123
  # Generates a convenient lookup key name for a hash/range index.
130
124
  # Should normally not be used directly.
131
125
  #
@@ -140,7 +134,6 @@ module Dynamoid
140
134
  name
141
135
  end
142
136
 
143
-
144
137
  # Generates a default index name.
145
138
  #
146
139
  # @param [Symbol] hash hash key name.
@@ -150,7 +143,6 @@ module Dynamoid
150
143
  "#{self.table_name}_index_#{self.index_key(hash, range)}"
151
144
  end
152
145
 
153
-
154
146
  # Convenience method to return all indexes on the table.
155
147
  #
156
148
  # @return [Hash<String, Object>] the combined hash of global and local
@@ -166,7 +158,6 @@ module Dynamoid
166
158
  end
167
159
  end
168
160
 
169
-
170
161
  # Represents the attributes of a DynamoDB index.
171
162
  class Index
172
163
  include ActiveModel::Validations
@@ -178,7 +169,6 @@ module Dynamoid
178
169
  :hash_key_schema, :range_key_schema, :projected_attributes,
179
170
  :read_capacity, :write_capacity
180
171
 
181
-
182
172
  validate do
183
173
  validate_index_type
184
174
  validate_hash_key
@@ -186,7 +176,6 @@ module Dynamoid
186
176
  validate_projected_attributes
187
177
  end
188
178
 
189
-
190
179
  def initialize(attrs={})
191
180
  unless attrs[:dynamoid_class].present?
192
181
  raise Dynamoid::Errors::InvalidIndex.new(':dynamoid_class is required')
@@ -205,7 +194,6 @@ module Dynamoid
205
194
  raise Dynamoid::Errors::InvalidIndex.new(self) unless self.valid?
206
195
  end
207
196
 
208
-
209
197
  # Convenience method to determine the projection type for an index.
210
198
  # Projection types are: :keys_only, :all, :include.
211
199
  #
@@ -218,56 +206,55 @@ module Dynamoid
218
206
  end
219
207
  end
220
208
 
221
-
222
209
  private
223
210
 
224
- def validate_projected_attributes
225
- unless (@projected_attributes.is_a?(Array) ||
226
- PROJECTION_TYPES.include?(@projected_attributes))
227
- errors.add(:projected_attributes, 'Invalid projected attributes specified.')
228
- end
211
+ def validate_projected_attributes
212
+ unless (@projected_attributes.is_a?(Array) ||
213
+ PROJECTION_TYPES.include?(@projected_attributes))
214
+ errors.add(:projected_attributes, 'Invalid projected attributes specified.')
229
215
  end
216
+ end
230
217
 
231
- def validate_index_type
232
- unless (@type.present? &&
233
- [:local_secondary, :global_secondary].include?(@type))
234
- errors.add(:type, 'Invalid index :type specified')
235
- end
218
+ def validate_index_type
219
+ unless (@type.present? &&
220
+ [:local_secondary, :global_secondary].include?(@type))
221
+ errors.add(:type, 'Invalid index :type specified')
236
222
  end
223
+ end
237
224
 
238
- def validate_range_key
239
- if @range_key.present?
240
- range_field_attributes = @dynamoid_class.attributes[@range_key]
241
- if range_field_attributes.present?
242
- range_key_type = range_field_attributes[:type]
243
- if Dynamoid::Fields::PERMITTED_KEY_TYPES.include?(range_key_type)
244
- @range_key_schema = {
245
- @range_key => @dynamoid_class.dynamo_type(range_key_type)
246
- }
247
- else
248
- errors.add(:range_key, 'Index :range_key is not a valid key type')
249
- end
225
+ def validate_range_key
226
+ if @range_key.present?
227
+ range_field_attributes = @dynamoid_class.attributes[@range_key]
228
+ if range_field_attributes.present?
229
+ range_key_type = range_field_attributes[:type]
230
+ if Dynamoid::Fields::PERMITTED_KEY_TYPES.include?(range_key_type)
231
+ @range_key_schema = {
232
+ @range_key => @dynamoid_class.dynamo_type(range_key_type)
233
+ }
250
234
  else
251
- errors.add(:range_key, "No such field #{@range_key} defined on table")
235
+ errors.add(:range_key, 'Index :range_key is not a valid key type')
252
236
  end
237
+ else
238
+ errors.add(:range_key, "No such field #{@range_key} defined on table")
253
239
  end
254
240
  end
241
+ end
255
242
 
256
- def validate_hash_key
257
- hash_field_attributes = @dynamoid_class.attributes[@hash_key]
258
- if hash_field_attributes.present?
259
- hash_field_type = hash_field_attributes[:type]
260
- if Dynamoid::Fields::PERMITTED_KEY_TYPES.include?(hash_field_type)
261
- @hash_key_schema = {
262
- @hash_key => @dynamoid_class.dynamo_type(hash_field_type)
263
- }
264
- else
265
- errors.add(:hash_key, 'Index :hash_key is not a valid key type')
266
- end
243
+ def validate_hash_key
244
+ hash_field_attributes = @dynamoid_class.attributes[@hash_key]
245
+ if hash_field_attributes.present?
246
+ hash_field_type = hash_field_attributes[:type]
247
+ if Dynamoid::Fields::PERMITTED_KEY_TYPES.include?(hash_field_type)
248
+ @hash_key_schema = {
249
+ @hash_key => @dynamoid_class.dynamo_type(hash_field_type)
250
+ }
267
251
  else
268
- errors.add(:hash_key, "No such field #{@hash_key} defined on table")
252
+ errors.add(:hash_key, 'Index :hash_key is not a valid key type')
269
253
  end
254
+ else
255
+ errors.add(:hash_key, "No such field #{@hash_key} defined on table")
270
256
  end
257
+ end
271
258
  end
272
259
  end
273
260
  end
@@ -21,7 +21,7 @@ module Dynamoid
21
21
  table_base_name = options[:name] || base_class.name.split('::').last
22
22
  .downcase.pluralize
23
23
 
24
- @table_name ||= [Dynamoid::Config.namespace.to_s,table_base_name].reject(&:empty?).join("_")
24
+ @table_name ||= [Dynamoid::Config.namespace.to_s, table_base_name].reject(&:empty?).join('_')
25
25
  end
26
26
 
27
27
  # Creates a table.
@@ -41,14 +41,14 @@ module Dynamoid
41
41
  range_key_hash = nil
42
42
  end
43
43
  options = {
44
- :id => self.hash_key,
45
- :table_name => self.table_name,
46
- :write_capacity => self.write_capacity,
47
- :read_capacity => self.read_capacity,
48
- :range_key => range_key_hash,
49
- :hash_key_type => dynamo_type(attributes[self.hash_key][:type]),
50
- :local_secondary_indexes => self.local_secondary_indexes.values,
51
- :global_secondary_indexes => self.global_secondary_indexes.values
44
+ id: self.hash_key,
45
+ table_name: self.table_name,
46
+ write_capacity: self.write_capacity,
47
+ read_capacity: self.read_capacity,
48
+ range_key: range_key_hash,
49
+ hash_key_type: dynamo_type(attributes[self.hash_key][:type]),
50
+ local_secondary_indexes: self.local_secondary_indexes.values,
51
+ global_secondary_indexes: self.global_secondary_indexes.values
52
52
  }.merge(options)
53
53
 
54
54
  Dynamoid.adapter.create_table(options[:table_name], options[:id], options)
@@ -74,9 +74,7 @@ module Dynamoid
74
74
  if incoming.has_key?(attribute)
75
75
  hash[attribute] = undump_field(incoming[attribute], options)
76
76
  elsif options.has_key?(:default)
77
- default_value = options[:default]
78
- value = default_value.respond_to?(:call) ? default_value.call : default_value.dup
79
- hash[attribute] = value
77
+ hash[attribute] = evaluate_default_value(options[:default])
80
78
  else
81
79
  hash[attribute] = nil
82
80
  end
@@ -119,34 +117,22 @@ module Dynamoid
119
117
  value
120
118
  end
121
119
  when :set
122
- Set.new(value)
120
+ undump_set(options, value)
123
121
  when :datetime
124
- if value.is_a?(Date) || value.is_a?(DateTime) || value.is_a?(Time)
125
- value
126
- else
127
- case Dynamoid::Config.application_timezone
128
- when :utc
129
- ActiveSupport::TimeZone['UTC'].at(value).to_datetime
130
- when :local
131
- Time.at(value).to_datetime
132
- when String
133
- ActiveSupport::TimeZone[Dynamoid::Config.application_timezone].at(value).to_datetime
134
- end
135
- end
122
+ parse_datetime(value, options)
136
123
  when :date
137
124
  if value.is_a?(Date) || value.is_a?(DateTime) || value.is_a?(Time)
138
125
  value.to_date
139
126
  else
140
- UNIX_EPOCH_DATE + value.to_i
127
+ parse_date(value, options)
141
128
  end
142
129
  when :boolean
143
- # persisted as 't', but because undump is called during initialize it can come in as true
144
130
  if value == 't' || value == true
145
131
  true
146
132
  elsif value == 'f' || value == false
147
133
  false
148
134
  else
149
- raise ArgumentError, "Boolean column neither true nor false"
135
+ raise ArgumentError, 'Boolean column neither true nor false'
150
136
  end
151
137
  else
152
138
  raise ArgumentError, "Unknown type #{options[:type]}"
@@ -155,6 +141,17 @@ module Dynamoid
155
141
  end
156
142
  end
157
143
 
144
+ def undump_set(options, value)
145
+ case options[:of]
146
+ when :integer
147
+ value.map { |v| Integer(v) }.to_set
148
+ when :number
149
+ value.map { |v| BigDecimal.new(v.to_s) }.to_set
150
+ else
151
+ value.is_a?(Set) ? value : Set.new(value)
152
+ end
153
+ end
154
+
158
155
  def dump_field(value, options)
159
156
  if (field_class = options[:type]).is_a?(Class)
160
157
  if value.respond_to?(:dynamoid_dump)
@@ -177,15 +174,23 @@ module Dynamoid
177
174
  when :array
178
175
  !value.nil? ? value : nil
179
176
  when :datetime
180
- !value.nil? ? value.to_time.to_f : nil
177
+ !value.nil? ? format_datetime(value, options) : nil
181
178
  when :date
182
- !value.nil? ? (value.to_date - UNIX_EPOCH_DATE).to_i : nil
179
+ !value.nil? ? format_date(value, options) : nil
183
180
  when :serialized
184
181
  options[:serializer] ? options[:serializer].dump(value) : value.to_yaml
185
182
  when :raw
186
183
  !value.nil? ? value : nil
187
184
  when :boolean
188
- !value.nil? ? value.to_s[0] : nil
185
+ if !value.nil?
186
+ if options[:store_as_native_boolean]
187
+ !!value # native boolean type
188
+ else
189
+ value.to_s[0] # => "f" or "t"
190
+ end
191
+ else
192
+ nil
193
+ end
189
194
  else
190
195
  raise ArgumentError, "Unknown type #{options[:type]}"
191
196
  end
@@ -207,6 +212,43 @@ module Dynamoid
207
212
  end
208
213
  end
209
214
 
215
+ # Creates several models at once.
216
+ # Neither callbacks nor validations run.
217
+ # It works efficiently because of using BatchWriteItem.
218
+ #
219
+ # Returns array of models
220
+ #
221
+ # Uses backoff specified by `Dynamoid::Config.backoff` config option
222
+ #
223
+ # @param [Array<Hash>] items
224
+ #
225
+ # @example
226
+ # User.import([{ name: 'a' }, { name: 'b' }])
227
+ def import(objects)
228
+ documents = objects.map do |attrs|
229
+ self.build(attrs).tap do |item|
230
+ item.hash_key = SecureRandom.uuid if item.hash_key.blank?
231
+ end
232
+ end
233
+
234
+ unless Dynamoid.config.backoff
235
+ Dynamoid.adapter.batch_write_item(self.table_name, documents.map(&:dump))
236
+ else
237
+ backoff = nil
238
+ Dynamoid.adapter.batch_write_item(self.table_name, documents.map(&:dump)) do |has_unprocessed_items|
239
+ if has_unprocessed_items
240
+ backoff ||= Dynamoid.config.build_backoff
241
+ backoff.call
242
+ else
243
+ backoff = nil
244
+ end
245
+ end
246
+ end
247
+
248
+ documents.each { |d| d.new_record = false }
249
+ documents
250
+ end
251
+
210
252
  private
211
253
 
212
254
  def undump_hash(hash)
@@ -231,6 +273,77 @@ module Dynamoid
231
273
  val
232
274
  end
233
275
  end
276
+
277
+ def format_datetime(value, options)
278
+ use_string_format = options[:store_as_string].nil? \
279
+ ? Dynamoid.config.store_datetime_as_string \
280
+ : options[:store_as_string]
281
+
282
+ if use_string_format
283
+ value.to_time.iso8601
284
+ else
285
+ unless value.respond_to?(:to_i) && value.respond_to?(:nsec)
286
+ value = value.to_time
287
+ end
288
+ BigDecimal("%d.%09d" % [value.to_i, value.nsec])
289
+ end
290
+ end
291
+
292
+ def format_date(value, options)
293
+ use_string_format = options[:store_as_string].nil? \
294
+ ? Dynamoid.config.store_date_as_string \
295
+ : options[:store_as_string]
296
+
297
+ unless use_string_format
298
+ (value.to_date - UNIX_EPOCH_DATE).to_i
299
+ else
300
+ value.to_date.iso8601
301
+ end
302
+ end
303
+
304
+ def parse_datetime(value, options)
305
+ return value if value.is_a?(Date) || value.is_a?(DateTime) || value.is_a?(Time)
306
+
307
+ use_string_format = options[:store_as_string].nil? \
308
+ ? Dynamoid.config.store_datetime_as_string \
309
+ : options[:store_as_string]
310
+ value = DateTime.iso8601(value).to_time.to_i if use_string_format
311
+
312
+ case Dynamoid::Config.application_timezone
313
+ when :utc
314
+ ActiveSupport::TimeZone['UTC'].at(value).to_datetime
315
+ when :local
316
+ Time.at(value).to_datetime
317
+ when String
318
+ ActiveSupport::TimeZone[Dynamoid::Config.application_timezone].at(value).to_datetime
319
+ end
320
+ end
321
+
322
+ def parse_date(value, options)
323
+ use_string_format = options[:store_as_string].nil? \
324
+ ? Dynamoid.config.store_date_as_string \
325
+ : options[:store_as_string]
326
+
327
+ unless use_string_format
328
+ UNIX_EPOCH_DATE + value.to_i
329
+ else
330
+ Date.iso8601(value)
331
+ end
332
+ end
333
+
334
+ # Evaluates the default value given, this is used by undump
335
+ # when determining the value of the default given for a field options.
336
+ #
337
+ # @param [Object] :value the attribute's default value
338
+ def evaluate_default_value(val)
339
+ if val.respond_to?(:call)
340
+ val.call
341
+ elsif val.duplicable?
342
+ val.dup
343
+ else
344
+ val
345
+ end
346
+ end
234
347
  end
235
348
 
236
349
  # Set updated_at and any passed in field to current DateTime. Useful for things like last_login_at, etc.
@@ -256,15 +369,13 @@ module Dynamoid
256
369
  self.class.create_table
257
370
 
258
371
  if new_record?
259
- conditions = { :unless_exists => [self.class.hash_key]}
372
+ conditions = { unless_exists: [self.class.hash_key]}
260
373
  conditions[:unless_exists] << range_key if(range_key)
261
374
 
262
375
  run_callbacks(:create) { persist(conditions) }
263
376
  else
264
377
  persist
265
378
  end
266
-
267
- self
268
379
  end
269
380
 
270
381
  #
@@ -274,10 +385,10 @@ module Dynamoid
274
385
  #
275
386
  def update!(conditions = {}, &block)
276
387
  run_callbacks(:update) do
277
- options = range_key ? {:range_key => dump_field(self.read_attribute(range_key), self.class.attributes[range_key])} : {}
388
+ options = range_key ? {range_key: dump_field(self.read_attribute(range_key), self.class.attributes[range_key])} : {}
278
389
 
279
390
  begin
280
- new_attrs = Dynamoid.adapter.update_item(self.class.table_name, self.hash_key, options.merge(:conditions => conditions)) do |t|
391
+ new_attrs = Dynamoid.adapter.update_item(self.class.table_name, self.hash_key, options.merge(conditions: conditions)) do |t|
281
392
  if(self.class.attributes[:lock_version])
282
393
  t.add(lock_version: 1)
283
394
  end
@@ -316,11 +427,11 @@ module Dynamoid
316
427
  #
317
428
  # @since 0.2.0
318
429
  def delete
319
- options = range_key ? {:range_key => dump_field(self.read_attribute(range_key), self.class.attributes[range_key])} : {}
430
+ options = range_key ? {range_key: dump_field(self.read_attribute(range_key), self.class.attributes[range_key])} : {}
320
431
 
321
432
  # Add an optimistic locking check if the lock_version column exists
322
433
  if(self.class.attributes[:lock_version])
323
- conditions = {:if => {}}
434
+ conditions = {if: {}}
324
435
  conditions[:if][:lock_version] =
325
436
  if changes[:lock_version].nil?
326
437
  self.lock_version
@@ -360,7 +471,7 @@ module Dynamoid
360
471
  # @since 0.2.0
361
472
  def persist(conditions = nil)
362
473
  run_callbacks(:save) do
363
- self.hash_key = SecureRandom.uuid if self.hash_key.nil? || self.hash_key.blank?
474
+ self.hash_key = SecureRandom.uuid if self.hash_key.blank?
364
475
 
365
476
  # Add an exists check to prevent overwriting existing records with new ones
366
477
  if(new_record?)
@@ -372,7 +483,7 @@ module Dynamoid
372
483
  if(self.class.attributes[:lock_version])
373
484
  conditions ||= {}
374
485
  self.lock_version = (lock_version || 0) + 1
375
- #Uses the original lock_version value from ActiveModel::Dirty in case user changed lock_version manually
486
+ # Uses the original lock_version value from ActiveModel::Dirty in case user changed lock_version manually
376
487
  (conditions[:if] ||= {})[:lock_version] = changes[:lock_version][0] if(changes[:lock_version][0])
377
488
  end
378
489