dynamoid 3.5.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -4
  3. data/README.md +24 -18
  4. data/lib/dynamoid.rb +1 -0
  5. data/lib/dynamoid/adapter.rb +7 -4
  6. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +14 -11
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +1 -0
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +8 -7
  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 +1 -0
  11. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +1 -0
  12. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/start_key.rb +1 -0
  13. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +1 -0
  14. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +1 -0
  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 +1 -0
  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 +1 -0
  27. data/lib/dynamoid/config.rb +3 -2
  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 +1 -0
  32. data/lib/dynamoid/criteria/chain.rb +353 -33
  33. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +1 -0
  34. data/lib/dynamoid/criteria/key_fields_detector.rb +10 -1
  35. data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +1 -0
  36. data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +1 -0
  37. data/lib/dynamoid/dirty.rb +71 -16
  38. data/lib/dynamoid/document.rb +123 -42
  39. data/lib/dynamoid/dumping.rb +9 -0
  40. data/lib/dynamoid/dynamodb_time_zone.rb +1 -0
  41. data/lib/dynamoid/fields.rb +189 -16
  42. data/lib/dynamoid/finders.rb +65 -28
  43. data/lib/dynamoid/identity_map.rb +6 -0
  44. data/lib/dynamoid/indexes.rb +74 -15
  45. data/lib/dynamoid/log/formatter.rb +26 -0
  46. data/lib/dynamoid/middleware/identity_map.rb +1 -0
  47. data/lib/dynamoid/persistence.rb +452 -106
  48. data/lib/dynamoid/persistence/import.rb +1 -0
  49. data/lib/dynamoid/persistence/save.rb +1 -0
  50. data/lib/dynamoid/persistence/update_fields.rb +1 -0
  51. data/lib/dynamoid/persistence/upsert.rb +1 -0
  52. data/lib/dynamoid/primary_key_type_mapping.rb +1 -0
  53. data/lib/dynamoid/railtie.rb +1 -0
  54. data/lib/dynamoid/tasks/database.rb +1 -0
  55. data/lib/dynamoid/type_casting.rb +12 -0
  56. data/lib/dynamoid/undumping.rb +8 -0
  57. data/lib/dynamoid/validations.rb +2 -0
  58. data/lib/dynamoid/version.rb +1 -1
  59. metadata +7 -19
@@ -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
@@ -33,18 +34,101 @@ module Dynamoid #:nodoc:
33
34
  module ClassMethods
34
35
  # Specify a field for a document.
35
36
  #
36
- # Its type determines how it is coerced when read in and out of the datastore.
37
- # 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+
38
48
  # or specify a class that defines a serialization strategy.
39
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
+ #
40
87
  # If you specify a class for field type, Dynamoid will serialize using
41
- # `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:
94
+ #
95
+ # field :age, :integer, default: 1
96
+ #
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
42
124
  #
43
- # Default field type is :string.
125
+ # user.age = '21'
126
+ # user.age # => 21 - integer
127
+ # user.age_before_type_cast # => '21' - string
44
128
  #
45
- # @param [Symbol] name the name of the field
46
- # @param [Symbol] type the type of the field (refer to method description for details)
47
- # @param [Hash] options any additional options for the field
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)
48
132
  #
49
133
  # @since 0.2.0
50
134
  def field(name, type = :string, options = {})
@@ -79,11 +163,69 @@ module Dynamoid #:nodoc:
79
163
  end
80
164
  end
81
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)
82
186
  def range(name, type = :string, options = {})
83
187
  field(name, type, options)
84
188
  self.range_key = name
85
189
  end
86
190
 
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
87
229
  def table(options)
88
230
  # a default 'id' column is created when Dynamoid::Document is included
89
231
  unless attributes.key? hash_key
@@ -104,6 +246,12 @@ module Dynamoid #:nodoc:
104
246
  end
105
247
  end
106
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
107
255
  def remove_field(field)
108
256
  field = field.to_sym
109
257
  attributes.delete(field) || raise('No such field')
@@ -120,6 +268,7 @@ module Dynamoid #:nodoc:
120
268
  end
121
269
  end
122
270
 
271
+ # @private
123
272
  def timestamps_enabled?
124
273
  options[:timestamps] || (options[:timestamps].nil? && Dynamoid::Config.timestamps)
125
274
  end
@@ -135,7 +284,7 @@ module Dynamoid #:nodoc:
135
284
  end
136
285
 
137
286
  def warn_about_method_overriding(method_name, field_name)
138
- if self.instance_methods.include?(method_name.to_sym)
287
+ if instance_methods.include?(method_name.to_sym)
139
288
  Dynamoid.logger.warn("Method #{method_name} generated for the field #{field_name} overrides already existing method")
140
289
  end
141
290
  end
@@ -145,10 +294,16 @@ module Dynamoid #:nodoc:
145
294
  attr_accessor :attributes
146
295
  alias raw_attributes attributes
147
296
 
148
- # 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
149
302
  #
150
- # @param [Symbol] name the name of the field
151
- # @param [Object] value the value to assign to that field
303
+ # Also marks the previous value as dirty.
304
+ #
305
+ # @param name [Symbol] the name of the field
306
+ # @param value [Object] the value to assign to that field
152
307
  #
153
308
  # @since 0.2.0
154
309
  def write_attribute(name, value)
@@ -169,22 +324,40 @@ module Dynamoid #:nodoc:
169
324
 
170
325
  # Read an attribute from an object.
171
326
  #
172
- # @param [Symbol] name the name of the field
327
+ # user.age = 20
328
+ # user.read_attribute(:age) # => 20
173
329
  #
330
+ # @param name [Symbol] the name of the field
331
+ # @return attribute value
174
332
  # @since 0.2.0
175
333
  def read_attribute(name)
176
334
  attributes[name.to_sym]
177
335
  end
178
336
  alias [] read_attribute
179
337
 
180
- # 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
181
347
  def attributes_before_type_cast
182
348
  @attributes_before_type_cast
183
349
  end
184
350
 
185
- # Returns the value of the attribute identified by name before typecasting
351
+ # Return the value of the attribute identified by name before type casting.
352
+ #
353
+ # user = User.new
354
+ # user.age = '21'
355
+ # user.age # => 21
356
+ #
357
+ # user.read_attribute_before_type_cast(:age) # => '21'
186
358
  #
187
- # @param [Symbol] attribute name
359
+ # @param name [Symbol] attribute name
360
+ # @return original attribute value
188
361
  def read_attribute_before_type_cast(name)
189
362
  return nil unless name.respond_to?(:to_sym)
190
363
 
@@ -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
@@ -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')
@@ -245,7 +280,8 @@ module Dynamoid
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/