Module: Dynamoid::Finders::ClassMethods

Defined in:
lib/dynamoid/finders.rb

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Dynamoid::Document|Array

Find using exciting method_missing finders attributes. Uses criteria chains under the hood to accomplish this neatness.

Examples:

find a user by a first name

User.find_by_first_name('Josh')

find all users by first and last name

User.find_all_by_first_name_and_last_name('Josh', 'Symonds')

Returns:

  • (Dynamoid::Document|Array)

    the found object, or an array of found objects if all was somewhere in the method

Since:

  • 0.2.0



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/dynamoid/finders.rb', line 291

def method_missing(method, *args)
  # Cannot use Symbol#start_with? because it was introduced in Ruby 2.7, but we support Ruby >= 2.3
  if method.to_s.start_with?('find')
    ActiveSupport::Deprecation.warn("[Dynamoid] .#{method} is deprecated! Call .where instead of")

    finder = method.to_s.split('_by_').first
    attributes = method.to_s.split('_by_').last.split('_and_')

    chain = Dynamoid::Criteria::Chain.new(self)
    chain = chain.where({}.tap { |h| attributes.each_with_index { |attr, index| h[attr.to_sym] = args[index] } })

    if finder.include?('all')
      chain.all
    else
      chain.first
    end
  else
    super
  end
end

Instance Method Details

#_find_all(ids, options = {}) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/dynamoid/finders.rb', line 109

def _find_all(ids, options = {})
  raise Errors::MissingRangeKey if range_key && ids.any? { |_pk, sk| sk.nil? }

  if range_key
    ids = ids.map do |pk, sk|
      sk_casted = TypeCasting.cast_field(sk, attributes[range_key])
      sk_dumped = Dumping.dump_field(sk_casted, attributes[range_key])

      [pk, sk_dumped]
    end
  end

  read_options = options.slice(:consistent_read)

  items = if Dynamoid.config.backoff
            items = []
            backoff = nil
            Dynamoid.adapter.read(table_name, ids, read_options) do |hash, has_unprocessed_items|
              items += hash[table_name]

              if has_unprocessed_items
                backoff ||= Dynamoid.config.build_backoff
                backoff.call
              else
                backoff = nil
              end
            end
            items
          else
            items = Dynamoid.adapter.read(table_name, ids, read_options)
            items ? items[table_name] : []
          end

  if items.size == ids.size || !options[:raise_error]
    models = items ? items.map { |i| from_database(i) } : []
    models.each { |m| m.run_callbacks :find }
    models
  else
    ids_list = range_key ? ids.map { |pk, sk| "(#{pk},#{sk})" } : ids.map(&:to_s)
    message = "Couldn't find all #{name.pluralize} with primary keys [#{ids_list.join(', ')}] "
    message += "(found #{items.size} results, but was looking for #{ids.size})"
    raise Errors::RecordNotFound, message
  end
end

#_find_by_id(id, options = {}) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/dynamoid/finders.rb', line 155

def _find_by_id(id, options = {})
  raise Errors::MissingRangeKey if range_key && options[:range_key].nil?

  if range_key
    key = options[:range_key]
    key_casted = TypeCasting.cast_field(key, attributes[range_key])
    key_dumped = Dumping.dump_field(key_casted, attributes[range_key])

    options[:range_key] = key_dumped
  end

  if item = Dynamoid.adapter.read(table_name, id, options.slice(:range_key, :consistent_read))
    model = from_database(item)
    model.run_callbacks :find
    model
  elsif options[:raise_error]
    primary_key = range_key ? "(#{id},#{options[:range_key]})" : id
    message = "Couldn't find #{name} with primary key #{primary_key}"
    raise Errors::RecordNotFound, message
  end
end

#find(*ids, **options) ⇒ Dynamoid::Document

Find one or many objects, specified by one id or an array of ids.

By default it raises RecordNotFound exception if at least one model isn’t found. This behavior can be changed with raise_error option. If specified raise_error: false option then find will not raise the exception.

When a document schema includes range key it always should be specified in find method call. In case it’s missing MissingRangeKey exception will be raised.

Please note that find doesn’t preserve order of models in result when passes multiple ids.

Supported following options:

  • consistent_read

  • range_key

  • raise_error

Examples:

Find by partition key

Document.find(101)

Find by partition key and sort key

Document.find(101, range_key: 'archived')

Find several documents by partition key

Document.find(101, 102, 103)
Document.find([101, 102, 103])

Find several documents by partition key and sort key

Document.find([[101, 'archived'], [102, 'new'], [103, 'deleted']])

Perform strong consistent reads

Document.find(101, consistent_read: true)
Document.find(101, 102, 103, consistent_read: true)
Document.find(101, range_key: 'archived', consistent_read: true)

Parameters:

  • ids (String|Array)

    hash key or an array of hash keys

  • options (Hash)

Returns:

  • (Dynamoid::Document)

    one object or an array of objects, depending on whether the input was an array or not

Since:

  • 0.2.0



52
53
54
55
56
57
58
# File 'lib/dynamoid/finders.rb', line 52

def find(*ids, **options)
  if ids.size == 1 && !ids[0].is_a?(Array)
    _find_by_id(ids[0], options.reverse_merge(raise_error: true))
  else
    _find_all(ids.flatten(1), options.reverse_merge(raise_error: true))
  end
end

#find_all(ids, options = {}) ⇒ Object

Find several models at once.

Returns objects found by the given array of ids, either hash keys, or hash/range key combinations using BatchGetItem.

Returns empty array if no results found.

Uses backoff specified by Dynamoid::Config.backoff config option.

Examples:

# Find all the user with hash key
User.find_all(['1', '2', '3'])

