Class: AWS::Record::Model
- Inherits:
-
Object
- Object
- AWS::Record::Model
- 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
# 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, :default_value => 'Me'
end
Book.new. #=> '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. #=> #<Set: {}>
b. = ['fiction', 'fantasy']
b. #=> #<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. #=> ['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:
-
As an sql-like fragment. If you need to escape values this form is not suggested.
Book.where('title = "My Book"')
-
An sql-like fragment, with placeholders. This escapes quoted arguments properly to avoid injection.
Book.where('title = ?', 'My Book')
-
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
-
.all(options = {}) ⇒ Scope
Returns an enumerable scope object represents all records.
-
.boolean_attr(name, options = {}) ⇒ Object
Adds a boolean attribute to this class.
-
.count(options = {}) ⇒ Object
(also: size)
Counts records in SimpleDB.
-
.create_domain(shard_name = nil) ⇒ SimpleDB::Domain
Creates the SimpleDB domain that is configured for this class.
-
.date_attr(name, options = {}) ⇒ Object
Adds a date attribute to this class.
-
.datetime_attr(name, options = {}) ⇒ Object
Adds a datetime attribute to this class.
-
.each(&block) ⇒ Object
Yields once for each record.
-
.find(*args) ⇒ Object
Finds records in SimpleDB and returns them as objects of the current class.
-
.find_by_id(id, options = {}) ⇒ Record::HashModel
(also: [])
Returns the record with the given id.
-
.first(options = {}) ⇒ Object?
Returns the first record found.
-
.float_attr(name, options = {}) ⇒ Object
Adds a float attribute to this class.
-
.integer_attr(name, options = {}) ⇒ Object
Adds an integer attribute to this class.
-
.limit(limit) ⇒ Object
The maximum number of records to return.
-
.order(attribute, direction = :asc) ⇒ Object
Defines the order in which records are returned when performing a find.
-
.shard(shard_name) ⇒ Scope
(also: domain)
Returns a chainable scope object that restricts further scopes to a particular domain.
-
.sortable_float_attr(name, options = {}) ⇒ Object
Adds sortable float attribute to this class.
-
.sortable_integer_attr(name, options = {}) ⇒ Object
Adds a sortable integer attribute to this class.
-
.string_attr(name, options = {}) ⇒ Object
Adds a string attribute to this class.
-
.timestamps ⇒ Object
A convenience method for adding the standard two datetime attributes
:created_at
and:updated_at
. -
.where(*args) ⇒ Object
Limits which records are retried from SimpleDB when performing a find.
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
118 119 120 |
# File 'lib/aws/record/model/finder_methods.rb', line 118 def all = {} new_scope.find(:all, ) end |
.boolean_attr(name, options = {}) ⇒ Object
Adds a boolean attribute to this class.
297 298 299 300 301 302 303 304 305 306 |
# File 'lib/aws/record/model/attributes.rb', line 297 def boolean_attr name, = {} attr = add_attribute(Attributes::BooleanAttr.new(name, )) # 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.
149 150 151 |
# File 'lib/aws/record/model/finder_methods.rb', line 149 def count( = {}) new_scope.count() 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'
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.
352 353 354 |
# File 'lib/aws/record/model/attributes.rb', line 352 def date_attr name, = {} add_attribute(Record::Attributes::DateAttr.new(name, )) 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.
329 330 331 |
# File 'lib/aws/record/model/attributes.rb', line 329 def datetime_attr name, = {} add_attribute(Record::Attributes::DateTimeAttr.new(name, )) 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.
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.
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, = {} domain = sdb_domain([:shard] || [: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.
156 157 158 |
# File 'lib/aws/record/model/finder_methods.rb', line 156 def first = {} new_scope.first() end |
.float_attr(name, options = {}) ⇒ Object
233 234 235 |
# File 'lib/aws/record/model/attributes.rb', line 233 def float_attr name, = {} add_attribute(Attributes::FloatAttr.new(name, )) end |
.integer_attr(name, options = {}) ⇒ Object
183 184 185 |
# File 'lib/aws/record/model/attributes.rb', line 183 def integer_attr name, = {} add_attribute(Attributes::IntegerAttr.new(name, )) 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| ... }
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
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
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.
276 277 278 |
# File 'lib/aws/record/model/attributes.rb', line 276 def sortable_float_attr name, = {} add_attribute(Attributes::SortableFloatAttr.new(name, )) 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.
216 217 218 |
# File 'lib/aws/record/model/attributes.rb', line 216 def sortable_integer_attr name, = {} add_attribute(Attributes::SortableIntegerAttr.new(name, )) end |
.string_attr(name, options = {}) ⇒ Object
Adds a string attribute to this class.
166 167 168 |
# File 'lib/aws/record/model/attributes.rb', line 166 def string_attr name, = {} add_attribute(Record::Attributes::StringAttr.new(name, )) end |
.timestamps ⇒ Object
A convenience method for adding the standard two datetime attributes :created_at
and :updated_at
.
370 371 372 373 374 |
# File 'lib/aws/record/model/attributes.rb', line 370 def 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)
195 196 197 |
# File 'lib/aws/record/model/finder_methods.rb', line 195 def where *args new_scope.where(*args) end |