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


296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/dynamoid/finders.rb', line 296

def method_missing(method, *args)
  if method =~ /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 =~ /all/
      chain.all
    else
      chain.first
    end
  else
    super
  end
end

Instance Method Details

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


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
153
154
155
156
157
158
159
160
161
# File 'lib/dynamoid/finders.rb', line 120

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]
    items ? items.map { |i| from_database(i) } : []
  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


164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/dynamoid/finders.rb', line 164

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))
    from_database(item)
  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


63
64
65
66
67
68
69
# File 'lib/dynamoid/finders.rb', line 63

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)

91
92
93
94
95
# File 'lib/dynamoid/finders.rb', line 91

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


216
217
218
219
220
221
222
# File 'lib/dynamoid/finders.rb', line 216

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:


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
277
278
279
280
281
# File 'lib/dynamoid/finders.rb', line 246

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
  range_op_mapped = nil

  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
    range_op_mapped = RANGE_MAP.fetch(range_key_op)
  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
  opts = {
    hash_key: hash_key_field.to_s,
    hash_value: hash_key_value,
    index_name: index.name
  }
  if range_key_field
    opts[:range_key] = range_key_field
    opts[range_op_mapped] = range_key_value
  end
  dynamo_options = opts.merge(options.reject { |key, _| key == :range })
  Dynamoid.adapter.query(table_name, dynamo_options).flat_map { |i| i }.map do |item|
    from_database(item)
  end
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


189
190
191
192
193
# File 'lib/dynamoid/finders.rb', line 189

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


113
114
115
116
117
# File 'lib/dynamoid/finders.rb', line 113

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