# Find all the tweets using hash key and range key with consistent read
Tweet.find_all([['1', 'red'], ['1', 'green']], consistent_read: true)

Parameters:

  • ids (Array)

    array of primary keys

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :consistent_read (true|false)
  • :raise_error (true|false)


80
81
82
83
84
# File 'lib/dynamoid/finders.rb', line 80

def find_all(ids, options = {})
  ActiveSupport::Deprecation.warn('[Dynamoid] .find_all is deprecated! Call .find instead of')

  _find_all(ids, options)
end

#find_all_by_composite_key(hash_key, options = {}) ⇒ Array

Find all objects by hash and range keys.

Examples:

find all ChamberTypes whose level is greater than 1

class ChamberType
  include Dynamoid::Document
  field :chamber_type,            :string
  range :level,                   :integer
  table :key => :chamber_type
end

ChamberType.find_all_by_composite_key('DustVault', range_greater_than: 1)

Parameters:

  • hash_key (String)

    of the objects to find

  • options (Hash) (defaults to: {})

    the options for the range key

Options Hash (options):

  • :range_value (Range)

    find the range key within this range

  • :range_greater_than (Number)

    find range keys greater than this

  • :range_less_than (Number)

    find range keys less than this

  • :range_gte (Number)

    find range keys greater than or equal to this

  • :range_lte (Number)

    find range keys less than or equal to this

Returns:

  • (Array)

    an array of all matching items



209
210
211
212
213
214
215
# File 'lib/dynamoid/finders.rb', line 209

def find_all_by_composite_key(hash_key, options = {})
  ActiveSupport::Deprecation.warn('[Dynamoid] .find_all_composite_key is deprecated! Call .where instead of')

  Dynamoid.adapter.query(table_name, options.merge(hash_value: hash_key)).flat_map { |i| i }.collect do |item|
    from_database(item)
  end
end

#find_all_by_secondary_index(hash, options = {}) ⇒ Array

Find all objects by using local secondary or global secondary index

Examples:

class User
  include Dynamoid::Document

  table :key => :email
  global_secondary_index hash_key: :age, range_key: :rank

  field :email,  :string
  field :age,    :integer
  field :gender, :string
  field :rank    :number
end

# NOTE: the first param and the second param are both hashes,
#       so curly braces must be used on first hash param if sending both params
User.find_all_by_secondary_index({ age: 5 }, range: { "rank.lte": 10 })

Parameters:

  • hash (Hash)

    conditions for the hash key e.g. { age: 5 }

  • options (Hash) (defaults to: {})

    conditions on range key e.g. +{ “rank.lte”: 10 }, query filter, projected keys, scan_index_forward etc.

Returns:

  • (Array)

    an array of all matching items

Raises:



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/dynamoid/finders.rb', line 239

def find_all_by_secondary_index(hash, options = {})
  ActiveSupport::Deprecation.warn('[Dynamoid] .find_all_by_secondary_index is deprecated! Call .where instead of')

  range = options[:range] || {}
  hash_key_field, hash_key_value = hash.first
  range_key_field, range_key_value = range.first

  if range_key_field
    range_key_field = range_key_field.to_s
    range_key_op = 'eq'
    if range_key_field.include?('.')
      range_key_field, range_key_op = range_key_field.split('.', 2)
    end
  end

  # Find the index
  index = find_index(hash_key_field, range_key_field)
  raise Dynamoid::Errors::MissingIndex, "attempted to find #{[hash_key_field, range_key_field]}" if index.nil?

  # Query
  query_key_conditions = {}
  query_key_conditions[hash_key_field.to_sym] = [[:eq, hash_key_value]]
  if range_key_field
    query_key_conditions[range_key_field.to_sym] = [[range_key_op.to_sym, range_key_value]]
  end

  query_non_key_conditions = options
    .except(*Dynamoid::AdapterPlugin::AwsSdkV3::Query::OPTIONS_KEYS)
    .except(:range)
    .symbolize_keys

  query_options = options.slice(*Dynamoid::AdapterPlugin::AwsSdkV3::Query::OPTIONS_KEYS)
  query_options[:index_name] = index.name

  Dynamoid.adapter.query(table_name, query_key_conditions, query_non_key_conditions, query_options)
    .flat_map { |i| i }
    .map { |item| from_database(item) }
end

#find_by_composite_key(hash_key, range_key, options = {}) ⇒ Object

Find one object directly by hash and range keys.

Parameters:

  • hash_key (Scalar value)

    hash key of the object to find

  • range_key (Scalar value)

    range key of the object to find



182
183
184
185
186
# File 'lib/dynamoid/finders.rb', line 182

def find_by_composite_key(hash_key, range_key, options = {})
  ActiveSupport::Deprecation.warn('[Dynamoid] .find_by_composite_key is deprecated! Call .find instead of')

  _find_by_id(hash_key, options.merge(range_key: range_key))
end

#find_by_id(id, options = {}) ⇒ Dynamoid::Document

Find one object directly by primary key.

Examples:

Find by partition key

Document.find_by_id(101)

Find by partition key and sort key

Document.find_by_id(101, range_key: 'archived')

Parameters:

  • id (String)

    the id of the object to find

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :consistent_read (true|false)
  • :raise_error (true|false)
  • :range_key (Scalar value)

Returns:

Since:

  • 0.2.0



102
103
104
105
106
# File 'lib/dynamoid/finders.rb', line 102

def find_by_id(id, options = {})
  ActiveSupport::Deprecation.warn('[Dynamoid] .find_by_id is deprecated! Call .find instead of', caller[1..-1])

  _find_by_id(id, options)
end