Fetching contributors…
Cannot retrieve contributors at this time
398 lines (264 sloc) 8.6 KB

Ohm Quickstart Guide

What is Ohm?

Ohm is a library that makes serializing your Ruby objects to Redis simple and fast.

What is Redis?

Redis is an ultra-fast Key/Value store with lots of features. Redis supports several data types, including Hashes, Lists, Sets, Counters.

How is Key/Value different than Relational DBs?

Is Ohm right for me?

Ohm is only one of many ways of representing your data. It does not attempt to cover all situations, or uses.

You should use Ohm if:

  • You need super-fast data access

You should not use Ohm if:

  • Real-time Ad-hoc queries are important. If you don't know how you will query your data ahead of time, Ohm can be slower than a traditional relational database.

Getting Ohm

Ohm is available as a rubygem. Install it as with any other Rubygem. Add sudo if it is approprate for your environment.

gem install ohm

Installing Redis

The package manager for your environment probably has a package for Redis. Redis 2+ is strongly preferred, but Ohm works with any version from 1.3.10 and beyond.

port install redis
brew install redis
apt-get install redis

Redis also compliles very easily, follow the instructions to get started.

Importing Ohm Into Your Project

Just like any other library, import Ohm via rubygems.

require 'rubygems'
require 'ohm'

Connecting to Redis

To setup a global connection for Ohm, call the Ohm.connect function with the correct options for your environment. See the extras/ directory for an example redis.conf file that will work with the command below.

Ohm.connect(:host => "", :port => 6379)

Setting Up Your First Model

Models are just classes that inherit from Ohm::Model, and define attributes, indexes.

class User < Ohm::Model
  attribute :email
  attribute :encrypted_password

  index :email

  collection :posts

Attribute types

Attribute is just a string value stored in Redis.

class Book < Ohm::Model
  attribute :title  

Counter is an atomically updating integer that you can shift up or down.

class Count < Ohm::Model
  counter :things 

c = Count.create
c.things # => 0

Collection is a foreign key type. This is equivalent to has_many in Active Record.

Reference is the other side of a collection. It is equivalent to belongs_to in Active Record.

class Book < Ohm::Model
  collection :authors, Author

class Author < Ohm::Model
  reference :book, Book

b = Book.create
a = Author.create
a2 = Author.create
b.authors << a
b.authors << a2

b.authors # => [a, a2]  # => b


Because Key/Value databases don't support searching directly, Ohm maintains a set of indexes that you tell it, which enables fast searching.

Creating / Retrieving / Deleting / Finding / Sorting

New Unsaved

u = => "")


u.valid?  # => true / false

Save # => true / false.  Won't save if validation fails

Create and Save

u = User.create(:email => "")


u = User[10] # => Find record #10




u.find(:email => "")

u.find(:status => "enabled").except(:name => "Chris")


User.all.sort # => Sorted by id

Combined Find & Sort

User.find(:status => "enabled").sort_by(:email)


Ohm makes it easy to verify that the data in your database is valid.

class User < Ohm::Model
  attribute :name
  attribute :email
  index :name

  def validate
    assert_present :name
    assert_present :email

    assert_unique :name # => Requires the index :name above

    assert_format :email, /^.*@.*$/ # => Make sure the email address has a @ sign in it.

u = User.create()
u.valid? # => false
u.errors # => [[:name, :not_present], [:email, :not_present], [:email, :format]]

Ohm doesn't try to render human readable errors. That's a job for the view layer of whatever application you are writing.

error_messages = u.errors.present do |e|
  e.on [:name, :not_present], "Name must be present"
  e.on [:email, :not_present], "You must supply an email address"
  e.on [:email, :format], "Your email needs an @ sign"

# => ["Name must be present", 
#     "You must supply an email address", 
#     "Your email needs an @ sign"]

Now you have nicely formatted error messages defined in your view, ready to be displayed.

Custom Validations

The real power of validations in Ohm is that you can write domain specific validations. For example, instead of using a format validation for email, you can create an assert_email validation.

def assert_email(attr)
  assert read_local(attr) =~ /^.*@.*$/, [attr, :not_email]

def validate
  assert_email :email

Blog Example

This is a quick runthrough of how to architect the database layer of a hypothetical blog. We will focus entirely on the Ohm layer, and gloss over any web related items.

Check the examples/blog directory for a full source file of this example.

Starting off

Before we do anything, we will need to import Ohm.

require 'rubygems'
require 'ohm'

And connect to Redis using default options


The Post

The core of a blog is the list of posts.

class Post < Ohm::Model
  attribute :title
  attribute :slug
  attribute :body
  index :slug

  # When we save, calculate the slug if we need to, then continue on with
  # Ohm's normal save behavior
  def save
    calculate_slug if slug.nil?

  def validate
    assert_present :title
    assert_present :slug
    assert_present :body

    assert_unique :slug

  def calculate_slug
    slug = title.gsub(/\W+/, '-'). # Remove non-word chars
                 gsub(/^-+/,'').   # Remove a leading dash
                 gsub(/-+$/,'').   # Remove a trailing dash

The Author

class Author < Ohm::Model
  collection :posts, Post

  attribute :name
  attribute :email

  def validate
    assert_present :name
    assert_present :email

class Post < Ohm::Model
  reference :author, Author

The Comments

class Comment < Ohm::Model
  reference :post, Post

  attribute :body

  def validate
    assert_present :body

class Post < Ohm::Model
  collection :comments, Comment

Finding Posts

When you start off, there won't be any posts.

Post.all # => []
Post.size # => 0

Later, you can find your posts

Post.find(:slug => "ohm-rocks")

Or search by author. This is a bit tricky, since .find() always returns a collection, even if it's empty. So you need to .all to convert to an array, then [0] to get the first element of that array.

Author.find(:name => "Chris").all[0].posts

Creating Posts

chris = Author.find(:name => "Chris").all[0].posts
Post.create({:title => "Ohm Rocks Part 2", :body => "yep, it really does rock", :author => chris})

Creating Comments

p = Post[10]
p.comments << Comment.create(:body => "Very insightful, Ohm does indeed rock")

Looping Over Comments

This is simple, and just like normal Ruby.

p = Post[10]
p.comments.each do |comment|
  puts comment.body


Redis Data Security

Redis has a tuneable tradeoff between data security and speed. By default, Redis keeps all updates in memory for a few seconds, before writing them to disk. This gives Redis massive speed updates, but does have the tradeoff of potentially losing some data in a system crash. By changing a config file, you can have Redis write every change to the disk in real-time, preventing potential data loss.

Behind the Scenes

What keys is your data in?

read_local write_local

What are these really for?

How to best describe them