Class: AWS::Record::Model

Inherits:
Object
  • Object
show all
Extended by:
AbstractBase
Defined in:
lib/aws/record/model.rb,
lib/aws/record/model/scope.rb,
lib/aws/record/model/attributes.rb,
lib/aws/record/model/finder_methods.rb

Overview

An ActiveRecord-like interface built ontop of Amazon SimpleDB.

class Book < AWS::Record::Model

  string_attr :title
  string_attr :author
  integer_attr :number_of_pages

  timestamps # adds a :created_at and :updated_at pair of timestamps

end

b = Book.new(:title => 'My Book', :author => 'Me', :pages => 1)
b.save

Attribute Macros

When extending AWS::Record::Model you should first consider what attributes your class should have. Unlike ActiveRecord, AWS::Record models are not backed by a database table/schema. You must choose what attributes (and what types) you need.

  • string_attr

  • boolean_attr

  • integer_attr

  • float_attr

  • datetime_attr

  • date_attr

Usage

Normally you just call these methods inside your model class definition:

class Book < AWS::Record::Model
  string_attr :title
  boolean_attr :has_been_read
  integer_attr :number_of_pages
  float_attr :weight_in_pounds
  datetime_attr :published_at
end

For each attribute macro a pair of setter/getter methods are added # to your class (and a few other useful methods).

b = Book.new
b.title = "My Book"
b.has_been_read = true
b.number_of_pages = 1000
b.weight_in_pounds = 1.1
b.published_at = Time.now
b.save

b.id #=> "0aa894ca-8223-4d34-831e-e5134b2bb71c"
b.attributes
#=> { 'title' => 'My Book', 'has_been_read' => true, ... }

Default Values

All attribute macros accept the :default_value option. This sets a value that is populated onto all new instnaces of the class.

class Book < AWS::Record::Model
  string_attr :author, :deafult_value => 'Me'
end

Book.new.author #=> 'Me'

Multi-Valued (Set) Attributes

AWS::Record permits storing multiple values with a single attribute.

class Book < AWS::Record::Model
  string_attr :tags, :set => true
end

b = Book.new
b.tags #=> #<Set: {}>

b.tags = ['fiction', 'fantasy']
b.tags #=> #<Set: {'fiction', 'fantasy'}>

These multi-valued attributes are treated as sets, not arrays. This means:

  • values are unordered

  • duplicate values are automatically omitted

Please consider these limitations when you choose to use the :set option with the attribute macros.

Validations

It’s important to validate models before there are persisted to keep your data clean. AWS::Record supports most of the ActiveRecord style validators.

class Book < AWS::Record::Model
  string_attr :title
  validates_presence_of :title
end

b = Book.new
b.valid? #=> false
b.errors.full_messages #=> ['Title may not be blank']

Validations are checked before saving a record. If any of the validators adds an error, the the save will fail.

For more information about the available validation methods see Validations.

Finder Methods

You can find records by their ID. Each record gets a UUID when it is saved for the first time. You can use this ID to fetch the record at a latter time:

b = Book["0aa894ca-8223-4d34-831e-e5134b2bb71c"]

b = Book.find("0aa894ca-8223-4d34-831e-e5134b2bb71c")

If you try to find a record by ID that has no data an error will be raised.

All

You can enumerate all of your records using all.

Book.all.each do |book|
  puts book.id
end

Book.find(:all) do |book|
  puts book.id
end

Be careful when enumerating all. Depending on the number of records and number of attributes each record has, this can take a while, causing quite a few requests.

First

If you only want a single record, you should use first.

b = Book.first

Modifiers

Frequently you do not want ALL records or the very first record. You can pass options to find, all and first.

my_books = Book.find(:all, :where => 'owner = "Me"')

book = Book.first(:where => { :has_been_read => false })

You can pass as find options:

  • :where - Conditions that must be met to be returned

  • :order - The order to sort matched records by

  • :limit - The maximum number of records to return

Scopes

More useful than writing query fragments all over the place is to name your most common conditions for reuse.

class Book < AWS::Record::Model

  scope :mine, where(:owner => 'Me')

  scope :unread, where(:has_been_read => false)

  scope :by_popularity, order(:score, :desc)

  scope :top_10, by_popularity.limit(10)

