Serquel: Concise ORM for Ruby

Sequel is an ORM framework for Ruby. Sequel provides thread safety, connection pooling, and a concise DSL for constructing queries and table schemas.

Sequel makes it easy to deal with multiple records without having to break your teeth on SQL.

Resources

To check out the source code:

svn co http://ruby-sequel.googlecode.com/svn/trunk

Contact

If you have any comments or suggestions please send an email to ciconia at gmail.com and I’ll get back to you.

Installation

sudo gem install sequel

Supported Databases

Sequel currently supports:

  • Postgresql

  • SQLite 3

  • MySQL (preliminary support)

The Sequel Console

Sequel includes an IRB console for quick’n’dirty access to databases. You can use it like this:

sequel sqlite:///test.db

You get an IRB session with the database object stored in DB.

A Short Tutorial

Connecting to a database

Before connecting to a database, you should require the corresponding adaptor, for example:

require 'sequel/sqlite'

Note: you don’t need to require ‘sequel’ separately before that, as each adapter requires ‘sequel’ if it hasn’t yet been required.

There are two ways to create a connection to a database. The easier way is to provide a connection URL:

DB = Sequel.connect("sqlite:///blog.db")

You can also specify optional parameters, such as the connection pool size:

DB = Sequel.connect("postgres://postgres:postgres@localhost/my_db",
  :max_connections => 10)

The second, more verbose, way is to create an instance of a database class:

DB = Sequel::Postgres::Database.new(:database => 'my_db', :host => 'localhost')

Arbitrary SQL queries

DB.execute("create table t (a text, b text)")
DB.execute("insert into t values ('a', 'b')")

Or more succinctly:

DB << "create table t (a text, b text)"
DB << "insert into t values ('a', 'b')"

Creating Datasets

Dataset is the primary means through which records are retrieved and manipulated. You can create an blank dataset by using the dataset method:

dataset = DB.dataset

Or by using the from methods:

posts = DB.from(:posts)

You can also use the equivalent shorthand:

posts = DB[:posts]

Note: the dataset will only fetch records when you explicitly ask for them, as will be shown below. Datasets can be manipulated to filter through records, change record order and even join tables, as will also be shown below.

Retrieving Records

You can retrieve records by using the all method:

posts.all

The all method returns an array of hashes, where each hash corresponds to a record.

You can also iterate through records one at a time:

posts.each {|row| p row}

Or perform more advanced stuff:

posts.map(:id)
posts.inject({}) {|h, r| h[r[:id]] = r[:name]}

You can also retrieve the first record in a dataset:

posts.first

Or retrieve a single record with a specific value:

posts[:id => 1]

If the dataset is ordered, you can also ask for the last record:

posts.order(:stamp).last

Filtering Records

The simplest way to filter records is to provide a hash of values to match:

my_posts = posts.filter(:category => 'ruby', :author => 'david')

You can also specify ranges:

my_posts = posts.filter(:stamp => (2.weeks.ago)..(1.week.ago))

Or lists of values:

my_posts = posts.filter(:category => ['ruby', 'postgres', 'linux'])

Some adapters (like postgresql) will also let you specify Regexps:

my_posts = posts.filter(:category => /ruby/i)

You can also use an inverse filter:

my_posts = posts.exclude(:category => /ruby/i)

You can then retrieve the records by using any of the retrieval methods:

my_posts.each {|row| p row}

You can also specify a custom WHERE clause:

posts.filter('(stamp < ?) AND (author <> ?)', 3.days.ago, author_name)

Summarizing Records

Counting records is easy:

posts.filter(:category => /ruby/i).count

And you can also query maximum/minimum values:

max_value = DB[:history].max(:value)

Or calculate a sum:

total = DB[:items].sum(:price)

Ordering Records

posts.order(:stamp)

You can also specify descending order

posts.order(:stamp.DESC)

Deleting Records

posts.filter('stamp < ?', 3.days.ago).delete

Inserting Records

posts.insert(:category => 'ruby', :author => 'david')

Or alternatively:

posts << {:category => 'ruby', :author => 'david'}

Updating Records

posts.filter('stamp < ?', 3.days.ago).update(:state => 'archived')

Joining Tables

Joining is very useful in a variety of scenarios, for example many-to-many relationships. With Sequel it’s really easy:

order_items = DB[:items].join(:order_items, :item_id => :id).
  filter(:order_items__order_id => 1234)

This is equivalent to the SQL:

SELECT * FROM items LEFT OUTER JOIN order_items
ON order_items.item_id = items.id 
WHERE order_items.order_id = 1234

You can then do anything you like with the dataset:

order_total = order_items.sum(:price)

Which is equivalent to the SQL:

SELECT sum(price) FROM items LEFT OUTER JOIN order_items
ON order_items.item_id = items.id
WHERE order_items.order_id = 1234