Class: AWS::Record::Base

Inherits:
Object
  • Object
show all
Extended by:
AttributeMacros, FinderMethods, OptimisticLocking, Scopes, Validations
Includes:
DirtyTracking
Defined in:
lib/aws/record/base.rb,
lib/aws/record/errors.rb

Overview

An ActiveRecord-like interface built ontop of AWS.

class Book < AWS::Record::Base

  string_attr :title
  string_attr :author
  integer :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::Base 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

For more information about the various attribute macros available, and what options they accept, see AttributeMacros.

Usage

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

class Book < AWS::Record::Base
  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::Base
  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::Base
  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::Base
  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::Base

  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

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Validations

validate, validates_acceptance_of, validates_confirmation_of, validates_count_of, validates_each, validates_exclusion_of, validates_format_of, validates_inclusion_of, validates_length_of, validates_numericality_of, validates_presence_of

Methods included from AttributeMacros

boolean_attr, datetime_attr, float_attr, integer_attr, sortable_float_attr, sortable_integer_attr, string_attr, timestamps

Methods included from FinderMethods

all, count, domain, find, first, limit, order, where

Methods included from OptimisticLocking

optimistic_locking

Methods included from Scopes

scope

Methods included from DirtyTracking

#changed, #changed?, #changes

Constructor Details

#initialize(attributes = {}) ⇒ Base

Constructs a new record for this class/domain.

Parameters:

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

    A set of attribute values to seed this record with. The attributes are bulk assigned.



305
306
307
308
309
# File 'lib/aws/record/base.rb', line 305

def initialize attributes = {}
  @_data = {}
  assign_default_values
  bulk_assign(attributes)
end

Class Method Details

.attributesHash<String,Attribute>

Returns a hash of all of the configured attributes for this class.

Returns:

  • (Hash<String,Attribute>)

    Returns a hash of all of the configured attributes for this class.



436
437
438
# File 'lib/aws/record/base.rb', line 436

def attributes
  @attributes ||= {}
end

.create_domain(name = nil) ⇒ AWS::SimpleDB::Domain

Creates the SimpleDB domain that is configured for this class.

Parameters:

  • name (String) (defaults to: nil)

    Name of the domain to create. Defaults to the name of this class. The name will be prefixed with domain_prefix if one is set.

Returns:



463
464
465
# File 'lib/aws/record/base.rb', line 463

def create_domain name = nil
  AWS::SimpleDB.new.domains.create(domain_name(name))
end

.domain_name(name = nil) ⇒ String

Returns the domain name this record class persists data into. The default domain name is the class name with the optional domain_prefix).

Parameters:

  • name (String) (defaults to: nil)

    Defaults to the name of this class.

Returns:

  • (String)

    Returns the full prefixed domain name for this class.



452
453
454
# File 'lib/aws/record/base.rb', line 452

def domain_name name = nil
  "#{Record.domain_prefix}#{name || @_domain_name || self.to_s}"
end

.set_domain_name(name) ⇒ Object

Allows you to override the default domain name for this record.

The defualt domain name is the class name.

Parameters:

  • The (String)

    domain name that should be used for this class.



443
444
445
# File 'lib/aws/record/base.rb', line 443

def set_domain_name name
  @_domain_name = name
end

Instance Method Details

#attributesHash

Returns A hash with attribute names as hash keys (strings) and attribute values (of mixed types) as hash values.

Returns:

  • (Hash)

    A hash with attribute names as hash keys (strings) and attribute values (of mixed types) as hash values.



321
322
323
324
325
326
327
328
# File 'lib/aws/record/base.rb', line 321

def attributes
  attributes = Core::IndifferentHash.new
  attributes['id'] = id if persisted?
  self.class.attributes.keys.inject(attributes) do |hash,attr_name|
    hash[attr_name] = __send__(attr_name)
    hash
  end
end

#attributes=(attributes) ⇒ Hash

Acts like #update but does not call #save.

record.attributes = { :name => 'abc', :age => 20 }

