dynamoid 3.4.0 → 3.7.1
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 +4 -4
- data/CHANGELOG.md +94 -3
- data/README.md +52 -26
- data/lib/dynamoid.rb +1 -0
- data/lib/dynamoid/adapter.rb +15 -6
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +48 -36
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +13 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +9 -8
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +5 -4
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/backoff.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +2 -3
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/start_key.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +4 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +6 -3
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/table.rb +1 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/until_past_table_status.rb +2 -1
- data/lib/dynamoid/application_time_zone.rb +1 -0
- data/lib/dynamoid/associations.rb +182 -19
- data/lib/dynamoid/associations/association.rb +10 -2
- data/lib/dynamoid/associations/belongs_to.rb +2 -1
- data/lib/dynamoid/associations/has_and_belongs_to_many.rb +2 -1
- data/lib/dynamoid/associations/has_many.rb +2 -1
- data/lib/dynamoid/associations/has_one.rb +2 -1
- data/lib/dynamoid/associations/many_association.rb +68 -23
- data/lib/dynamoid/associations/single_association.rb +31 -4
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/config.rb +5 -5
- data/lib/dynamoid/config/backoff_strategies/constant_backoff.rb +1 -0
- data/lib/dynamoid/config/backoff_strategies/exponential_backoff.rb +1 -0
- data/lib/dynamoid/config/options.rb +1 -0
- data/lib/dynamoid/criteria.rb +9 -1
- data/lib/dynamoid/criteria/chain.rb +422 -46
- data/lib/dynamoid/criteria/ignored_conditions_detector.rb +3 -3
- data/lib/dynamoid/criteria/key_fields_detector.rb +32 -11
- data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +3 -2
- data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +1 -1
- data/lib/dynamoid/dirty.rb +119 -64
- data/lib/dynamoid/document.rb +125 -43
- data/lib/dynamoid/dumping.rb +9 -0
- data/lib/dynamoid/dynamodb_time_zone.rb +1 -0
- data/lib/dynamoid/errors.rb +2 -0
- data/lib/dynamoid/fields.rb +217 -36
- data/lib/dynamoid/fields/declare.rb +86 -0
- data/lib/dynamoid/finders.rb +69 -32
- data/lib/dynamoid/identity_map.rb +6 -0
- data/lib/dynamoid/indexes.rb +86 -17
- data/lib/dynamoid/loadable.rb +2 -2
- data/lib/dynamoid/log/formatter.rb +26 -0
- data/lib/dynamoid/middleware/identity_map.rb +1 -0
- data/lib/dynamoid/persistence.rb +496 -104
- data/lib/dynamoid/persistence/import.rb +1 -0
- data/lib/dynamoid/persistence/save.rb +1 -0
- data/lib/dynamoid/persistence/update_fields.rb +5 -2
- data/lib/dynamoid/persistence/update_validations.rb +18 -0
- data/lib/dynamoid/persistence/upsert.rb +5 -3
- data/lib/dynamoid/primary_key_type_mapping.rb +1 -0
- data/lib/dynamoid/railtie.rb +1 -0
- data/lib/dynamoid/tasks.rb +3 -1
- data/lib/dynamoid/tasks/database.rb +1 -0
- data/lib/dynamoid/type_casting.rb +12 -2
- data/lib/dynamoid/undumping.rb +8 -0
- data/lib/dynamoid/validations.rb +6 -1
- data/lib/dynamoid/version.rb +1 -1
- metadata +48 -74
- data/.coveralls.yml +0 -1
- data/.document +0 -5
- data/.gitignore +0 -74
- data/.rspec +0 -2
- data/.rubocop.yml +0 -71
- data/.rubocop_todo.yml +0 -55
- data/.travis.yml +0 -44
- data/Appraisals +0 -22
- data/Gemfile +0 -8
- data/Rakefile +0 -46
- data/Vagrantfile +0 -29
- data/docker-compose.yml +0 -7
- data/dynamoid.gemspec +0 -57
- data/gemfiles/rails_4_2.gemfile +0 -9
- data/gemfiles/rails_5_0.gemfile +0 -8
- data/gemfiles/rails_5_1.gemfile +0 -8
- data/gemfiles/rails_5_2.gemfile +0 -8
- data/gemfiles/rails_6_0.gemfile +0 -8
data/lib/dynamoid/document.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module Dynamoid
|
|
3
|
+
module Dynamoid
|
|
4
4
|
# This is the base module for all domain objects that need to be persisted to
|
|
5
5
|
# the database as documents.
|
|
6
6
|
module Document
|
|
@@ -17,28 +17,19 @@ module Dynamoid #:nodoc:
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
module ClassMethods
|
|
20
|
-
#
|
|
21
|
-
# write capacity.
|
|
22
|
-
#
|
|
23
|
-
# @param [Hash] options options to pass for this table
|
|
24
|
-
# @option options [Symbol] :name the name for the table; this still gets namespaced
|
|
25
|
-
# @option options [Symbol] :id id column for the table
|
|
26
|
-
# @option options [Integer] :read_capacity set the read capacity for the table; does not work on existing tables
|
|
27
|
-
# @option options [Integer] :write_capacity set the write capacity for the table; does not work on existing tables
|
|
28
|
-
#
|
|
29
|
-
# @since 0.4.0
|
|
20
|
+
# @private
|
|
30
21
|
def table(options = {})
|
|
31
22
|
self.options = options
|
|
32
23
|
super if defined? super
|
|
33
24
|
end
|
|
34
25
|
|
|
35
26
|
def attr_readonly(*read_only_attributes)
|
|
36
|
-
ActiveSupport::Deprecation.warn('[Dynamoid] .attr_readonly is deprecated! Call .find instead of')
|
|
37
27
|
self.read_only_attributes.concat read_only_attributes.map(&:to_s)
|
|
38
28
|
end
|
|
39
29
|
|
|
40
|
-
# Returns the
|
|
30
|
+
# Returns the read capacity for this table.
|
|
41
31
|
#
|
|
32
|
+
# @return [Integer] read capacity units
|
|
42
33
|
# @since 0.4.0
|
|
43
34
|
def read_capacity
|
|
44
35
|
options[:read_capacity] || Dynamoid::Config.read_capacity
|
|
@@ -46,32 +37,53 @@ module Dynamoid #:nodoc:
|
|
|
46
37
|
|
|
47
38
|
# Returns the write_capacity for this table.
|
|
48
39
|
#
|
|
40
|
+
# @return [Integer] write capacity units
|
|
49
41
|
# @since 0.4.0
|
|
50
42
|
def write_capacity
|
|
51
43
|
options[:write_capacity] || Dynamoid::Config.write_capacity
|
|
52
44
|
end
|
|
53
45
|
|
|
54
|
-
|
|
55
46
|
# Returns the billing (capacity) mode for this table.
|
|
56
|
-
#
|
|
47
|
+
#
|
|
48
|
+
# Could be either +provisioned+ or +on_demand+.
|
|
49
|
+
#
|
|
50
|
+
# @return [Symbol]
|
|
57
51
|
def capacity_mode
|
|
58
52
|
options[:capacity_mode] || Dynamoid::Config.capacity_mode
|
|
59
53
|
end
|
|
60
54
|
|
|
61
55
|
# Returns the field name used to support STI for this table.
|
|
56
|
+
#
|
|
57
|
+
# Default field name is +type+ but it can be overrided in the +table+
|
|
58
|
+
# method call.
|
|
59
|
+
#
|
|
60
|
+
# User.inheritance_field # => :type
|
|
62
61
|
def inheritance_field
|
|
63
62
|
options[:inheritance_field] || :type
|
|
64
63
|
end
|
|
65
64
|
|
|
66
|
-
# Returns the
|
|
65
|
+
# Returns the hash key field name for this class.
|
|
66
|
+
#
|
|
67
|
+
# By default +id+ field is used. But it can be overriden in the +table+
|
|
68
|
+
# method call.
|
|
67
69
|
#
|
|
70
|
+
# User.hash_key # => :id
|
|
71
|
+
#
|
|
72
|
+
# @return [Symbol] a hash key name
|
|
68
73
|
# @since 0.4.0
|
|
69
74
|
def hash_key
|
|
70
75
|
options[:key] || :id
|
|
71
76
|
end
|
|
72
77
|
|
|
73
|
-
#
|
|
78
|
+
# Return the count of items for this class.
|
|
79
|
+
#
|
|
80
|
+
# It returns aproximate value based on DynamoDB statistic. DynamoDB
|
|
81
|
+
# updates it periodicaly so the value can be no accurate.
|
|
74
82
|
#
|
|
83
|
+
# It's a reletivly cheap operation and doesn't read all the items in a
|
|
84
|
+
# table. It makes just one HTTP request to DynamoDB.
|
|
85
|
+
#
|
|
86
|
+
# @return [Integer] items count in a table
|
|
75
87
|
# @since 0.6.1
|
|
76
88
|
def count
|
|
77
89
|
Dynamoid.adapter.count(table_name)
|
|
@@ -79,35 +91,67 @@ module Dynamoid #:nodoc:
|
|
|
79
91
|
|
|
80
92
|
# Initialize a new object.
|
|
81
93
|
#
|
|
82
|
-
#
|
|
94
|
+
# User.build(name: 'A')
|
|
83
95
|
#
|
|
84
|
-
#
|
|
96
|
+
# Initialize an object and pass it into a block to set other attributes.
|
|
97
|
+
#
|
|
98
|
+
# User.build(name: 'A') do |u|
|
|
99
|
+
# u.age = 21
|
|
100
|
+
# end
|
|
101
|
+
#
|
|
102
|
+
# The only difference between +build+ and +new+ methods is that +build+
|
|
103
|
+
# supports STI (Single table inheritance) and looks at the inheritance
|
|
104
|
+
# field. So it can build a model of actual class. For instance:
|
|
105
|
+
#
|
|
106
|
+
# class Employee
|
|
107
|
+
# include Dynamoid::Document
|
|
108
|
+
#
|
|
109
|
+
# field :type
|
|
110
|
+
# field :name
|
|
111
|
+
# end
|
|
85
112
|
#
|
|
113
|
+
# class Manager < Employee
|
|
114
|
+
# end
|
|
115
|
+
#
|
|
116
|
+
# Employee.build(name: 'Alice', type: 'Manager') # => #<Manager:0x00007f945756e3f0 ...>
|
|
117
|
+
#
|
|
118
|
+
# @param attrs [Hash] Attributes with which to create the document
|
|
119
|
+
# @param block [Proc] Block to process a document after initialization
|
|
120
|
+
# @return [Dynamoid::Document] the new document
|
|
86
121
|
# @since 0.2.0
|
|
87
|
-
def build(attrs = {})
|
|
88
|
-
choose_right_class(attrs).new(attrs)
|
|
122
|
+
def build(attrs = {}, &block)
|
|
123
|
+
choose_right_class(attrs).new(attrs, &block)
|
|
89
124
|
end
|
|
90
125
|
|
|
91
|
-
# Does this
|
|
126
|
+
# Does this model exist in a table?
|
|
127
|
+
#
|
|
128
|
+
# User.exists?('713') # => true
|
|
92
129
|
#
|
|
93
|
-
#
|
|
94
|
-
# Multiple keys and single compound primary key should be passed only as Array explicitily.
|
|
130
|
+
# If a range key is declared it should be specified in the following way:
|
|
95
131
|
#
|
|
96
|
-
#
|
|
132
|
+
# User.exists?([['713', 'range-key-value']]) # => true
|
|
97
133
|
#
|
|
98
|
-
#
|
|
134
|
+
# It's possible to check existence of several models at once:
|
|
99
135
|
#
|
|
100
|
-
#
|
|
136
|
+
# User.exists?(['713', '714', '715'])
|
|
101
137
|
#
|
|
102
|
-
#
|
|
138
|
+
# Or in case when a range key is declared:
|
|
103
139
|
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
140
|
+
# User.exists?(
|
|
141
|
+
# [
|
|
142
|
+
# ['713', 'range-key-value-1'],
|
|
143
|
+
# ['714', 'range-key-value-2'],
|
|
144
|
+
# ['715', 'range-key-value-3']
|
|
145
|
+
# ]
|
|
146
|
+
# )
|
|
106
147
|
#
|
|
107
|
-
#
|
|
148
|
+
# It's also possible to specify models not with primary key but with
|
|
149
|
+
# conditions on the attributes (in the +where+ method style):
|
|
108
150
|
#
|
|
109
|
-
#
|
|
151
|
+
# User.exists?(age: 20, 'created_at.gt': Time.now - 1.day)
|
|
110
152
|
#
|
|
153
|
+
# @param id_or_conditions [String|Array[String]|Array[Array]|Hash] the primary id of the model, a list of primary ids or a hash with the options to filter from.
|
|
154
|
+
# @return [true|false]
|
|
111
155
|
# @since 0.2.0
|
|
112
156
|
def exists?(id_or_conditions = {})
|
|
113
157
|
case id_or_conditions
|
|
@@ -122,10 +166,12 @@ module Dynamoid #:nodoc:
|
|
|
122
166
|
end
|
|
123
167
|
end
|
|
124
168
|
|
|
169
|
+
# @private
|
|
125
170
|
def deep_subclasses
|
|
126
171
|
subclasses + subclasses.map(&:deep_subclasses).flatten
|
|
127
172
|
end
|
|
128
173
|
|
|
174
|
+
# @private
|
|
129
175
|
def choose_right_class(attrs)
|
|
130
176
|
attrs[inheritance_field] ? attrs[inheritance_field].constantize : self
|
|
131
177
|
end
|
|
@@ -133,12 +179,20 @@ module Dynamoid #:nodoc:
|
|
|
133
179
|
|
|
134
180
|
# Initialize a new object.
|
|
135
181
|
#
|
|
136
|
-
#
|
|
182
|
+
# User.new(name: 'A')
|
|
137
183
|
#
|
|
184
|
+
# Initialize an object and pass it into a block to set other attributes.
|
|
185
|
+
#
|
|
186
|
+
# User.new(name: 'A') do |u|
|
|
187
|
+
# u.age = 21
|
|
188
|
+
# end
|
|
189
|
+
#
|
|
190
|
+
# @param attrs [Hash] Attributes with which to create the document
|
|
191
|
+
# @param block [Proc] Block to process a document after initialization
|
|
138
192
|
# @return [Dynamoid::Document] the new document
|
|
139
193
|
#
|
|
140
194
|
# @since 0.2.0
|
|
141
|
-
def initialize(attrs = {})
|
|
195
|
+
def initialize(attrs = {}, &block)
|
|
142
196
|
run_callbacks :initialize do
|
|
143
197
|
@new_record = true
|
|
144
198
|
@attributes ||= {}
|
|
@@ -156,11 +210,19 @@ module Dynamoid #:nodoc:
|
|
|
156
210
|
attrs_virtual = attrs.slice(*(attrs.keys - self.class.attributes.keys))
|
|
157
211
|
|
|
158
212
|
load(attrs_with_defaults.merge(attrs_virtual))
|
|
213
|
+
|
|
214
|
+
if block
|
|
215
|
+
block.call(self)
|
|
216
|
+
end
|
|
159
217
|
end
|
|
160
218
|
end
|
|
161
219
|
|
|
162
|
-
#
|
|
220
|
+
# Check equality of two models.
|
|
163
221
|
#
|
|
222
|
+
# A model is equal to another model only if their primary keys (hash key
|
|
223
|
+
# and optionaly range key) are equal.
|
|
224
|
+
#
|
|
225
|
+
# @return [true|false]
|
|
164
226
|
# @since 0.2.0
|
|
165
227
|
def ==(other)
|
|
166
228
|
if self.class.identity_map_on?
|
|
@@ -172,36 +234,54 @@ module Dynamoid #:nodoc:
|
|
|
172
234
|
end
|
|
173
235
|
end
|
|
174
236
|
|
|
237
|
+
# Check equality of two models.
|
|
238
|
+
#
|
|
239
|
+
# Works exactly like +==+ does.
|
|
240
|
+
#
|
|
241
|
+
# @return [true|false]
|
|
175
242
|
def eql?(other)
|
|
176
243
|
self == other
|
|
177
244
|
end
|
|
178
245
|
|
|
246
|
+
# Generate an Integer hash value for this model.
|
|
247
|
+
#
|
|
248
|
+
# Hash value is based on primary key. So models can be used safely as a
|
|
249
|
+
# +Hash+ keys.
|
|
250
|
+
#
|
|
251
|
+
# @return [Integer]
|
|
179
252
|
def hash
|
|
180
253
|
hash_key.hash ^ range_value.hash
|
|
181
254
|
end
|
|
182
255
|
|
|
183
|
-
# Return
|
|
256
|
+
# Return a model's hash key value.
|
|
184
257
|
#
|
|
185
258
|
# @since 0.4.0
|
|
186
259
|
def hash_key
|
|
187
|
-
|
|
260
|
+
self[self.class.hash_key.to_sym]
|
|
188
261
|
end
|
|
189
262
|
|
|
190
|
-
# Assign
|
|
263
|
+
# Assign a model's hash key value, regardless of what it might be called to
|
|
264
|
+
# the object.
|
|
191
265
|
#
|
|
192
266
|
# @since 0.4.0
|
|
193
267
|
def hash_key=(value)
|
|
194
|
-
|
|
268
|
+
self[self.class.hash_key.to_sym] = value
|
|
195
269
|
end
|
|
196
270
|
|
|
271
|
+
# Return a model's range key value.
|
|
272
|
+
#
|
|
273
|
+
# Returns +nil+ if a range key isn't declared for a model.
|
|
197
274
|
def range_value
|
|
198
|
-
if
|
|
199
|
-
|
|
275
|
+
if self.class.range_key
|
|
276
|
+
self[self.class.range_key.to_sym]
|
|
200
277
|
end
|
|
201
278
|
end
|
|
202
279
|
|
|
280
|
+
# Assign a model's range key value.
|
|
203
281
|
def range_value=(value)
|
|
204
|
-
|
|
282
|
+
if self.class.range_key
|
|
283
|
+
self[self.class.range_key.to_sym] = value
|
|
284
|
+
end
|
|
205
285
|
end
|
|
206
286
|
|
|
207
287
|
private
|
|
@@ -213,7 +293,7 @@ module Dynamoid #:nodoc:
|
|
|
213
293
|
# Evaluates the default value given, this is used by undump
|
|
214
294
|
# when determining the value of the default given for a field options.
|
|
215
295
|
#
|
|
216
|
-
# @param [Object]
|
|
296
|
+
# @param val [Object] the attribute's default value
|
|
217
297
|
def evaluate_default_value(val)
|
|
218
298
|
if val.respond_to?(:call)
|
|
219
299
|
val.call
|
|
@@ -225,3 +305,5 @@ module Dynamoid #:nodoc:
|
|
|
225
305
|
end
|
|
226
306
|
end
|
|
227
307
|
end
|
|
308
|
+
|
|
309
|
+
ActiveSupport.run_load_hooks(:dynamoid, Dynamoid::Document)
|
data/lib/dynamoid/dumping.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Dynamoid
|
|
4
|
+
# @private
|
|
4
5
|
module Dumping
|
|
5
6
|
def self.dump_attributes(attributes, attributes_options)
|
|
6
7
|
{}.tap do |h|
|
|
@@ -35,6 +36,7 @@ module Dynamoid
|
|
|
35
36
|
when :serialized then SerializedDumper
|
|
36
37
|
when :raw then RawDumper
|
|
37
38
|
when :boolean then BooleanDumper
|
|
39
|
+
when :binary then BinaryDumper
|
|
38
40
|
when Class then CustomTypeDumper
|
|
39
41
|
end
|
|
40
42
|
|
|
@@ -287,6 +289,13 @@ module Dynamoid
|
|
|
287
289
|
end
|
|
288
290
|
end
|
|
289
291
|
|
|
292
|
+
# string -> string
|
|
293
|
+
class BinaryDumper < Base
|
|
294
|
+
def process(value)
|
|
295
|
+
Base64.strict_encode64(value)
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
290
299
|
# any object -> string
|
|
291
300
|
class CustomTypeDumper < Base
|
|
292
301
|
def process(value)
|
data/lib/dynamoid/errors.rb
CHANGED
data/lib/dynamoid/fields.rb
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require 'dynamoid/fields/declare'
|
|
4
|
+
|
|
5
|
+
module Dynamoid
|
|
4
6
|
# All fields on a Dynamoid::Document must be explicitly defined -- if you have fields in the database that are not
|
|
5
7
|
# specified with field, then they will be ignored.
|
|
6
8
|
module Fields
|
|
7
9
|
extend ActiveSupport::Concern
|
|
8
10
|
|
|
11
|
+
# @private
|
|
9
12
|
# Types allowed in indexes:
|
|
10
13
|
PERMITTED_KEY_TYPES = %i[
|
|
11
14
|
number
|
|
12
15
|
integer
|
|
13
16
|
string
|
|
17
|
+
date
|
|
14
18
|
datetime
|
|
15
19
|
serialized
|
|
16
20
|
].freeze
|
|
@@ -33,51 +37,188 @@ module Dynamoid #:nodoc:
|
|
|
33
37
|
module ClassMethods
|
|
34
38
|
# Specify a field for a document.
|
|
35
39
|
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
40
|
+
# class User
|
|
41
|
+
# include Dynamoid::Document
|
|
42
|
+
#
|
|
43
|
+
# field :last_name
|
|
44
|
+
# field :age, :integer
|
|
45
|
+
# field :last_sign_in, :datetime
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# Its type determines how it is coerced when read in and out of the
|
|
49
|
+
# data store. You can specify +string+, +integer+, +number+, +set+, +array+,
|
|
50
|
+
# +map+, +datetime+, +date+, +serialized+, +raw+, +boolean+ and +binary+
|
|
38
51
|
# or specify a class that defines a serialization strategy.
|
|
39
52
|
#
|
|
53
|
+
# By default field type is +string+.
|
|
54
|
+
#
|
|
55
|
+
# Set can store elements of the same type only (it's a limitation of
|
|
56
|
+
# DynamoDB itself). If a set should store elements only of some particular
|
|
57
|
+
# type then +of+ option should be specified:
|
|
58
|
+
#
|
|
59
|
+
# field :hobbies, :set, of: :string
|
|
60
|
+
#
|
|
61
|
+
# Only +string+, +integer+, +number+, +date+, +datetime+ and +serialized+
|
|
62
|
+
# element types are supported.
|
|
63
|
+
#
|
|
64
|
+
# Element type can have own options - they should be specified in the
|
|
65
|
+
# form of +Hash+:
|
|
66
|
+
#
|
|
67
|
+
# field :hobbies, :set, of: { serialized: { serializer: JSON } }
|
|
68
|
+
#
|
|
69
|
+
# Array can contain element of different types but if supports the same
|
|
70
|
+
# +of+ option to convert all the provided elements to the declared type.
|
|
71
|
+
#
|
|
72
|
+
# field :rates, :array, of: :number
|
|
73
|
+
#
|
|
74
|
+
# By default +date+ and +datetime+ fields are stored as integer values.
|
|
75
|
+
# The format can be changed to string with option +store_as_string+:
|
|
76
|
+
#
|
|
77
|
+
# field :published_on, :datetime, store_as_string: true
|
|
78
|
+
#
|
|
79
|
+
# Boolean field by default is stored as a string +t+ or +f+. But DynamoDB
|
|
80
|
+
# supports boolean type natively. In order to switch to the native
|
|
81
|
+
# boolean type an option +store_as_native_boolean+ should be specified:
|
|
82
|
+
#
|
|
83
|
+
# field :active, :boolean, store_as_native_boolean: true
|
|
84
|
+
#
|
|
85
|
+
# If you specify the +serialized+ type a value will be serialized to
|
|
86
|
+
# string in Yaml format by default. Custom way to serialize value to
|
|
87
|
+
# string can be specified with +serializer+ option. Custom serializer
|
|
88
|
+
# should have +dump+ and +load+ methods.
|
|
89
|
+
#
|
|
40
90
|
# If you specify a class for field type, Dynamoid will serialize using
|
|
41
|
-
#
|
|
91
|
+
# +dynamoid_dump+ method and load using +dynamoid_load+ method.
|
|
92
|
+
#
|
|
93
|
+
# Default field type is +string+.
|
|
42
94
|
#
|
|
43
|
-
#
|
|
95
|
+
# A field can have a default value. It's assigned at initializing a model
|
|
96
|
+
# if no value is specified:
|
|
44
97
|
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
98
|
+
# field :age, :integer, default: 1
|
|
99
|
+
#
|
|
100
|
+
# If a defautl value should be recalculated every time it can be
|
|
101
|
+
# specified as a callable object (it should implement a +call+ method
|
|
102
|
+
# e.g. +Proc+ object):
|
|
103
|
+
#
|
|
104
|
+
# field :date_of_birth, :date, default: -> { Date.today }
|
|
105
|
+
#
|
|
106
|
+
# For every field Dynamoid creates several methods:
|
|
107
|
+
#
|
|
108
|
+
# * getter
|
|
109
|
+
# * setter
|
|
110
|
+
# * predicate +<name>?+ to check whether a value set
|
|
111
|
+
# * +<name>_before_type_cast?+ to get an original field value before it was type casted
|
|
112
|
+
#
|
|
113
|
+
# It works in the following way:
|
|
114
|
+
#
|
|
115
|
+
# class User
|
|
116
|
+
# include Dynamoid::Document
|
|
117
|
+
#
|
|
118
|
+
# field :age, :integer
|
|
119
|
+
# end
|
|
120
|
+
#
|
|
121
|
+
# user = User.new
|
|
122
|
+
# user.age # => nil
|
|
123
|
+
# user.age? # => false
|
|
124
|
+
#
|
|
125
|
+
# user.age = 20
|
|
126
|
+
# user.age? # => true
|
|
127
|
+
#
|
|
128
|
+
# user.age = '21'
|
|
129
|
+
# user.age # => 21 - integer
|
|
130
|
+
# user.age_before_type_cast # => '21' - string
|
|
131
|
+
#
|
|
132
|
+
# There is also an option +alias+ which allows to use another name for a
|
|
133
|
+
# field:
|
|
134
|
+
#
|
|
135
|
+
# class User
|
|
136
|
+
# include Dynamoid::Document
|
|
137
|
+
#
|
|
138
|
+
# field :firstName, :string, alias: :first_name
|
|
139
|
+
# end
|
|
140
|
+
#
|
|
141
|
+
# user = User.new(firstName: 'Michael')
|
|
142
|
+
# user.firstName # Michael
|
|
143
|
+
# user.first_name # Michael
|
|
144
|
+
#
|
|
145
|
+
# @param name [Symbol] name of the field
|
|
146
|
+
# @param type [Symbol] type of the field (optional)
|
|
147
|
+
# @param options [Hash] any additional options for the field type (optional)
|
|
48
148
|
#
|
|
49
149
|
# @since 0.2.0
|
|
50
150
|
def field(name, type = :string, options = {})
|
|
51
|
-
named = name.to_s
|
|
52
151
|
if type == :float
|
|
53
152
|
Dynamoid.logger.warn("Field type :float, which you declared for '#{name}', is deprecated in favor of :number.")
|
|
54
153
|
type = :number
|
|
55
154
|
end
|
|
56
|
-
self.attributes = attributes.merge(name => { type: type }.merge(options))
|
|
57
155
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
generated_methods.module_eval do
|
|
61
|
-
define_method(named) { read_attribute(named) }
|
|
62
|
-
define_method("#{named}?") do
|
|
63
|
-
value = read_attribute(named)
|
|
64
|
-
case value
|
|
65
|
-
when true then true
|
|
66
|
-
when false, nil then false
|
|
67
|
-
else
|
|
68
|
-
!value.nil?
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
define_method("#{named}=") { |value| write_attribute(named, value) }
|
|
72
|
-
define_method("#{named}_before_type_cast") { read_attribute_before_type_cast(named) }
|
|
73
|
-
end
|
|
156
|
+
Dynamoid::Fields::Declare.new(self, name, type, options).call
|
|
74
157
|
end
|
|
75
158
|
|
|
159
|
+
# Declare a table range key.
|
|
160
|
+
#
|
|
161
|
+
# class User
|
|
162
|
+
# include Dynamoid::Document
|
|
163
|
+
#
|
|
164
|
+
# range :last_name
|
|
165
|
+
# end
|
|
166
|
+
#
|
|
167
|
+
# By default a range key is a string. In order to use any other type it
|
|
168
|
+
# should be specified as a second argument:
|
|
169
|
+
#
|
|
170
|
+
# range :age, :integer
|
|
171
|
+
#
|
|
172
|
+
# Type options can be specified as well:
|
|
173
|
+
#
|
|
174
|
+
# range :date_of_birth, :date, store_as_string: true
|
|
175
|
+
#
|
|
176
|
+
# @param name [Symbol] a range key attribute name
|
|
177
|
+
# @param type [Symbol] a range key type (optional)
|
|
178
|
+
# @param options [Symbol] type options (optional)
|
|
76
179
|
def range(name, type = :string, options = {})
|
|
77
180
|
field(name, type, options)
|
|
78
181
|
self.range_key = name
|
|
79
182
|
end
|
|
80
183
|
|
|
184
|
+
# Set table level properties.
|
|
185
|
+
#
|
|
186
|
+
# There are some sensible defaults:
|
|
187
|
+
#
|
|
188
|
+
# * table name is based on a model class e.g. +users+ for +User+ class
|
|
189
|
+
# * hash key name - +id+ by default
|
|
190
|
+
# * hash key type - +string+ by default
|
|
191
|
+
# * generating timestamp fields +created_at+ and +updated_at+
|
|
192
|
+
# * billing mode and read/write capacity units
|
|
193
|
+
#
|
|
194
|
+
# The +table+ method can be used to override the defaults:
|
|
195
|
+
#
|
|
196
|
+
# class User
|
|
197
|
+
# include Dynamoid::Document
|
|
198
|
+
#
|
|
199
|
+
# table name: :customers, key: :uuid
|
|
200
|
+
# end
|
|
201
|
+
#
|
|
202
|
+
# The hash key field is declared by default and a type is a string. If
|
|
203
|
+
# another type is needed the field should be declared explicitly:
|
|
204
|
+
#
|
|
205
|
+
# class User
|
|
206
|
+
# include Dynamoid::Document
|
|
207
|
+
#
|
|
208
|
+
# field :id, :integer
|
|
209
|
+
# end
|
|
210
|
+
#
|
|
211
|
+
# @param options [Hash] options to override default table settings
|
|
212
|
+
# @option options [Symbol] :name name of a table
|
|
213
|
+
# @option options [Symbol] :key name of a hash key attribute
|
|
214
|
+
# @option options [Symbol] :inheritance_field name of an attribute used for STI
|
|
215
|
+
# @option options [Symbol] :capacity_mode table billing mode - either +provisioned+ or +on_demand+
|
|
216
|
+
# @option options [Integer] :write_capacity table write capacity units
|
|
217
|
+
# @option options [Integer] :read_capacity table read capacity units
|
|
218
|
+
# @option options [true|false] :timestamps whether generate +created_at+ and +updated_at+ fields or not
|
|
219
|
+
# @option options [Hash] :expires set up a table TTL and should have following structure +{ field: <attriubute name>, after: <seconds> }+
|
|
220
|
+
#
|
|
221
|
+
# @since 0.4.0
|
|
81
222
|
def table(options)
|
|
82
223
|
# a default 'id' column is created when Dynamoid::Document is included
|
|
83
224
|
unless attributes.key? hash_key
|
|
@@ -98,6 +239,12 @@ module Dynamoid #:nodoc:
|
|
|
98
239
|
end
|
|
99
240
|
end
|
|
100
241
|
|
|
242
|
+
# Remove a field declaration
|
|
243
|
+
#
|
|
244
|
+
# Removes a field from the list of fields and removes all te generated
|
|
245
|
+
# for a field methods.
|
|
246
|
+
#
|
|
247
|
+
# @param field [Symbol] a field name
|
|
101
248
|
def remove_field(field)
|
|
102
249
|
field = field.to_sym
|
|
103
250
|
attributes.delete(field) || raise('No such field')
|
|
@@ -114,12 +261,12 @@ module Dynamoid #:nodoc:
|
|
|
114
261
|
end
|
|
115
262
|
end
|
|
116
263
|
|
|
264
|
+
# @private
|
|
117
265
|
def timestamps_enabled?
|
|
118
266
|
options[:timestamps] || (options[:timestamps].nil? && Dynamoid::Config.timestamps)
|
|
119
267
|
end
|
|
120
268
|
|
|
121
|
-
private
|
|
122
|
-
|
|
269
|
+
# @private
|
|
123
270
|
def generated_methods
|
|
124
271
|
@generated_methods ||= begin
|
|
125
272
|
Module.new.tap do |mod|
|
|
@@ -133,15 +280,25 @@ module Dynamoid #:nodoc:
|
|
|
133
280
|
attr_accessor :attributes
|
|
134
281
|
alias raw_attributes attributes
|
|
135
282
|
|
|
136
|
-
# Write an attribute on the object.
|
|
283
|
+
# Write an attribute on the object.
|
|
284
|
+
#
|
|
285
|
+
# user.age = 20
|
|
286
|
+
# user.write_attribute(:age, 21)
|
|
287
|
+
# user.age # => 21
|
|
137
288
|
#
|
|
138
|
-
#
|
|
139
|
-
#
|
|
289
|
+
# Also marks the previous value as dirty.
|
|
290
|
+
#
|
|
291
|
+
# @param name [Symbol] the name of the field
|
|
292
|
+
# @param value [Object] the value to assign to that field
|
|
140
293
|
#
|
|
141
294
|
# @since 0.2.0
|
|
142
295
|
def write_attribute(name, value)
|
|
143
296
|
name = name.to_sym
|
|
144
297
|
|
|
298
|
+
unless attribute_is_present_on_model?(name)
|
|
299
|
+
raise Dynamoid::Errors::UnknownAttribute.new("Attribute #{name} is not part of the model")
|
|
300
|
+
end
|
|
301
|
+
|
|
145
302
|
if association = @associations[name]
|
|
146
303
|
association.reset
|
|
147
304
|
end
|
|
@@ -157,22 +314,40 @@ module Dynamoid #:nodoc:
|
|
|
157
314
|
|
|
158
315
|
# Read an attribute from an object.
|
|
159
316
|
#
|
|
160
|
-
#
|
|
317
|
+
# user.age = 20
|
|
318
|
+
# user.read_attribute(:age) # => 20
|
|
161
319
|
#
|
|
320
|
+
# @param name [Symbol] the name of the field
|
|
321
|
+
# @return attribute value
|
|
162
322
|
# @since 0.2.0
|
|
163
323
|
def read_attribute(name)
|
|
164
324
|
attributes[name.to_sym]
|
|
165
325
|
end
|
|
166
326
|
alias [] read_attribute
|
|
167
327
|
|
|
168
|
-
#
|
|
328
|
+
# Return attributes values before type casting.
|
|
329
|
+
#
|
|
330
|
+
# user = User.new
|
|
331
|
+
# user.age = '21'
|
|
332
|
+
# user.age # => 21
|
|
333
|
+
#
|
|
334
|
+
# user.attributes_before_type_cast # => { age: '21' }
|
|
335
|
+
#
|
|
336
|
+
# @return [Hash] original attribute values
|
|
169
337
|
def attributes_before_type_cast
|
|
170
338
|
@attributes_before_type_cast
|
|
171
339
|
end
|
|
172
340
|
|
|
173
|
-
#
|
|
341
|
+
# Return the value of the attribute identified by name before type casting.
|
|
174
342
|
#
|
|
175
|
-
#
|
|
343
|
+
# user = User.new
|
|
344
|
+
# user.age = '21'
|
|
345
|
+
# user.age # => 21
|
|
346
|
+
#
|
|
347
|
+
# user.read_attribute_before_type_cast(:age) # => '21'
|
|
348
|
+
#
|
|
349
|
+
# @param name [Symbol] attribute name
|
|
350
|
+
# @return original attribute value
|
|
176
351
|
def read_attribute_before_type_cast(name)
|
|
177
352
|
return nil unless name.respond_to?(:to_sym)
|
|
178
353
|
|
|
@@ -192,7 +367,8 @@ module Dynamoid #:nodoc:
|
|
|
192
367
|
#
|
|
193
368
|
# @since 0.2.0
|
|
194
369
|
def set_updated_at
|
|
195
|
-
|
|
370
|
+
# @_touch_record=false means explicit disabling
|
|
371
|
+
if self.class.timestamps_enabled? && !updated_at_changed? && @_touch_record != false
|
|
196
372
|
self.updated_at = DateTime.now.in_time_zone(Time.zone)
|
|
197
373
|
end
|
|
198
374
|
end
|
|
@@ -219,5 +395,10 @@ module Dynamoid #:nodoc:
|
|
|
219
395
|
send("#{type}=", self.class.name)
|
|
220
396
|
end
|
|
221
397
|
end
|
|
398
|
+
|
|
399
|
+
def attribute_is_present_on_model?(attribute_name)
|
|
400
|
+
setter = "#{attribute_name}=".to_sym
|
|
401
|
+
respond_to?(setter)
|
|
402
|
+
end
|
|
222
403
|
end
|
|
223
404
|
end
|