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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +111 -1
- data/README.md +580 -241
- data/lib/dynamoid.rb +2 -0
- data/lib/dynamoid/adapter.rb +15 -15
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +82 -102
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +108 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +29 -16
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +3 -2
- 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 +15 -6
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +15 -5
- 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 +5 -3
- data/lib/dynamoid/application_time_zone.rb +1 -0
- data/lib/dynamoid/associations.rb +182 -19
- data/lib/dynamoid/associations/association.rb +4 -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 +65 -22
- data/lib/dynamoid/associations/single_association.rb +28 -1
- data/lib/dynamoid/components.rb +8 -3
- data/lib/dynamoid/config.rb +16 -3
- 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 +2 -1
- data/lib/dynamoid/criteria/chain.rb +418 -46
- data/lib/dynamoid/criteria/ignored_conditions_detector.rb +3 -3
- data/lib/dynamoid/criteria/key_fields_detector.rb +109 -32
- 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 +239 -32
- data/lib/dynamoid/document.rb +130 -251
- data/lib/dynamoid/dumping.rb +9 -0
- data/lib/dynamoid/dynamodb_time_zone.rb +1 -0
- data/lib/dynamoid/fields.rb +246 -20
- data/lib/dynamoid/finders.rb +69 -32
- data/lib/dynamoid/identity_map.rb +6 -0
- data/lib/dynamoid/indexes.rb +76 -17
- data/lib/dynamoid/loadable.rb +31 -0
- data/lib/dynamoid/log/formatter.rb +26 -0
- data/lib/dynamoid/middleware/identity_map.rb +1 -0
- data/lib/dynamoid/persistence.rb +592 -122
- data/lib/dynamoid/persistence/import.rb +73 -0
- data/lib/dynamoid/persistence/save.rb +64 -0
- data/lib/dynamoid/persistence/update_fields.rb +63 -0
- data/lib/dynamoid/persistence/upsert.rb +60 -0
- 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 +2 -0
- data/lib/dynamoid/version.rb +1 -1
- metadata +49 -71
- 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 -41
- data/Appraisals +0 -28
- 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 -11
- data/gemfiles/rails_5_0.gemfile +0 -10
- data/gemfiles/rails_5_1.gemfile +0 -10
- data/gemfiles/rails_5_2.gemfile +0 -10
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/fields.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Dynamoid
|
3
|
+
module Dynamoid
|
4
4
|
# All fields on a Dynamoid::Document must be explicitly defined -- if you have fields in the database that are not
|
5
5
|
# specified with field, then they will be ignored.
|
6
6
|
module Fields
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
|
+
# @private
|
9
10
|
# Types allowed in indexes:
|
10
11
|
PERMITTED_KEY_TYPES = %i[
|
11
12
|
number
|
@@ -21,8 +22,11 @@ module Dynamoid #:nodoc:
|
|
21
22
|
class_attribute :range_key
|
22
23
|
|
23
24
|
self.attributes = {}
|
24
|
-
|
25
|
-
|
25
|
+
|
26
|
+
# Timestamp fields could be disabled later in `table` method call.
|
27
|
+
# So let's declare them here and remove them later if it will be necessary
|
28
|
+
field :created_at, :datetime if Dynamoid::Config.timestamps
|
29
|
+
field :updated_at, :datetime if Dynamoid::Config.timestamps
|
26
30
|
|
27
31
|
field :id # Default primary key
|
28
32
|
end
|
@@ -30,18 +34,101 @@ module Dynamoid #:nodoc:
|
|
30
34
|
module ClassMethods
|
31
35
|
# Specify a field for a document.
|
32
36
|
#
|
33
|
-
#
|
34
|
-
#
|
37
|
+
# class User
|
38
|
+
# include Dynamoid::Document
|
39
|
+
#
|
40
|
+
# field :last_name
|
41
|
+
# field :age, :integer
|
42
|
+
# field :last_sign_in, :datetime
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# Its type determines how it is coerced when read in and out of the
|
46
|
+
# datastore. You can specify +string+, +integer+, +number+, +set+, +array+,
|
47
|
+
# +map+, +datetime+, +date+, +serialized+, +raw+, +boolean+ and +binary+
|
35
48
|
# or specify a class that defines a serialization strategy.
|
36
49
|
#
|
50
|
+
# By default field type is +string+.
|
51
|
+
#
|
52
|
+
# Set can store elements of the same type only (it's a limitation of
|
53
|
+
# DynamoDB itself). If a set should store elements only some particular
|
54
|
+
# type +of+ option should be specified:
|
55
|
+
#
|
56
|
+
# field :hobbies, :set, of: :string
|
57
|
+
#
|
58
|
+
# Only +string+, +integer+, +number+, +date+, +datetime+ and +serialized+
|
59
|
+
# element types are supported.
|
60
|
+
#
|
61
|
+
# Element type can have own options - they should be specified in the
|
62
|
+
# form of +Hash+:
|
63
|
+
#
|
64
|
+
# field :hobbies, :set, of: { serialized: { serializer: JSON } }
|
65
|
+
#
|
66
|
+
# Array can contain element of different types but if supports the same
|
67
|
+
# +of+ option to convert all the provided elements to the declared type.
|
68
|
+
#
|
69
|
+
# field :rates, :array, of: :number
|
70
|
+
#
|
71
|
+
# By default +date+ and +datetime+ fields are stored as integer values.
|
72
|
+
# The format can be changed to string with option +store_as_string+:
|
73
|
+
#
|
74
|
+
# field :published_on, :datetime, store_as_string: true
|
75
|
+
#
|
76
|
+
# Boolean field by default is stored as a string +t+ or +f+. But DynamoDB
|
77
|
+
# supports boolean type natively. In order to switch to the native
|
78
|
+
# boolean type an option +store_as_native_boolean+ should be specified:
|
79
|
+
#
|
80
|
+
# field :active, :boolean, store_as_native_boolean: true
|
81
|
+
#
|
82
|
+
# If you specify the +serialized+ type a value will be serialized to
|
83
|
+
# string in Yaml format by default. Custom way to serialize value to
|
84
|
+
# string can be specified with +serializer+ option. Custom serializer
|
85
|
+
# should have +dump+ and +load+ methods.
|
86
|
+
#
|
37
87
|
# If you specify a class for field type, Dynamoid will serialize using
|
38
|
-
#
|
88
|
+
# +dynamoid_dump+ method and load using +dynamoid_load+ method.
|
89
|
+
#
|
90
|
+
# Default field type is +string+.
|
91
|
+
#
|
92
|
+
# A field can have a default value. It's assigned at initializing a model
|
93
|
+
# if no value is specified:
|
39
94
|
#
|
40
|
-
#
|
95
|
+
# field :age, :integer, default: 1
|
41
96
|
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
97
|
+
# If a defautl value should be recalculated every time it can be
|
98
|
+
# specified as a callable object (it should implement a +call+ method
|
99
|
+
# e.g. +Proc+ object):
|
100
|
+
#
|
101
|
+
# field :date_of_birth, :date, default: -> { Date.today }
|
102
|
+
#
|
103
|
+
# For every field Dynamoid creates several methods:
|
104
|
+
#
|
105
|
+
# * getter
|
106
|
+
# * setter
|
107
|
+
# * predicate +<name>?+ to check whether a value set
|
108
|
+
# * +<name>_before_type_cast?+ to get an original field value before it was type casted
|
109
|
+
#
|
110
|
+
# It works in the following way:
|
111
|
+
#
|
112
|
+
# class User
|
113
|
+
# include Dynamoid::Document
|
114
|
+
#
|
115
|
+
# field :age, :integer
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# user = User.new
|
119
|
+
# user.age # => nil
|
120
|
+
# user.age? # => false
|
121
|
+
#
|
122
|
+
# user.age = 20
|
123
|
+
# user.age? # => true
|
124
|
+
#
|
125
|
+
# user.age = '21'
|
126
|
+
# user.age # => 21 - integer
|
127
|
+
# user.age_before_type_cast # => '21' - string
|
128
|
+
#
|
129
|
+
# @param name [Symbol] name of the field
|
130
|
+
# @param type [Symbol] type of the field (optional)
|
131
|
+
# @param options [Hash] any additional options for the field type (optional)
|
45
132
|
#
|
46
133
|
# @since 0.2.0
|
47
134
|
def field(name, type = :string, options = {})
|
@@ -52,6 +139,14 @@ module Dynamoid #:nodoc:
|
|
52
139
|
end
|
53
140
|
self.attributes = attributes.merge(name => { type: type }.merge(options))
|
54
141
|
|
142
|
+
# should be called before `define_attribute_methods` method because it defines a getter itself
|
143
|
+
warn_about_method_overriding(name, name)
|
144
|
+
warn_about_method_overriding("#{named}=", name)
|
145
|
+
warn_about_method_overriding("#{named}?", name)
|
146
|
+
warn_about_method_overriding("#{named}_before_type_cast?", name)
|
147
|
+
|
148
|
+
define_attribute_method(name) # Dirty API
|
149
|
+
|
55
150
|
generated_methods.module_eval do
|
56
151
|
define_method(named) { read_attribute(named) }
|
57
152
|
define_method("#{named}?") do
|
@@ -68,23 +163,103 @@ module Dynamoid #:nodoc:
|
|
68
163
|
end
|
69
164
|
end
|
70
165
|
|
166
|
+
# Declare a table range key.
|
167
|
+
#
|
168
|
+
# class User
|
169
|
+
# include Dynamoid::Document
|
170
|
+
#
|
171
|
+
# range :last_name
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# By default a range key is a string. In order to use any other type it
|
175
|
+
# should be specified as a second argument:
|
176
|
+
#
|
177
|
+
# range :age, :integer
|
178
|
+
#
|
179
|
+
# Type options can be specified as well:
|
180
|
+
#
|
181
|
+
# range :date_of_birth, :date, store_as_string: true
|
182
|
+
#
|
183
|
+
# @param name [Symbol] a range key attribute name
|
184
|
+
# @param type [Symbol] a range key type (optional)
|
185
|
+
# @param options [Symbol] type options (optional)
|
71
186
|
def range(name, type = :string, options = {})
|
72
187
|
field(name, type, options)
|
73
188
|
self.range_key = name
|
74
189
|
end
|
75
190
|
|
76
|
-
|
191
|
+
# Set table level properties.
|
192
|
+
#
|
193
|
+
# There are some sensible defaults:
|
194
|
+
#
|
195
|
+
# * table name is based on a model class e.g. +users+ for +User+ class
|
196
|
+
# * hash key name - +id+ by default
|
197
|
+
# * hash key type - +string+ by default
|
198
|
+
# * generating timestamp fields +created_at+ and +updated_at+
|
199
|
+
# * billing mode and read/write capacity units
|
200
|
+
#
|
201
|
+
# The +table+ method can be used to override the defaults:
|
202
|
+
#
|
203
|
+
# class User
|
204
|
+
# include Dynamoid::Document
|
205
|
+
#
|
206
|
+
# table name: :customers, key: :uuid
|
207
|
+
# end
|
208
|
+
#
|
209
|
+
# The hash key field is declared by default and a type is a string. If
|
210
|
+
# another type is needed the field should be declared explicitly:
|
211
|
+
#
|
212
|
+
# class User
|
213
|
+
# include Dynamoid::Document
|
214
|
+
#
|
215
|
+
# field :id, :integer
|
216
|
+
# end
|
217
|
+
#
|
218
|
+
# @param options [Hash] options to override default table settings
|
219
|
+
# @option options [Symbol] :name name of a table
|
220
|
+
# @option options [Symbol] :key name of a hash key attribute
|
221
|
+
# @option options [Symbol] :inheritance_field name of an attribute used for STI
|
222
|
+
# @option options [Symbol] :capacity_mode table billing mode - either +provisioned+ or +on_demand+
|
223
|
+
# @option options [Integer] :write_capacity table write capacity units
|
224
|
+
# @option options [Integer] :read_capacity table read capacity units
|
225
|
+
# @option options [true|false] :timestamps whether generate +created_at+ and +updated_at+ fields or not
|
226
|
+
# @option options [Hash] :expires set up a table TTL and should have following structure +{ field: <attriubute name>, after: <seconds> }+
|
227
|
+
#
|
228
|
+
# @since 0.4.0
|
229
|
+
def table(options)
|
77
230
|
# a default 'id' column is created when Dynamoid::Document is included
|
78
231
|
unless attributes.key? hash_key
|
79
232
|
remove_field :id
|
80
233
|
field(hash_key)
|
81
234
|
end
|
235
|
+
|
236
|
+
if options[:timestamps] && !Dynamoid::Config.timestamps
|
237
|
+
# Timestamp fields weren't declared in `included` hook because they
|
238
|
+
# are disabled globaly
|
239
|
+
field :created_at, :datetime
|
240
|
+
field :updated_at, :datetime
|
241
|
+
elsif options[:timestamps] == false && Dynamoid::Config.timestamps
|
242
|
+
# Timestamp fields were declared in `included` hook but they are
|
243
|
+
# disabled for a table
|
244
|
+
remove_field :created_at
|
245
|
+
remove_field :updated_at
|
246
|
+
end
|
82
247
|
end
|
83
248
|
|
249
|
+
# Remove a field declaration
|
250
|
+
#
|
251
|
+
# Removes a field from the list of fields and removes all te generated
|
252
|
+
# for a field methods.
|
253
|
+
#
|
254
|
+
# @param field [Symbol] a field name
|
84
255
|
def remove_field(field)
|
85
256
|
field = field.to_sym
|
86
257
|
attributes.delete(field) || raise('No such field')
|
87
258
|
|
259
|
+
# Dirty API
|
260
|
+
undefine_attribute_methods
|
261
|
+
define_attribute_methods attributes.keys
|
262
|
+
|
88
263
|
generated_methods.module_eval do
|
89
264
|
remove_method field
|
90
265
|
remove_method :"#{field}="
|
@@ -93,6 +268,11 @@ module Dynamoid #:nodoc:
|
|
93
268
|
end
|
94
269
|
end
|
95
270
|
|
271
|
+
# @private
|
272
|
+
def timestamps_enabled?
|
273
|
+
options[:timestamps] || (options[:timestamps].nil? && Dynamoid::Config.timestamps)
|
274
|
+
end
|
275
|
+
|
96
276
|
private
|
97
277
|
|
98
278
|
def generated_methods
|
@@ -102,16 +282,28 @@ module Dynamoid #:nodoc:
|
|
102
282
|
end
|
103
283
|
end
|
104
284
|
end
|
285
|
+
|
286
|
+
def warn_about_method_overriding(method_name, field_name)
|
287
|
+
if instance_methods.include?(method_name.to_sym)
|
288
|
+
Dynamoid.logger.warn("Method #{method_name} generated for the field #{field_name} overrides already existing method")
|
289
|
+
end
|
290
|
+
end
|
105
291
|
end
|
106
292
|
|
107
293
|
# You can access the attributes of an object directly on its attributes method, which is by default an empty hash.
|
108
294
|
attr_accessor :attributes
|
109
295
|
alias raw_attributes attributes
|
110
296
|
|
111
|
-
# Write an attribute on the object.
|
297
|
+
# Write an attribute on the object.
|
298
|
+
#
|
299
|
+
# user.age = 20
|
300
|
+
# user.write_attribute(:age, 21)
|
301
|
+
# user.age # => 21
|
302
|
+
#
|
303
|
+
# Also marks the previous value as dirty.
|
112
304
|
#
|
113
|
-
# @param [Symbol]
|
114
|
-
# @param [Object]
|
305
|
+
# @param name [Symbol] the name of the field
|
306
|
+
# @param value [Object] the value to assign to that field
|
115
307
|
#
|
116
308
|
# @since 0.2.0
|
117
309
|
def write_attribute(name, value)
|
@@ -121,6 +313,8 @@ module Dynamoid #:nodoc:
|
|
121
313
|
association.reset
|
122
314
|
end
|
123
315
|
|
316
|
+
attribute_will_change!(name) # Dirty API
|
317
|
+
|
124
318
|
@attributes_before_type_cast[name] = value
|
125
319
|
|
126
320
|
value_casted = TypeCasting.cast_field(value, self.class.attributes[name])
|
@@ -130,22 +324,40 @@ module Dynamoid #:nodoc:
|
|
130
324
|
|
131
325
|
# Read an attribute from an object.
|
132
326
|
#
|
133
|
-
#
|
327
|
+
# user.age = 20
|
328
|
+
# user.read_attribute(:age) # => 20
|
134
329
|
#
|
330
|
+
# @param name [Symbol] the name of the field
|
331
|
+
# @return attribute value
|
135
332
|
# @since 0.2.0
|
136
333
|
def read_attribute(name)
|
137
334
|
attributes[name.to_sym]
|
138
335
|
end
|
139
336
|
alias [] read_attribute
|
140
337
|
|
141
|
-
#
|
338
|
+
# Return attributes values before type casting.
|
339
|
+
#
|
340
|
+
# user = User.new
|
341
|
+
# user.age = '21'
|
342
|
+
# user.age # => 21
|
343
|
+
#
|
344
|
+
# user.attributes_before_type_cast # => { age: '21' }
|
345
|
+
#
|
346
|
+
# @return [Hash] original attribute values
|
142
347
|
def attributes_before_type_cast
|
143
348
|
@attributes_before_type_cast
|
144
349
|
end
|
145
350
|
|
146
|
-
#
|
351
|
+
# Return the value of the attribute identified by name before type casting.
|
147
352
|
#
|
148
|
-
#
|
353
|
+
# user = User.new
|
354
|
+
# user.age = '21'
|
355
|
+
# user.age # => 21
|
356
|
+
#
|
357
|
+
# user.read_attribute_before_type_cast(:age) # => '21'
|
358
|
+
#
|
359
|
+
# @param name [Symbol] attribute name
|
360
|
+
# @return original attribute value
|
149
361
|
def read_attribute_before_type_cast(name)
|
150
362
|
return nil unless name.respond_to?(:to_sym)
|
151
363
|
|
@@ -158,18 +370,32 @@ module Dynamoid #:nodoc:
|
|
158
370
|
#
|
159
371
|
# @since 0.2.0
|
160
372
|
def set_created_at
|
161
|
-
self.created_at ||= DateTime.now.in_time_zone(Time.zone) if
|
373
|
+
self.created_at ||= DateTime.now.in_time_zone(Time.zone) if self.class.timestamps_enabled?
|
162
374
|
end
|
163
375
|
|
164
376
|
# Automatically called during the save callback to set the updated_at time.
|
165
377
|
#
|
166
378
|
# @since 0.2.0
|
167
379
|
def set_updated_at
|
168
|
-
|
380
|
+
# @_touch_record=false means explicit disabling
|
381
|
+
if self.class.timestamps_enabled? && !updated_at_changed? && @_touch_record != false
|
169
382
|
self.updated_at = DateTime.now.in_time_zone(Time.zone)
|
170
383
|
end
|
171
384
|
end
|
172
385
|
|
386
|
+
def set_expires_field
|
387
|
+
options = self.class.options[:expires]
|
388
|
+
|
389
|
+
if options.present?
|
390
|
+
name = options[:field]
|
391
|
+
seconds = options[:after]
|
392
|
+
|
393
|
+
if self[name].blank?
|
394
|
+
send("#{name}=", Time.now.to_i + seconds)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
173
399
|
def set_inheritance_field
|
174
400
|
# actually it does only following logic:
|
175
401
|
# self.type ||= self.class.name if self.class.attributes[:type]
|
data/lib/dynamoid/finders.rb
CHANGED
@@ -6,6 +6,7 @@ module Dynamoid
|
|
6
6
|
module Finders
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
|
+
# @private
|
9
10
|
RANGE_MAP = {
|
10
11
|
'gt' => :range_greater_than,
|
11
12
|
'lt' => :range_less_than,
|
@@ -19,9 +20,25 @@ module Dynamoid
|
|
19
20
|
module ClassMethods
|
20
21
|
# Find one or many objects, specified by one id or an array of ids.
|
21
22
|
#
|
22
|
-
#
|
23
|
-
#
|
23
|
+
# By default it raises +RecordNotFound+ exception if at least one model
|
24
|
+
# isn't found. This behavior can be changed with +raise_error+ option. If
|
25
|
+
# specified +raise_error: false+ option then +find+ will not raise the
|
26
|
+
# exception.
|
24
27
|
#
|
28
|
+
# When a document schema includes range key it always should be specified
|
29
|
+
# in +find+ method call. In case it's missing +MissingRangeKey+ exception
|
30
|
+
# will be raised.
|
31
|
+
#
|
32
|
+
# Please note that +find+ doesn't preserve order of models in result when
|
33
|
+
# passes multiple ids.
|
34
|
+
#
|
35
|
+
# Supported following options:
|
36
|
+
# * +consistent_read+
|
37
|
+
# * +range_key+
|
38
|
+
# * +raise_error+
|
39
|
+
#
|
40
|
+
# @param ids [String|Array] hash key or an array of hash keys
|
41
|
+
# @param options [Hash]
|
25
42
|
# @return [Dynamoid::Document] one object or an array of objects, depending on whether the input was an array or not
|
26
43
|
#
|
27
44
|
# @example Find by partition key
|
@@ -45,36 +62,45 @@ module Dynamoid
|
|
45
62
|
# @since 0.2.0
|
46
63
|
def find(*ids, **options)
|
47
64
|
if ids.size == 1 && !ids[0].is_a?(Array)
|
48
|
-
_find_by_id(ids[0], options.
|
65
|
+
_find_by_id(ids[0], options.reverse_merge(raise_error: true))
|
49
66
|
else
|
50
|
-
_find_all(ids.flatten(1), options.
|
67
|
+
_find_all(ids.flatten(1), options.reverse_merge(raise_error: true))
|
51
68
|
end
|
52
69
|
end
|
53
70
|
|
54
|
-
#
|
71
|
+
# Find several models at once.
|
72
|
+
#
|
73
|
+
# Returns objects found by the given array of ids, either hash keys, or
|
74
|
+
# hash/range key combinations using +BatchGetItem+.
|
75
|
+
#
|
55
76
|
# Returns empty array if no results found.
|
56
77
|
#
|
57
|
-
# Uses backoff specified by
|
78
|
+
# Uses backoff specified by +Dynamoid::Config.backoff+ config option.
|
58
79
|
#
|
59
|
-
# @param [Array
|
60
|
-
# @param [Hash]
|
80
|
+
# @param ids [Array] array of primary keys
|
81
|
+
# @param options [Hash]
|
82
|
+
# @option options [true|false] :consistent_read
|
83
|
+
# @option options [true|false] :raise_error
|
61
84
|
#
|
62
85
|
# @example
|
63
|
-
#
|
86
|
+
# # Find all the user with hash key
|
64
87
|
# User.find_all(['1', '2', '3'])
|
65
88
|
#
|
66
|
-
#
|
67
|
-
# Tweet.find_all([['1', 'red'], ['1', 'green']], :
|
89
|
+
# # Find all the tweets using hash key and range key with consistent read
|
90
|
+
# Tweet.find_all([['1', 'red'], ['1', 'green']], consistent_read: true)
|
68
91
|
def find_all(ids, options = {})
|
69
92
|
ActiveSupport::Deprecation.warn('[Dynamoid] .find_all is deprecated! Call .find instead of')
|
70
93
|
|
71
94
|
_find_all(ids, options)
|
72
95
|
end
|
73
96
|
|
74
|
-
# Find one object directly by
|
75
|
-
#
|
76
|
-
# @param [String] id the id of the object to find
|
97
|
+
# Find one object directly by primary key.
|
77
98
|
#
|
99
|
+
# @param id [String] the id of the object to find
|
100
|
+
# @param options [Hash]
|
101
|
+
# @option options [true|false] :consistent_read
|
102
|
+
# @option options [true|false] :raise_error
|
103
|
+
# @option options [Scalar value] :range_key
|
78
104
|
# @return [Dynamoid::Document] the found object, or nil if nothing was found
|
79
105
|
#
|
80
106
|
# @example Find by partition key
|
@@ -90,7 +116,10 @@ module Dynamoid
|
|
90
116
|
_find_by_id(id, options)
|
91
117
|
end
|
92
118
|
|
119
|
+
# @private
|
93
120
|
def _find_all(ids, options = {})
|
121
|
+
raise Errors::MissingRangeKey if range_key && ids.any? { |pk, sk| sk.nil? }
|
122
|
+
|
94
123
|
if range_key
|
95
124
|
ids = ids.map do |pk, sk|
|
96
125
|
sk_casted = TypeCasting.cast_field(sk, attributes[range_key])
|
@@ -131,7 +160,10 @@ module Dynamoid
|
|
131
160
|
end
|
132
161
|
end
|
133
162
|
|
163
|
+
# @private
|
134
164
|
def _find_by_id(id, options = {})
|
165
|
+
raise Errors::MissingRangeKey if range_key && options[:range_key].nil?
|
166
|
+
|
135
167
|
if range_key
|
136
168
|
key = options[:range_key]
|
137
169
|
key_casted = TypeCasting.cast_field(key, attributes[range_key])
|
@@ -149,10 +181,10 @@ module Dynamoid
|
|
149
181
|
end
|
150
182
|
end
|
151
183
|
|
152
|
-
# Find one object directly by hash and range keys
|
184
|
+
# Find one object directly by hash and range keys.
|
153
185
|
#
|
154
|
-
# @param [
|
155
|
-
# @param [
|
186
|
+
# @param hash_key [Scalar value] hash key of the object to find
|
187
|
+
# @param range_key [Scalar value] range key of the object to find
|
156
188
|
#
|
157
189
|
def find_by_composite_key(hash_key, range_key, options = {})
|
158
190
|
ActiveSupport::Deprecation.warn('[Dynamoid] .find_by_composite_key is deprecated! Call .find instead of')
|
@@ -169,6 +201,7 @@ module Dynamoid
|
|
169
201
|
# range :level, :integer
|
170
202
|
# table :key => :chamber_type
|
171
203
|
# end
|
204
|
+
#
|
172
205
|
# ChamberType.find_all_by_composite_key('DustVault', range_greater_than: 1)
|
173
206
|
#
|
174
207
|
# @param [String] hash_key of the objects to find
|
@@ -183,7 +216,7 @@ module Dynamoid
|
|
183
216
|
def find_all_by_composite_key(hash_key, options = {})
|
184
217
|
ActiveSupport::Deprecation.warn('[Dynamoid] .find_all_composite_key is deprecated! Call .where instead of')
|
185
218
|
|
186
|
-
Dynamoid.adapter.query(table_name, options.merge(hash_value: hash_key)).flat_map{ |i| i }.collect do |item|
|
219
|
+
Dynamoid.adapter.query(table_name, options.merge(hash_value: hash_key)).flat_map { |i| i }.collect do |item|
|
187
220
|
from_database(item)
|
188
221
|
end
|
189
222
|
end
|
@@ -193,20 +226,22 @@ module Dynamoid
|
|
193
226
|
# @example
|
194
227
|
# class User
|
195
228
|
# include Dynamoid::Document
|
196
|
-
#
|
197
|
-
# field :age, :integer
|
198
|
-
# field :gender, :string
|
199
|
-
# field :rank :number
|
229
|
+
#
|
200
230
|
# table :key => :email
|
201
|
-
# global_secondary_index :
|
231
|
+
# global_secondary_index hash_key: :age, range_key: :rank
|
232
|
+
#
|
233
|
+
# field :email, :string
|
234
|
+
# field :age, :integer
|
235
|
+
# field :gender, :string
|
236
|
+
# field :rank :number
|
202
237
|
# end
|
238
|
+
#
|
203
239
|
# # NOTE: the first param and the second param are both hashes,
|
204
240
|
# # so curly braces must be used on first hash param if sending both params
|
205
|
-
# User.find_all_by_secondary_index({:
|
241
|
+
# User.find_all_by_secondary_index({ age: 5 }, range: { "rank.lte": 10 })
|
206
242
|
#
|
207
|
-
# @param [Hash]
|
208
|
-
# @param [Hash]
|
209
|
-
# @param [Hash] options - query filter, projected keys, scan_index_forward etc
|
243
|
+
# @param hash [Hash] conditions for the hash key e.g. +{ age: 5 }+
|
244
|
+
# @param options [Hash] conditions on range key e.g. +{ "rank.lte": 10 }, query filter, projected keys, scan_index_forward etc.
|
210
245
|
# @return [Array] an array of all matching items
|
211
246
|
def find_all_by_secondary_index(hash, options = {})
|
212
247
|
ActiveSupport::Deprecation.warn('[Dynamoid] .find_all_by_secondary_index is deprecated! Call .where instead of')
|
@@ -240,12 +275,13 @@ module Dynamoid
|
|
240
275
|
opts[range_op_mapped] = range_key_value
|
241
276
|
end
|
242
277
|
dynamo_options = opts.merge(options.reject { |key, _| key == :range })
|
243
|
-
Dynamoid.adapter.query(table_name, dynamo_options).flat_map{ |i| i }.map do |item|
|
278
|
+
Dynamoid.adapter.query(table_name, dynamo_options).flat_map { |i| i }.map do |item|
|
244
279
|
from_database(item)
|
245
280
|
end
|
246
281
|
end
|
247
282
|
|
248
|
-
# Find using exciting method_missing finders attributes. Uses criteria
|
283
|
+
# Find using exciting method_missing finders attributes. Uses criteria
|
284
|
+
# chains under the hood to accomplish this neatness.
|
249
285
|
#
|
250
286
|
# @example find a user by a first name
|
251
287
|
# User.find_by_first_name('Josh')
|
@@ -253,8 +289,9 @@ module Dynamoid
|
|
253
289
|
# @example find all users by first and last name
|
254
290
|
# User.find_all_by_first_name_and_last_name('Josh', 'Symonds')
|
255
291
|
#
|
256
|
-
# @return [Dynamoid::Document
|
292
|
+
# @return [Dynamoid::Document|Array] the found object, or an array of found objects if all was somewhere in the method
|
257
293
|
#
|
294
|
+
# @private
|
258
295
|
# @since 0.2.0
|
259
296
|
def method_missing(method, *args)
|
260
297
|
if method =~ /find/
|
@@ -267,9 +304,9 @@ module Dynamoid
|
|
267
304
|
chain = chain.where({}.tap { |h| attributes.each_with_index { |attr, index| h[attr.to_sym] = args[index] } })
|
268
305
|
|
269
306
|
if finder =~ /all/
|
270
|
-
|
307
|
+
chain.all
|
271
308
|
else
|
272
|
-
|
309
|
+
chain.first
|
273
310
|
end
|
274
311
|
else
|
275
312
|
super
|