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
@@ -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)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
+ # @private
4
5
  module DynamodbTimeZone
5
6
  def self.in_time_zone(value)
6
7
  case Dynamoid::Config.dynamodb_timezone
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Dynamoid #:nodoc:
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
- field :created_at, :datetime
25
- field :updated_at, :datetime
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
- # Its type determines how it is coerced when read in and out of the datastore.
34
- # You can specify :integer, :number, :set, :array, :datetime, :date and :serialized,
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
- # `dynamoid_dump` or `dump` methods, and load using `dynamoid_load` or `load` methods.
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
- # Default field type is :string.
95
+ # field :age, :integer, default: 1
41
96
  #
42
- # @param [Symbol] name the name of the field
43
- # @param [Symbol] type the type of the field (refer to method description for details)
44
- # @param [Hash] options any additional options for the field
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
- def table(_options)
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. Also marks the previous value as dirty.
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] name the name of the field
114
- # @param [Object] value the value to assign to that field
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
- # @param [Symbol] name the name of the field
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
- # Returns a hash of attributes before typecasting
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
- # Returns the value of the attribute identified by name before typecasting
351
+ # Return the value of the attribute identified by name before type casting.
147
352
  #
148
- # @param [Symbol] attribute name
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 Dynamoid::Config.timestamps
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
- if Dynamoid::Config.timestamps && !updated_at_changed?
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]
@@ -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
- # @param [Array/String] *id an array of ids or one single id
23
- # @param [Hash] options
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.merge(raise_error: true))
65
+ _find_by_id(ids[0], options.reverse_merge(raise_error: true))
49
66
  else
50
- _find_all(ids.flatten(1), options.merge(raise_error: true))
67
+ _find_all(ids.flatten(1), options.reverse_merge(raise_error: true))
51
68
  end
52
69
  end
53
70
 
54
- # Return objects found by the given array of ids, either hash keys, or hash/range key combinations using BatchGetItem.
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 `Dynamoid::Config.backoff` config option
78
+ # Uses backoff specified by +Dynamoid::Config.backoff+ config option.
58
79
  #
59
- # @param [Array<ID>] ids
60
- # @param [Hash] options: Passed to the underlying query.
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
- # find all the user with hash key
86
+ # # Find all the user with hash key
64
87
  # User.find_all(['1', '2', '3'])
65
88
  #
66
- # find all the tweets using hash key and range key with consistent read
67
- # Tweet.find_all([['1', 'red'], ['1', 'green']], :consistent_read => true)
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 id.
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 [String] hash_key of the object to find
155
- # @param [String/Number] range_key of the object to find
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
- # field :email, :string
197
- # field :age, :integer
198
- # field :gender, :string
199
- # field :rank :number
229
+ #
200
230
  # table :key => :email
201
- # global_secondary_index :hash_key => :age, :range_key => :rank
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({:age => 5}, :range => {"rank.lte" => 10})
241
+ # User.find_all_by_secondary_index({ age: 5 }, range: { "rank.lte": 10 })
206
242
  #
207
- # @param [Hash] eg: {:age => 5}
208
- # @param [Hash] eg: {"rank.lte" => 10}
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 chains under the hood to accomplish this neatness.
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/Array] the found object, or an array of found objects if all was somewhere in the method
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
- return chain.all
307
+ chain.all
271
308
  else
272
- return chain.first
309
+ chain.first
273
310
  end
274
311
  else
275
312
  super