class AWS::SimpleDB::ItemCollection

Represents a collection of items in a SimpleDB domain.

Constants

OUTSIDE_QUOTES_REGEX

Identifies quoted regions in the string, giving access to the regions before and after each quoted region, for example:

"? ? `foo?``bar?` ? 'foo?' ?".scan(OUTSIDE_QUOTES_REGEX)
# => [["? ? ", "`foo?``bar?`", " ? "], ["", "'foo?'", " ?"]]

@private

Attributes

conditions[R]

@private

domain[R]

@return [Domain] The domain the items belong to.

output_list[R]

@private

sort_instructions[R]

@private

Public Class Methods

new(domain, options = {}) click to toggle source

@param [Domain] domain The domain that you want an item collection for. @return [ItemCollection]

# File lib/aws/simple_db/item_collection.rb, line 49
def initialize domain, options = {}
  @domain = domain
  @output_list = options[:output_list] || 'itemName()'
  @conditions = options[:conditions] || []
  @sort_instructions = options[:sort_instructions]
  @not_null_attribute = options[:not_null_attribute]
  @limit = options[:limit]
  super
end

Public Instance Methods

[](item_name) click to toggle source

Retuns an item with the given name.

@note This does not make a request to SimpleDB.

You can ask for any item. The named item may or may not actually exist in SimpleDB.

@example Get an item by symbol or string name

item = domain.items[:itemname]
item = domain.items['itemname']

@param [String, Symbol] item_name name of the item to get. @return [Item] Returns an item with the given name.

# File lib/aws/simple_db/item_collection.rb, line 91
def [] item_name
  Item.new(domain, item_name.to_s)
end
_limit(*args) click to toggle source
Alias for: limit
count(options = {}) click to toggle source

Counts the items in the collection.

domain.items.count

You can specify what items to count with {where}:

domain.items.where(:color => "red").count

You can also limit the number of items to count:

# count up to 500 items and then stop
domain.items.limit(500).count

@param [Hash] options Options for counting items.

@option options [Boolean] :consistent_read (false) Causes this

method to yield the most current data in the domain when +true+.

@option options :where Restricts the item collection using

