CSC/ECE 517 Fall 2010/ch3 3j KS
Object-relational Mapping for Ruby
Introduction
Object-relational mapping (ORM) provides developers with a set of tools that ease management of the relationships between objects and relational databases, thus allowing applications to be easily extended to add data persistence. For Ruby, several object-relational mapping options are available. This wiki concentrates more on the comparison of ORM's and it gives a very high level overview of the various ORM's such as ActiveRecord , Sequel and DataMapper.
Overview
Object-relational Mapping (ORM) frameworks unburden the designer of the complex translation between database and object space.
Typical ORM features:
- Automatic mapping from classes to database tables
- Class instance variables to database columns
- Class instances to table rows
- Aggregation and association relationships between mapped classes are managed
- Example, :has_many, :belongs_to associations in ActiveRecord
- Inheritance cases are mapped to tables
- Validation of data prior to table storage
- Class extensions to enable search, as well as creation, read, update, and deletion (CRUD) of instances/records
- Usually abstracts the database from program space in such a way that alternate database types can be easily chosen (SQLite, Oracle, etc)
The diagram below depicts a simple mapping of an object to a database table….
When applied to Ruby, implementations of ORM often leverage the language’s metaprogramming strengths to create intuitive application-specific methods and otherwise extend classes to support database functionality.
ActiveRecord
ActiveRecord, originally created by David Heinemeier and released in 2003, became the de-facto ORM for Ruby since it was integrated in the widely-used Rails framework, however alternate ORMs for Ruby have been developed and Rails 3.x is ORM independent. ActiveRecord is an implementation of the active record design pattern, where a table is wrapped into a class and this class implements accessor methods for each column in the table.
To create a table, ActiveRecord makes use of a migration class rather than including table definition in the actual class being modeled. As an example, the following code creates a table ‘’users’’ to store a collection of class ‘’User’’ objects:
class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :name t.string :email t.string :age t.timestamps # add creation and modification timestamps end end def self.down # undo the table creation drop_table :users end end
Note in the preceeding example, that the ActiveRecord’s migration scheme provides a means for backing out changes via the ‘’self.down’’ method. In addition, a table key ‘’id’’ is added to the new table without explicit definition and record creation and modification timestamps are included via the ‘’timestamps’’ method provided by ActiveRecord.
ActiveRecord manages associations between table elements and provides the means to define such associations in the model definition. For example, the ‘’User’’ class shown below defines a ‘’to_many’’ association with both the cheers and posts tables. Also note ActiveRecord’s integrated support for validation of table information when attempting an update.
class User < ActiveRecord::Base has_many :cheers has_many :posts validates_presence_of :name validates_presence_of :age validates_uniqueness_of :name validates_length_of :name, :within => 3..20 end
While ActiveRecord provides the flexibility to create more sophisticated table relationships to represent class hierarchy, its base scheme is a single table inheritance, which trades some storage efficiency for simplicity in the database design. The following figure illustrates this concept of simplicity over efficiency.
Pros -
- Integrated with popular Rails development framework
- Dynamically created database search methods (eg: User.find_by_address) ease db queries and make queries database syntax independent
- DB creation/management using the migrate scheme provides a means for backing out unwanted table changes
Cons -
- DB creation/management is decoupled from the model, requiring a separate utility (rake/migrate) that must be kept in sync with application
Sequel
Sequel was originally developed by Sharon Rosner and the first release was in March 2007. It is based on the active record pattern. Sequel and Active record share a lot of common features , for example association and inheritance. But Sequel handles these features in a much more flexible manner. Currently Sequel is at version 3.16.0. Initially Sequel was had three core modules - sequel, sequel_core and sequel_model.Starting from version 1.4 , sequel and sequel_model were merged. Sequel handles validations using a validation plug-in and helpers.
class CreateUser < Sequel::Migration def up create_table(:user) { primary_key :id String :name String :age} end def down drop_table(:user) end end # CreateUser.apply(DB, :up)
Sequel supports associations and validations similar to ActiveRecord. The following example shows how validations and associations can be enforced int he User table that has been created above. It enforces one to many relationship between the user table and the cheers , posts tables. It also validates for the presence , uniqueness and the length of the attribute name.
class User < Sequel::Model one_to_many :cheers one_to_many :posts validates_presence [:name, :age] validates_unique(:name) validates_length_range 3..20, :name end
Some of the key features of sequel are,
- Connection Pooling
- Thread Safety
- Eager Loading / Lazy Loading
- Model Caching
DataMapper
DataMapper is an open source ORM for Ruby originally developed by Sam Smoot and first released in 2007. DataMapper provides a very flexible mapping API which allows creation of adapters to a wide variety of datastores beyond traditional SQL-based relational databases – DataMapper adapters have been created to non-standard sources such as the Salesforce API and even Google Video.
Some of the key features of DataMapper are:
- API supports a wide variety of databases, including non SQL types
- Thread Safety
- Eager loading of child associations
- Lazy loading
Unlike ActiveRecord and Sequel, DataMapper does not rely on a migration scheme to create and manage DB tables. Instead, DataMapper allows table definition as part of the model class definition which keeps the model definition contained to a single file, thus minimizing the effort required to keep the database and model definitions in sync. When required, DataMapper can also support a migration methodology similar to other ORMs.
Example of table definition in the model:
class User include DataMapper::Resource property :id, Serial # key property :name, String property :password, String property :age, String property :created_at, DateTime has n, :comments # one to many association end
Comparison of ORM Features
Features | ActiveRecord | Sequel | DataMapper |
---|---|---|---|
Databases |
MySQL, PostgreSQL, SQLite, Oracle, SQLServer, and DB2 | ADO, DataObjects, DB2, DBI, Firebird, Informix, JDBC, MySQL, ODBC, OpenBase, Oracle, PostgreSQL and SQLite3 | SQLite, MySQL, PostgreSQL, Oracle, MongoDB, SimpleDB, many others, including CouchDB, Apache Solr, Google Data API |
Migrations |
Yes | Yes | Yes, but optional |
EagerLoading |
Supported by scanning the SQL fragments | Supported using eager (preloading) and eager_graph (joins) | Supported |
Flexible Overriding |
No. Overriding is done using alias methods. | Using methods and by calling 'super' | Using methods (verify) |
Dynamic Finders |
Yes. Uses 'Method Missing' | No. Alternative is to use <Model>.FindOrCreate(:name=>"John") | Yes. Using the dm_ar_finders plugin |
Further Reading
To get more information on ORM and the different type of ORM's available for Ruby , please look into the Object-relational mapping Fall 2007 wiki.