module Dynamoid::Finders::ClassMethods

Public Instance Methods

_find_all(ids, options = {}) click to toggle source

@private

# 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]
    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 = {}) click to toggle source

@private

# File lib/dynamoid/finders.rb, line 166
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) click to toggle source

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

@param ids [String|Array] hash key or an array of hash keys @param options [Hash] @return [Dynamoid::Document] one object or an array of objects, depending on whether the input was an array or not

@example Find by partition key

Document.find(101)

@example Find by partition key and sort key

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

@example Find several documents by partition key

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

@example Find several documents by partition key and sort key

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

@example 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)

@since 0.2.0

# 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 = {}) click to toggle source

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.

@param ids [Array] array of primary keys @param options [Hash] @option options [true|false] :consistent_read @option options [true|false] :raise_error

@example

# 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)
# 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 = {}) click to toggle source

Find all objects by hash and range keys.

@example 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)

@param [String] hash_key of the objects to find @param [Hash] options the options for the range key @option options [Range] :range_value find the range key within this range @option options [Number] :range_greater_than find range keys greater than this @option options [Number] :range_less_than find range keys less than this @option options [Number] :range_gte find range keys greater than or equal to this @option options [Number] :range_lte find range keys less than or equal to this

@return [Array] an array of all matching items

# File lib/dynamoid/finders.rb, line 220
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 = {}) click to toggle source

Find all objects by using local secondary or global secondary index

@example

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 })

@param hash [Hash] conditions for the hash key e.g. +{ age: 5 }+ @param options [Hash] conditions on range key e.g. +{ “rank.lte”: 10 }, query filter, projected keys, scan_index_forward etc. @return [Array] an array of all matching items

# File lib/dynamoid/finders.rb, line 250
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 = {}) click to toggle source

Find one object directly by hash and range keys.

@param hash_key [Scalar value] hash key of the object to find @param range_key [Scalar value] range key of the object to find

# File lib/dynamoid/finders.rb, line 193
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 = {}) click to toggle source

Find one object directly by primary key.

@param id [String] the id of the object to find @param options [Hash] @option options [true|false] :consistent_read @option options [true|false] :raise_error @option options [Scalar value] :range_key @return [Dynamoid::Document] the found object, or nil if nothing was found

@example Find by partition key

Document.find_by_id(101)

@example Find by partition key and sort key

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

@since 0.2.0

# 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
method_missing(method, *args) click to toggle source

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

@example find a user by a first name

User.find_by_first_name('Josh')

@example find all users by first and last name

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

@return [Dynamoid::Document|Array] the found object, or an array of found objects if all was somewhere in the method

@private @since 0.2.0

Calls superclass method
# File lib/dynamoid/finders.rb, line 300
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 =~ /all/
      chain.all
    else
      chain.first
    end
  else
    super
  end
end