end

# The following expression returns 10 books that belong
# to me, that are unread sorted by popularity.
next_good_reads = Book.mine.unread.top_10

There are 3 standard scope methods:

  • where

  • order

  • limit

Conditions (where)

Where accepts aruments in a number of forms:

  1. As an sql-like fragment. If you need to escape values this form is not suggested.

    Book.where('title = "My Book"')
    
  2. An sql-like fragment, with placeholders. This escapes quoted arguments properly to avoid injection.

    Book.where('title = ?', 'My Book')
    
  3. A hash of key-value pairs. This is the simplest form, but also the least flexible. You can not use this form if you need more complex expressions that use or.

    Book.where(:title => 'My Book')
    

Order

This orders the records as returned by AWS. Default ordering is ascending. Pass the value :desc as a second argument to sort in reverse ordering.

Book.order(:title)        # alphabetical ordering 
Book.order(:title, :desc) # reverse alphabetical ordering

You may only order by a single attribute. If you call order twice in the chain, the last call gets presedence:

Book.order(:title).order(:price)

In this example the books will be ordered by :price and the order(:title) is lost.

Limit

Just call limit with an integer argument. This sets the maximum number of records to retrieve:

Book.limit(2)

Delayed Execution

It should be noted that all finds are lazy (except first). This means the value returned is not an array of records, rather a handle to a Scope object that will return records when you enumerate over them.

This allows you to build an expression without making unecessary requests. In the following example no request is made until the call to each_with_index.

all_books = Books.all
ten_books = all_books.limit(10)

ten_books.each_with_index do |book,n|
  puts "#{n + 1} : #{book.title}"
end

Defined Under Namespace

Modules: Attributes Classes: Scope

Class Method Summary collapse

Class Method Details

.all(options = {}) ⇒ Scope

Returns an enumerable scope object represents all records.

Book.all.each do |book|
  # ...
end

This method is equivalent to find(:all), and therefore you can also pass aditional options. See find for more information on what options you can pass.

