ORM in Ruby: An Introduction
Anyone with experience in developing web based applications or using a web framework has dealt with relational databases, such as MySQL or PostgreSQL. Dealing with SQL can be a bit scary at times, especially when dealing with data across different tables and applying various filters.
This is where ORM comes to the rescue.
What is ORM ?
ORM is the acronym for Object Relational Mapping.
Object-relational mapping (ORM, O/RM, and O/R mapping) in computer software is a programming technique for converting data between incompatible type systems in object-oriented programming languages. This creates, in effect, a “virtual object database” that can be used from within the programming language. There are both free and commercial packages available that perform object-relational mapping, although some programmers opt to create their own ORM tools.
In other words, an ORM framework is written in an object oriented language (like Ruby, Python, PHP etc.) and wrapped around a relational database. The object classes are mapped to the data tables in the database and the object instances are mapped to rows in those tables.
The following are advantages of ORM as a concept. It’s worth noting that not all ORM implementations support all the following advantages.
This is probably the biggest advantage of using ORM in an application. No need to write code specific to a particular database. Simply start a project using SQLite and, later on, change to MySQL or PostgreSQL. Changing a few lines of code in the database configuration adapter makes it work with another database.
Domain Model Pattern
When using ORM for building an application, it lets the business code access objects rather than the database structure itself, using the mapping between the business model and the database.
Reduce Code and Increase Efficiency
ORM provides an abstraction which allows the developers to focus on their business logic, rather than complex database querries, resulting in a huge reduction of code and increase in efficiency of the developer.
Rich Query Interface
ORM frameworks usually come with a rich query interface, freeing the developer from the complex semantics of SQL.
ORM provides a hassle free management of database relationships. The related objects are automatically loaded when a query is translated to its corresponding SQL.
ORM supports concurrency, allowing multiple users to update the same set of data simultaneously.
Objects can be cached in the memory, reducing the load on the database.
The changes to an object can be bound to a transaction, which can either be committed or rolled back. Multiple transactions can be active at a given point of time, but all these transactions are isolated from one another.
As ORM framework adds a layer of abstraction, speeding up the development and lowering the complexity, However, it also adds overhead to the application. Using ORM will consume more memory and increase CPU usage.
Applying ORM to an application expects the developer to have experience working with a particular framework. There is a learning curve involved with most ORM frameworks.
Some actions, such as bulk insert, update, delete, etc. are slower when implemented using ORM. These are, usually, more efficiently handled using native SQL queries.
While ORM makes life easier for developers, it often results in the developer not learning SQL and database internals unless the developers are keen in exploring the same.
ORM Frameworks in Ruby
There are a lot of ORM frameworks written in Ruby. Some of the more popular ones are briefly described here.
ActiveRecord is the ORM that comes out-of-the-box with Rails. ActiveRecord (or AR) attempts to follow the Active Record design pattern. It plays the role of Model in the MVC architecture employed by Rails.
ActiveRecord creates the abstraction from the database using business objects, thereby relieving the developers from dealing with the underlying database complexities. One of ActiveRecord’s main tenets is “convention over configuration”. It automatically detects the configuration if the correct conventions are followed for the table names, variable names etc. However, it is always possible to override the default configurations, if specified explicitly.
Some of the notable features of ActiveRecord are as follows:
- Data is represented as models.
- Associations can be implemented using these models.
- Inheritance can be achieved through related models.
- Validation of data before being saved into the database.
- Object oriented way of dealing with database operations.
Although ActiveRecord is included with Rails, it can be installed as an independent gem.
gem install activerecord
Here is a quick sample demonstrating the usage of ActiveRecord:
require 'rubygems' require 'activerecord' ActiveRecord::Base.establish_connection( :adapter=> "mysql", :host => "localhost", :database=> "articles" ) class Article < ActiveRecord::Base end #Creating a new article Article.create(title:'ORM', author:'Manu') #Fetching an article article = Article.find(:first) #Destroy an article Article.find(:first).destroy
The major disadvantages of ActiveRecord is that it breaks the Single Responsibility Principle(SRP). Since the domain objects also handle persistance, an extra responsibility, the complexity of the objects increase.
DataMapper was developed to address perceived shortcomings in the ActiveRecord library and to create an ORM framework which is fast, thread-safe, and feature rich. It follows the DataMapper design pattern
While in Active Record, the Domain Model encapsulates both the business logic and the responsibility of database access, DataMapper is ignorant of the database, resulting in Plain old Ruby Objects(POROs) with business logic. DataMapper allows the database logic and object model to evolve independently, isolating them from each other, thereby following the Single Responsibility Principle(SRP).
Advantages of Datamapper are as follows:
- No need to write migrations
- Scoped relations or Collection chaining
- Lazy loading on certain attribute types, like text fields, which can make the query slow.
- Eager loading, avoiding the N+1 query problem
- Dirty tracking, only saves the fields that actually changed
- Respects the Single Responsibility Principle
Simple example of using DataMapper:
require 'rubygems' require 'dm-core' require 'dm-migrations' DataMapper::Logger.new($stdout, :debug) DataMapper.setup(:default, 'mysql://localhost/test') class Author include DataMapper::Resource property :id, Serial property :name, String, :required => true end DataMapper.auto_migrate! #Create a new record Author.create(:name => 'Manu') #Fetching a record using DM Author.first(:name => 'Manu') #Destroying a record author = Author.get(7) author.destroy
In the above example, DataMapper::Resource is added to the Author class in order to handle persistance.
Sequel is designed to provide an easy, database independent API for interacting with SQL databases. It is also based on the ActiveRecord pattern. There are many similarities between ActiveRecord and Sequel.
- Provides thread safety, connection pooling, and a concise DSL for constructing SQL queries and table schemas
- Includes a comprehensive ORM layer for mapping records to Ruby objects and handling associated records
- Supports advanced database features such as prepared statements, bound variables, stored procedures, savepoints, two-phase commit, transaction isolation, master/slave configurations, and database sharding
- Currently has adapters for ADO, Amalgalite, CUBRID, DataObjects, DB2, DBI, Firebird, IBM_DB, Informix, JDBC, MySQL, Mysql2, ODBC, OpenBase, Oracle, PostgreSQL, SQLAnywhere, SQLite3, Swift, and TinyTDS
Simple example using Sequel:
require "rubygems" require "sequel" DB = Sequel.sqlite DB.create_table :author do primary_key :id String :name end authors = DB[:authors] authors.insert(:name => 'Manu')
ORM aims to solve a problem and make the developer’s life easier, with many different options for acheiving the same goal. While ActiveRecord has dominiated the Ruby ORM landscape, DataMapper and Sequel (and others) are legitimate options. If you haven’t already, you should choose an ORM strategy that fits your use case.