{#where} before querying.

@option options :limit [Integer] The maximum number of

items to count in SimpleDB.

@return [Integer] The number of items counted.

# File lib/aws/simple_db/item_collection.rb, line 205
def count options = {}, &block

  handle_query_options(options) do |collection, opts|
    return collection.count(opts, &block)
  end

  options = options.merge(:output_list => "count(*)")

  count = 0
  next_token = nil

  begin

    response = select_request(options, next_token)

    if 
      domain_item = response.items.first and
      count_attribute = domain_item.attributes.first
    then
      count += count_attribute.value.to_i
    end

    break unless next_token = response.next_token

  end while limit.nil? || count < limit

  count

end
Also aliased as: size
create(item_name, *args) click to toggle source

Creates a new item in SimpleDB with the given attributes:

domain.items.create('shirt', {
  'colors' => ['red', 'blue'],  
  'category' => 'clearance'})

@overload create(item_name, attribute_hash) @param [String] item_name The name of the item as you want it stored

in SimpleDB.

@param [Hash] attribute_hash A hash of attribute names and values

you want to store in SimpleDB.

@return [Item] Returns a reference to the object that was created.

# File lib/aws/simple_db/item_collection.rb, line 71
def create item_name, *args
  item = self[item_name]
  item.attributes.replace(*args)
  item
end
each(options = {}) click to toggle source

Yields to the block once for each item in the collection. This method can yield two type of objects:

The defualt mode of an ItemCollection is to yield Item objects with no populated attributes.

# only receives item names from SimpleDB
domain.items.each do |item|
  puts item.name
  puts item.class.name # => AWS::SimpleDB::Item
end

You can switch a collection into yielded {ItemData} objects by specifying what attributes to request:

domain.items.select(:all).each do |item_data|
  puts item_data.class.name # => AWS::SimpleDB::ItemData
  puts item_data.attributes # => { 'attr-name' => 'attr-value', ... }
end

You can also pass the standard scope options to each as well:

# output the item names of the 10 most expesive items
domain.items.each(:order => [:price, :desc], :limit => 10).each do |item|
  puts item.name
end

@yield [item] Yields once for every item in the {domain}.

@yieldparam [Item,ItemData] item If the item collection has been

scoped by chaining +#select+ or by passing the +:select+ option
then {ItemData} objects (that contain a hash of attrbiutes) are 
yielded.  If no list of attributes has been provided, then#
{Item} objects (with no populated data) are yielded.

@param options [Hash]

@option options [Boolean] :consistent_read (false) Causes this

method to yield the most current data in the domain.

@option options [Mixed] :select If select is provided, then each

will yield {ItemData} objects instead of empty {Item}.
The +:select+ option may be:

* +:all+ - Specifies that all attributes should requested.

* A single or array of attribute names (as strings or symbols).
  This causes the named attribute(s) to be requested.

@option options :where Restricts the item collection using

{#where} before querying (see {#where}).

@option options :order Changes the order in which the items

will be yielded (see {#order}).

@option options :limit [Integer] The maximum number of

items to fetch from SimpleDB.

@option options :batch_size Specifies a maximum number of records

to fetch from SimpleDB in a single request.  SimpleDB may return
fewer items than :batch_size per request, but never more.
Generally you should not need to specify this option.

@return [String,nil] Returns a next token that can be used with

the exact same SimpleDB select expression to get more results.
A next token is returned ONLY if there was a limit on the
expression, otherwise all items will be enumerated and
nil is returned.
# File lib/aws/simple_db/item_collection.rb, line 167
def each options = {}, &block
  super
end
each_batch(options = {}) click to toggle source

@private

# File lib/aws/simple_db/item_collection.rb, line 172
def each_batch options = {}, &block
  handle_query_options(options) do |collection, opts|
    return collection.each_batch(opts, &block)
  end
  super
end
limit(*args) click to toggle source

Limits the number of items that are returned or yielded. For example, to get the 100 most popular item names:

domain.items.
  order(:popularity, :desc).
  limit(100).
  map(&:name)

@overload limit

@return [Integer] Returns the current limit for the collection.

@overload limit(value)

@return [ItemCollection] Returns a collection with the given limit.
# File lib/aws/simple_db/item_collection.rb, line 446
def limit *args
  return @limit if args.empty?
  collection_with(:limit => Integer(args.first))
end
Also aliased as: _limit
order(attribute, order = nil) click to toggle source

Changes the order in which results are returned or yielded. For example, to get item names in descending order of popularity, you can do:

domain.items.order(:popularity, :desc).map(&:name)

@param attribute [String or Symbol] The attribute name to

order by.

@param order [String or Symbol] The desired order, which may be:

* +asc+ or +ascending+ (the default)
* +desc+ or +descending+

@return [ItemCollection] Returns a new item collection with the

given ordering logic.
# File lib/aws/simple_db/item_collection.rb, line 424
def order(attribute, order = nil)
  sort = coerce_attribute(attribute)
  sort += " DESC" if order.to_s =~ %r^desc(ending)?$/
  sort += " ASC" if order.to_s =~ %r^asc(ending)?$/
  collection_with(:sort_instructions => sort,
                  :not_null_attribute => attribute.to_s)
end
select(*attributes, &block) click to toggle source

Specifies a list of attributes select from SimpleDB.

domain.items.select('size', 'color').each do |item_data|
  puts item_data.attributes # => { 'size' => ..., :color => ... }
end

You can select all attributes by passing :all or ‘*’:

domain.items.select('*').each {|item_data| ... }

domain.items.select(:all).each {|item_data| ... }

Calling select causes each to yield {ItemData} objects with attribute hashes, instead of {Item} objects with an item name.

@param *attributes [Symbol, String, or Array] The attributes to

retrieve.  This can be:

* +:all+ or '*' to request all attributes for each item

* A list or array of attribute names as strinsg or symbols

  Attribute names may contain any characters that are valid
  in a SimpleDB attribute name; this method will handle
  escaping them for inclusion in the query.  Note that you
  cannot use this method to select the number of items; use
  {#count} instead.

@return [ItemCollection] Returns a new item collection with the

specified list of attributes to select.
# File lib/aws/simple_db/item_collection.rb, line 282
def select *attributes, &block

  # Before select was morphed into a chainable method, it accepted
  # a hash of options (e.g. :where, :order, :limit) that no longer
  # make sense, but to maintain backwards compatability we still
  # consume those.
  #
  # TODO : it would be a good idea to add a deprecation warning for
  #        passing options to #select
  #
  handle_query_options(*attributes) do |collection, *args|
    return collection.select(*args, &block)
  end

  options = attributes.last.is_a?(Hash) ? attributes.pop : {}

  output_list = case attributes.flatten
  when []     then '*'
  when ['*']  then '*'
  when [:all] then '*'
  else attributes.flatten.map{|attr| coerce_attribute(attr) }.join(', ')
  end

  collection = collection_with(:output_list => output_list)

  if block_given?
    # previously select accepted a block and it would enumerate items
    # this is for backwards compatability
    collection.each(options, &block)
    nil
  else
    collection
  end

end
size(options = {}) click to toggle source
Alias for: count
where(conditions, *substitutions) click to toggle source

Returns an item collection defined by the given conditions in addition to any conditions defined on this collection. For example:

items = domain.items.where(:color => 'blue').
  where('engine_type is not null')

# does SELECT itemName() FROM `mydomain`
#      WHERE color = "blue" AND engine_type is not null
items.each { |i| ... }

Hash Conditions

When conditions is a hash, each entry produces a condition on the attribute named in the hash key. For example:

# produces "WHERE `foo` = 'bar'"
domain.items.where(:foo => 'bar')

You can pass an array value to use an “IN” operator instead of “=”:

# produces "WHERE `foo` IN ('bar', 'baz')"
domain.items.where(:foo => ['bar', 'baz'])

You can also pass a range value to use a “BETWEEN” operator:

# produces "WHERE `foo` BETWEEN 'bar' AND 'baz'
domain.items.where(:foo => 'bar'..'baz')

# produces "WHERE (`foo` >= 'bar' AND `foo` < 'baz')"
domain.items.where(:foo => 'bar'...'baz')

Placeholders

If conditions is a string and “?” appears outside of any quoted part of the expression, placeholers is expected to contain a value for each of the “?” characters in the expression. For example:

# produces "WHERE foo like 'fred''s % value'"
domain.items.where("foo like ?", "fred's % value")

Array values are surrounded with parentheses when they are substituted for a placeholder:

# produces "WHERE foo in ('1', '2')"
domain.items.where("foo in ?", [1, 2])

Note that no substitutions are made within a quoted region of the query:

# produces "WHERE `foo?` = 'red'"
domain.items.where("`foo?` = ?", "red")

# produces "WHERE foo = 'fuzz?' AND bar = 'zap'"
domain.items.where("foo = 'fuzz?' AND bar = ?", "zap")

Also note that no attempt is made to correct for syntax:

# produces "WHERE 'foo' = 'bar'", which is invalid
domain.items.where("? = 'bar'", "foo")

@return [ItemCollection] Returns a new item collection with the

additional conditions.
# File lib/aws/simple_db/item_collection.rb, line 384
def where conditions, *substitutions
  case conditions
  when String
    conditions = [replace_placeholders(conditions, *substitutions)]
  when Hash
    conditions = conditions.map do |name, value|
      name = coerce_attribute(name)
      case value
      when Array
        "#{name} IN " + coerce_substitution(value)
      when Range
        if value.exclude_end?
          "(#{name} >= #{coerce_substitution(value.begin)} AND " +
            "#{name} < #{coerce_substitution(value.end)})"
        else
          "#{name} BETWEEN #{coerce_substitution(value.begin)} AND " +
            coerce_substitution(value.end)
        end
      else
        "#{name} = " + coerce_substitution(value)
      end
    end
  end

  collection_with(:conditions => self.conditions + conditions)
end

Protected Instance Methods

_each_item(next_token, max, options = {}) { |self| ... } click to toggle source
# File lib/aws/simple_db/item_collection.rb, line 481
def _each_item next_token, max, options = {}, &block

  handle_query_options(options) do |collection, opts|
    return collection._each_item(next_token, max, opts, &block)
  end

  response = select_request(options, next_token, max)

  if output_list == 'itemName()'  
    response.items.each do |item|
      yield(self[item.name])
    end
  else
    response.items.each do |item|
      yield(ItemData.new(:domain => domain, :response_object => item))
    end
  end

  response.next_token
  
end
coerce_attribute(name) click to toggle source

@private

# File lib/aws/simple_db/item_collection.rb, line 643
def coerce_attribute(name)
  '`' + name.to_s.gsub('`', '``') + '`'
end
coerce_substitution(subst) click to toggle source

@private

# File lib/aws/simple_db/item_collection.rb, line 632
def coerce_substitution(subst)
  if subst.kind_of?(Array)
    "(" +
      subst.flatten.map { |s| coerce_substitution(s) }.join(", ") + ")"
  else
    "'" + subst.to_s.gsub("'", "''") + "'"
  end
end
collection_with(options) click to toggle source

@private

# File lib/aws/simple_db/item_collection.rb, line 561
def collection_with options
  ItemCollection.new(domain, { 
    :output_list => output_list,
    :conditions => conditions,
    :sort_instructions => sort_instructions,
    :not_null_attribute => @not_null_attribute,
    :limit => limit,
  }.merge(options))
end
handle_query_options(*args) { |collection, *args| ... } click to toggle source

Applies standard scope options (e.g. :where => ‘foo’) and removes them from the options hash by calling their method (e.g. by calling where(‘foo’)). Yields only if there were scope options to apply.

@private

# File lib/aws/simple_db/item_collection.rb, line 457
def handle_query_options(*args)

  options = args.last.is_a?(Hash) ? args.pop : {}

  if 
    query_options = options.keys & [:select, :where, :order, :limit] and
    !query_options.empty?
  then
    collection = self
    query_options.each do |query_option|
      option_args = options[query_option]
      option_args = [option_args] unless option_args.kind_of?(Array)
      options.delete(query_option)
      collection = collection.send(query_option, *option_args)
    end

    args << options

    yield(collection, *args)

  end
end
limit_clause() click to toggle source

@private

# File lib/aws/simple_db/item_collection.rb, line 555
def limit_clause
  limit ? "LIMIT #{limit}" : nil
end
order_by_clause() click to toggle source

@private

# File lib/aws/simple_db/item_collection.rb, line 549
def order_by_clause
  sort_instructions ? "ORDER BY #{sort_instructions}" : nil
end
replace_named_placeholders(str, named) click to toggle source

@private

# File lib/aws/simple_db/item_collection.rb, line 620
def replace_named_placeholders(str, named)
  named.each do |name, value|
    str = str.gsub(name.to_sym.inspect, coerce_substitution(value))
  end
  str.scan(%r:\S+/) do |missing|
    raise ArgumentError.new("missing value for placeholder #{missing}")
  end
  str
end
replace_placeholders(str, *substitutions) click to toggle source

@private

# File lib/aws/simple_db/item_collection.rb, line 573
def replace_placeholders(str, *substitutions)
  named = {}
  named = substitutions.pop if substitutions.last.kind_of?(Hash)
  if str =~ %r['"`]/
    count = 0
    str = str.scan(OUTSIDE_QUOTES_REGEX).
      map do |(before, quoted, after)|

      (before, after) = [before, after].map do |s|
        s, count =
          replace_placeholders_outside_quotes(s, count, substitutions, named)
        s
      end
      [before, quoted, after].join
    end.join
  else
    # no quotes
    str, count =
      replace_placeholders_outside_quotes(str, 0, substitutions, named)
  end
  raise ArgumentError.new("extra value(s): #{substitutions.inspect}") unless
    substitutions.empty?
  str
end
replace_placeholders_outside_quotes(str, count, substitutions, named = {}) click to toggle source

@private

# File lib/aws/simple_db/item_collection.rb, line 600
def replace_placeholders_outside_quotes(str, count, substitutions, named = {})
  str, count = replace_positional_placeders(str, count, substitutions)
  str = replace_named_placeholders(str, named)
  [str, count]
end
replace_positional_placeders(str, count, substitutions) click to toggle source

@private

# File lib/aws/simple_db/item_collection.rb, line 608
def replace_positional_placeders(str, count, substitutions)
  str = str.gsub("?") do |placeholder|
    count += 1
    raise ArgumentError.new("missing value for placeholder #{count}") if
      substitutions.empty?
    coerce_substitution(substitutions.shift)
  end
  [str, count]
end
select_expression(options = {}) click to toggle source

@private

# File lib/aws/simple_db/item_collection.rb, line 523
def select_expression options = {}
  expression = []
  expression << "SELECT #{options[:output_list] || self.output_list}"
  expression << "FROM `#{domain.name}`"
  expression << where_clause
  expression << order_by_clause
  expression << limit_clause
  expression.compact.join(' ')
end
select_request(options, next_token = nil, limit = nil) click to toggle source
# File lib/aws/simple_db/item_collection.rb, line 504
def select_request(options, next_token = nil, limit = nil)

  opts = {}
  opts[:select_expression] = select_expression(options)
  opts[:consistent_read] = consistent_read(options)
  opts[:next_token] = next_token if next_token

  if limit
    unless opts[:select_expression].gsub!(%rLIMIT \d+/, "LIMIT #{limit}")
      opts[:select_expression] << " LIMIT #{limit}"
    end
  end

  client.select(opts)

end
where_clause() click to toggle source

@private

# File lib/aws/simple_db/item_collection.rb, line 535
def where_clause

  conditions = self.conditions.dup

  if @not_null_attribute
    conditions << coerce_attribute(@not_null_attribute) + " IS NOT NULL"
  end

  conditions.empty? ? nil : "WHERE #{conditions.join(" AND ")}"

end