Book.all(:where => { :author' => 'me' }).each do |my_book|
  # ...
end

Returns:

  • (Scope)

    Returns an enumerable scope object.



118
119
120
# File 'lib/aws/record/model/finder_methods.rb', line 118

def all options = {}
  new_scope.find(:all, options)
end

.boolean_attr(name, options = {}) ⇒ Object

Adds a boolean attribute to this class.

Examples:


class Book < AWS::Record::Model
  boolean_attr :read
end

b = Book.new
b.read? # => false
b.read = true
b.read? # => true

listing = Listing.new(:score => '123.456'
listing.score # => 123.456

Parameters:

  • name (Symbol)

    The name of the attribute.



297
298
299
300
301
302
303
304
305
306
# File 'lib/aws/record/model/attributes.rb', line 297

def boolean_attr name, options = {}
  
  attr = add_attribute(Attributes::BooleanAttr.new(name, options))
  
  # add the boolean question mark method
  define_method("#{attr.name}?") do
    !!__send__(attr.name)
  end
  
end

.count(options = {}) ⇒ Object Also known as: size

Counts records in SimpleDB.

With no arguments, counts all records:

People.count

Accepts query options to count a subset of records:

People.count(:where => { :boss => true })

You can also count records on a scope object:

People.find(:all).where(:boss => true).count

See find and Scope#count for more details.

Parameters:

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

    ({}) Options for counting records.

Options Hash (options):

  • :where (Mixed)

    Conditions that determine what records are counted.

  • :limit (Integer)

    The max number of records to count.



149
150
151
# File 'lib/aws/record/model/finder_methods.rb', line 149

def count(options = {})
  new_scope.count(options)
end

.create_domain(shard_name = nil) ⇒ SimpleDB::Domain

Creates the SimpleDB domain that is configured for this class.

class Product < AWS::Record::Model
end

Product.create_table #=> 'Product'

If you share a single AWS account with multiple applications, you can provide a domain prefix for your model classes.

AWS::Record.domain_prefix = 'myapp-'

Product.create_table #=> 'myapp-Product'

If you have set a model shard name, this is used in place of the class name.

AWS::Record.domain_prefix = 'prod-'
class Product < AWS::Record::Model
  set_shard_name 'products'
end

Product.create_table #=> 'prod-products'

If you shard you data across multiple domains, you can specify the shard name:

# create two domains, with the given names
Product.create_domain 'products-1'
Product.create_domain 'products-2'

Parameters:

  • shard_name (optional, String) (defaults to: nil)

    Defaults to the class name.

Returns:



313
314
315
# File 'lib/aws/record/model.rb', line 313

def create_domain shard_name = nil
  sdb.domains.create(sdb_domain_name(shard_name))
end

.date_attr(name, options = {}) ⇒ Object

Adds a date attribute to this class.

Examples:

A standard date attribute


class Person < AWS::Record::Model
  date_attr :birthdate
end

baby = Person.new
baby.birthdate = Time.now
baby.birthdate #=> <Date: ....>

Parameters:

  • name (Symbol)

    The name of the attribute.

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

Options Hash (options):

  • :set (Boolean) — default: false

    When true this attribute can have multiple dates.



352
353
354
# File 'lib/aws/record/model/attributes.rb', line 352

def date_attr name, options = {}
  add_attribute(Record::Attributes::DateAttr.new(name, options))
end

.datetime_attr(name, options = {}) ⇒ Object

Adds a datetime attribute to this class.

If you add a datetime_attr for :created_at and/or :updated_at those will be automanaged.

Examples:

A standard datetime attribute


class Recipe < AWS::Record::Model
  datetime_attr :invented
end

recipe = Recipe.new(:invented => Time.now)
recipe.invented #=> <DateTime ...>

Parameters:

  • name (Symbol)

    The name of the attribute.

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

Options Hash (options):

  • :set (Boolean) — default: false

    When true this attribute can have multiple date times.



329
330
331
# File 'lib/aws/record/model/attributes.rb', line 329

def datetime_attr name, options = {}
  add_attribute(Record::Attributes::DateTimeAttr.new(name, options))
end

.each(&block) ⇒ Object

Yields once for each record.



123
124
125
# File 'lib/aws/record/model/finder_methods.rb', line 123

def each &block
  all.each(&block)
end

.find(id) ⇒ Object .find(mode, options = {}) ⇒ Object

Finds records in SimpleDB and returns them as objects of the current class.

Finding :all returns an enumerable scope object

People.find(:all, :order => [:age, :desc], :limit => 10).each do |person|
  puts person.name
end

Finding :first returns a single record (or nil)

boss = People.find(:first, :where => { :boss => true })

Find accepts a hash of find modifiers (:where, :order and :limit). You can also choose to omit these modifiers and chain them on the scope object returned. In the following example only one request is made to SimpleDB (when #each is called)

people = People.find(:all)

johns = people.where(:name => 'John Doe')

johns.order(:age, :desc).limit(10).each do |suspects|
  # ...
end

See also where, order and limit for more information and options.

Overloads:

  • .find(id) ⇒ Object

    Parameters:

    • id

      The record to find, raises an exception if the record is not found.

  • .find(mode, options = {}) ⇒ Object

    Parameters:

    • mode (:all, :first)

      (:all) When finding :all matching records and array is returned of records. When finding :first then nil or a single record will be returned.

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

    Options Hash (options):

    • :where (Mixed)

      Conditions that determine what records are returned.

    • :sort (String, Array)

      The order records should be returned in.

    • :limit (Integer)

      The max number of records to fetch.



85
86
87
# File 'lib/aws/record/model/finder_methods.rb', line 85

def find *args
  new_scope.find(*args)
end

.find_by_id(id, options = {}) ⇒ Record::HashModel Also known as: []

Returns the record with the given id.

Parameters:

  • id (String)

    The id of the record to load.

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

Options Hash (options):

  • :shard (String)

    Specifies what shard (i.e. domain) should be searched.

Returns:

Raises:

  • (RecordNotFound)

    Raises a record not found exception if there was no data found for the given id.



26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/aws/record/model/finder_methods.rb', line 26

def find_by_id id, options = {}
  
  domain = sdb_domain(options[:shard] || options[:domain])
  
  data = domain.items[id].data.attributes
  
  raise RecordNotFound, "no data found for id: #{id}" if data.empty?
  
  obj = self.new(:shard => domain)
  obj.send(:hydrate, id, data)
  obj
  
end

.first(options = {}) ⇒ Object?

Returns the first record found. If there were no records found, nil is returned.

Returns:

  • (Object, nil)

    Returns the first record found. If there were no records found, nil is returned.



156
157
158
# File 'lib/aws/record/model/finder_methods.rb', line 156

def first options = {}
  new_scope.first(options)
end

.float_attr(name, options = {}) ⇒ Object

Adds a float attribute to this class.

class Listing < AWS::Record::Model
  float_attr :score
end

listing = Listing.new(:score => '123.456')
listing.score # => 123.456

Parameters:

  • name (Symbol)

    The name of the attribute.

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

Options Hash (options):

  • :set (Boolean) — default: false

    When true this attribute can have multiple values.



233
234
235
# File 'lib/aws/record/model/attributes.rb', line 233

def float_attr name, options = {}
  add_attribute(Attributes::FloatAttr.new(name, options))
end

.integer_attr(name, options = {}) ⇒ Object

Adds an integer attribute to this class.

class Recipe < AWS::Record::Model
  integer_attr :servings
end

recipe = Recipe.new(:servings => '10')
recipe.servings #=> 10

Parameters:

  • name (Symbol)

    The name of the attribute.

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

Options Hash (options):

  • :set (Boolean) — default: false

    When true this attribute can have multiple values.



183
184
185
# File 'lib/aws/record/model/attributes.rb', line 183

def integer_attr name, options = {}
  add_attribute(Attributes::IntegerAttr.new(name, options))
end

.limit(limit) ⇒ Object

The maximum number of records to return. By default, all records matching the where conditions will be returned from a find.

People.limit(10).each {|person| ... }

Limit can be chained with other scope modifiers:

People.where(:age => 40).limit(10).each {|person| ... }


225
226
227
# File 'lib/aws/record/model/finder_methods.rb', line 225

def limit limit
  new_scope.limit(limit)
end

.order(attribute, direction = :asc) ⇒ Object

Defines the order in which records are returned when performing a find. SimpleDB only allows sorting by one attribute per request.

# oldest to youngest
People.order(:age, :desc).each {|person| ... }

You can chain order with the other scope modifiers:

Pepole.order(:age, :desc).limit(10).each {|person| ... }

Parameters:

  • attribute (String, Symbol)

    The attribute to sort by.

  • direction (:asc, :desc) (defaults to: :asc)

    (:asc) The direction to sort.



212
213
214
# File 'lib/aws/record/model/finder_methods.rb', line 212

def order *args
  new_scope.order(*args)
end

.shard(shard_name) ⇒ Scope Also known as: domain

Returns a chainable scope object that restricts further scopes to a particular domain.

Book.domain('books-2').each do |book|
  # ...
end

Parameters:

  • shard_name (String)

Returns:

  • (Scope)

    Returns a scope for restricting the domain of subsequent



98
99
100
# File 'lib/aws/record/model/finder_methods.rb', line 98

def shard shard_name
  new_scope.shard(shard_name)
end

.sortable_float_attr(name, options = {}) ⇒ Object

Note:

If you change the :range after some values have been persisted you must also manually migrate all of the old values to have the correct padding & offset or they will be interpreted differently.

Adds sortable float attribute to this class.

Persisted values are stored (and sorted) as strings. This makes it more difficult to sort numbers because they don’t sort lexicographically unless they have been offset to be positive and then zero padded.

Postive Floats

To store floats in a sort-friendly manor:

sortable_float_attr :score, :range => (0..10)

This will cause values like 5.5 to persist as a string like ‘05.5’ so that they can be sorted lexicographically.

Negative Floats

If you need to store negative sortable floats, increase your :range to include a negative value.

sortable_float_attr :position, :range => (-10..10)

AWS::Record will add 10 to all values and zero pad them (e.g. -10.0 will be represented as ‘00.0’ and 10 will be represented as ‘20.0’). This will allow the values to be compared lexicographically.

Parameters:

  • name (Symbol)

    The name of the attribute.

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

Options Hash (options):

  • :range (Range)

    The range of numbers this attribute should represent. The min and max values of this range will determine how many digits of precision are required and how much of an offset is required to make the numbers sort lexicographically.

  • :set (Boolean) — default: false

    When true this attribute can have multiple values.



276
277
278
# File 'lib/aws/record/model/attributes.rb', line 276

def sortable_float_attr name, options = {}
  add_attribute(Attributes::SortableFloatAttr.new(name, options))
end

.sortable_integer_attr(name, options = {}) ⇒ Object

Adds a sortable integer attribute to this class.

class Person < AWS::Record::Model
  sortable_integer_attr :age, :range => 0..150
end

person = Person.new(:age => 10)
person.age #=> 10

Validations

It is recomended to apply a validates_numericality_of with minimum and maximum value constraints. If a value is assigned to a sortable integer that falls outside of the +:range: it will raise a runtime error when the record is saved.

Difference Between Sortable an Regular Integer Attributes

Because SimpleDB does not support numeric types, all values must be converted to strings. This complicates sorting by numeric values. To accomplish sorting numeric attributes the values must be zero padded and have an offset applied to eliminate negative values.

Parameters:

  • name (Symbol)

    The name of the attribute.

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

Options Hash (options):

  • :range (Range)

    A numeric range the represents the minimum and maximum values this attribute should accept.

  • :set (Boolean) — default: false

    When true this attribute can have multiple values.



216
217
218
# File 'lib/aws/record/model/attributes.rb', line 216

def sortable_integer_attr name, options = {}
  add_attribute(Attributes::SortableIntegerAttr.new(name, options))
end

.string_attr(name, options = {}) ⇒ Object

Adds a string attribute to this class.

Examples:

A standard string attribute


class Recipe < AWS::Record::Model
  string_attr :name
end

recipe = Recipe.new(:name => "Buttermilk Pancakes")
recipe.name #=> 'Buttermilk Pancakes'

A string attribute with :set set to true


class Recipe < AWS::Record::Model
  string_attr :tags, :set => true
end

recipe = Recipe.new(:tags => %w(popular dessert))
recipe.tags #=> #<Set: {"popular", "desert"}> 

Parameters:

  • name (Symbol)

    The name of the attribute.

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

Options Hash (options):

  • :set (Boolean) — default: false

    When true this attribute can have multiple values.



166
167
168
# File 'lib/aws/record/model/attributes.rb', line 166

def string_attr name, options = {}
  add_attribute(Record::Attributes::StringAttr.new(name, options))
end

.timestampsObject

A convenience method for adding the standard two datetime attributes :created_at and :updated_at.

Examples:


class Recipe < AWS::Record::Model
  timestamps
end

recipe = Recipe.new
recipe.save
recipe.created_at #=> <DateTime ...>
recipe.updated_at #=> <DateTime ...>


370
371
372
373
374
# File 'lib/aws/record/model/attributes.rb', line 370

def timestamps
  c = datetime_attr :created_at
  u = datetime_attr :updated_at
  [c, u]
end

.where(conditions_hash) ⇒ Object .where(sql_fragment[, quote_params, ...]) ⇒ Object

Limits which records are retried from SimpleDB when performing a find.

Simple string condition

Car.where('color = "red" or color = "blue"').each {|car| ... }

String with placeholders for quoting params

Car.where('color = ?', 'red')

Car.where('color = ? OR style = ?', 'red', 'compact')

# produces a condition using in, like: WHERE color IN ('red', 'blue')
Car.where('color IN ?', ['red','blue'])

Hash arguments

# WHERE age = '40' AND gender = 'male'
People.where(:age => 40, :gender => 'male').each {|person| ... }

# WHERE name IN ('John', 'Jane')
People.where(:name => ['John', 'Jane']).each{|person| ... }

Chaining where with other scope modifiers

# 10 most expensive red cars
Car.where(:color => 'red').order(:price, :desc).limit(10)

Overloads:

  • .where(conditions_hash) ⇒ Object

    Parameters:

    • conditions_hash (Hash)

      A hash of attributes to values. Each key/value pair from the hash becomes a find condition. All conditions are joined by AND.



195
196
197
# File 'lib/aws/record/model/finder_methods.rb', line 195

def where *args
  new_scope.where(*args)
end