Parameters:

  • attributes (Hash)

    A hash of attributes to set on this record without calling save.

Returns:

  • (Hash)

    Returns the attribute hash that was passed in.



339
340
341
# File 'lib/aws/record/base.rb', line 339

def attributes= attributes
  bulk_assign(attributes)
end

#deleteObject

Deletes the record.



415
416
417
418
419
420
421
422
423
424
425
# File 'lib/aws/record/base.rb', line 415

def delete
  if persisted?
    if deleted?
      raise 'unable to delete, this object has already been deleted'
    else
      delete_item
    end
  else
    raise 'unable to delete, this object has not been saved yet'
  end
end

#deleted?Boolean

Returns true if this instance object has been deleted.

Returns:

  • (Boolean)

    Returns true if this instance object has been deleted.



428
429
430
# File 'lib/aws/record/base.rb', line 428

def deleted?
  persisted? ? !!@_deleted : false
end

#errorsObject



19
20
21
# File 'lib/aws/record/errors.rb', line 19

def errors
  @errors ||= Errors.new
end

#idString

The id for each record is auto-generated. The default strategy generates uuid strings.

Returns:

  • (String)

    Returns the id string (uuid) for this record. Retuns nil if this is a new record that has not been persisted yet.



315
316
317
# File 'lib/aws/record/base.rb', line 315

def id
  @_id
end

#new_record?Boolean

Returns true if this record has not been persisted to SimpleDB.

Returns:

  • (Boolean)

    Returns true if this record has not been persisted to SimpleDB.



358
359
360
# File 'lib/aws/record/base.rb', line 358

def new_record?
  !persisted?
end

#persisted?Boolean

Persistence indicates if the record has been saved previously or not.

Examples:

@recipe = Recipe.new(:name => 'Buttermilk Pancackes')
@recipe.persisted? #=> false
@recipe.save!
@recipe.persisted? #=> true

Returns:

  • (Boolean)

    Returns true if this record has been persisted.



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

def persisted?
  !!@_persisted
end

#saveBoolean

Creates new records, updates existing records.

Returns:

  • (Boolean)

    Returns true if the record saved without errors, false otherwise.



371
372
373
374
375
376
377
378
379
# File 'lib/aws/record/base.rb', line 371

def save
  if valid?
    persisted? ? update : create
    clear_changes!
    true
  else
    false
  end
end

#save!true

Creates new records, updates exsting records. If there is a validation error then an exception is raised.

Returns:

  • (true)

    Returns true after a successful save.

Raises:

  • (InvalidRecordError)

    Raised when the record has validation errors and can not be saved.



386
387
388
389
# File 'lib/aws/record/base.rb', line 386

def save!
  raise InvalidRecordError.new(self) unless save
  true
end

#update_attributes(attribute_hash) ⇒ Boolean

Bulk assigns the attributes and then saves the record.

Parameters:

  • attribute_hash (Hash)

    A hash of attribute names (keys) and attribute values to assign to this record.

Returns:

  • (Boolean)

    Returns true if the record saved without errors, false otherwise.



395
396
397
398
# File 'lib/aws/record/base.rb', line 395

def update_attributes attribute_hash
  bulk_assign(attribute_hash)
  save
end

#update_attributes!(attribute_hash) ⇒ true

Bulk assigns the attributes and then saves the record. Raises an exception (AWS::Record::InvalidRecordError) if the record is not valid.

Parameters:

  • attribute_hash (Hash)

    A hash of attribute names (keys) and attribute values to assign to this record.

Returns:

  • (true)


405
406
407
408
409
410
411
# File 'lib/aws/record/base.rb', line 405

def update_attributes! attribute_hash
  if update_attributes(attribute_hash)
    true
  else
    raise InvalidRecordError.new(self)
  end
end

#valid?Boolean

Returns true if this record has no validation errors.

Returns:

  • (Boolean)

    Returns true if this record has no validation errors.



363
364
365
366
# File 'lib/aws/record/base.rb', line 363

def valid?
  run_validations
  errors.empty?
end