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, date_attr, datetime_attr, float_attr, integer_attr, sortable_float_attr, sortable_integer_attr, string_attr, timestamps

Methods included from FinderMethods

all, count, find, find_by_id, 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.



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/aws/record/base.rb', line 307

def initialize attributes = {}

  opts = attributes.dup

  @_data = {}

  @_domain = attributes.delete(:domain)
  @_domain ||= attributes.delete('domain')
  @_domain = self.class.domain_name(@_domain)

  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.



454
455
456
# File 'lib/aws/record/base.rb', line 454

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:



487
488
489
# File 'lib/aws/record/base.rb', line 487

def create_domain name = nil
  sdb.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.



470
471
472
473
474
475
476
477
478
# File 'lib/aws/record/base.rb', line 470

def domain_name name = nil

  name = @_domain_name if name.nil?
  name = self.name if name.nil?
  name = name.name if name.is_a?(SimpleDB::Domain)

  "#{Record.domain_prefix}#{name}"

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.



461
462
463
# File 'lib/aws/record/base.rb', line 461

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.



339
340
341
342
343
344
345
346
# File 'lib/aws/record/base.rb', line 339

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.



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

def attributes= attributes
  bulk_assign(attributes)
end

#deleteObject

Deletes the record.



433
434
435
436
437
438
439
440
441
442
443
# File 'lib/aws/record/base.rb', line 433

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.



446
447
448
# File 'lib/aws/record/base.rb', line 446

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

#domainString

Returns the name of the SimpleDB domain this record is persisted to or will be persisted to.

Returns:

  • (String)

    Returns the name of the SimpleDB domain this record is persisted to or will be persisted to.



333
334
335
# File 'lib/aws/record/base.rb', line 333

def domain
  @_domain
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.



327
328
329
# File 'lib/aws/record/base.rb', line 327

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.



376
377
378
# File 'lib/aws/record/base.rb', line 376

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.



370
371
372
# File 'lib/aws/record/base.rb', line 370

def persisted?
  !!@_persisted
end

#saveBoolean

Creates new records, updates existing records.

Returns:

  • (Boolean)

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



389
390
391
392
393
394
395
396
397
# File 'lib/aws/record/base.rb', line 389

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.



404
405
406
407
# File 'lib/aws/record/base.rb', line 404

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.



413
414
415
416
# File 'lib/aws/record/base.rb', line 413

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)


423
424
425
426
427
428
429
# File 'lib/aws/record/base.rb', line 423

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.



381
382
383
384
# File 'lib/aws/record/base.rb', line 381

def valid?
  run_validations
  errors.empty?
end