Object-hash mapping library for Redis.
Ohm is a library for storing objects in Redis, a persistent key-value database. It has very good performance.
Join the mailing list: http://groups.google.com/group/ohm-ruby
These are libraries in other languages that were inspired by Ohm.
- JOhm for Java, created by xetorthio
- Lohm for Lua, created by slact
- ohm.lua for Lua, created by amakawa
- Nohm for Node.js, created by maritz
- Redisco for Python, created by iamteem
- redis3m for C++, created by luca3m
Articles and Presentations
Install Redis. On most platforms it's as easy as grabbing the sources,
running make and then putting the
redis-server binary in the PATH.
Once you have it installed, you can execute
redis-server and it will
localhost:6379 by default. Check the
redis.conf file that comes
with the sources if you want to change some settings.
If you don't have Ohm, try this:
$ [sudo] gem install ohm
Or you can grab the code from http://github.com/soveran/ohm.
Now, in an irb session you can test the Redis adapter directly:
>> require "ohm" => true >> Ohm.redis.call "SET", "Foo", "Bar" => "OK" >> Ohm.redis.call "GET", "Foo" => "Bar"
Connecting to a Redis database
Ohm uses a lightweight Redis client called Redic. To connect
to a Redis database, you will need to set an instance of
an URL of the form
redis://:<passwd>@<host>:<port>/<db>, through the
Ohm.redis= method, e.g.
require "ohm" Ohm.redis = Redic.new("redis://127.0.0.1:6379") Ohm.redis.call "SET", "Foo", "Bar" Ohm.redis.call "GET", "Foo" # => "Bar"
Ohm defaults to a Redic connection to "redis://127.0.0.1:6379". The example above could be rewritten as:
require "ohm" Ohm.redis.call "SET", "Foo", "Bar" Ohm.redis.call "GET", "Foo" # => "Bar"
All Ohm models inherit the same connection settings from
For cases where certain models need to connect to different databases,
they simple have to override that, i.e.
require "ohm" Ohm.redis = Redic.new(ENV["REDIS_URL1"]) class User < Ohm::Model end User.redis = Redic.new(ENV["REDIS_URL2"])
Ohm's purpose in life is to map objects to a key value datastore. It doesn't need migrations or external schema definitions. Take a look at the example below:
class Event < Ohm::Model attribute :name reference :venue, :Venue set :participants, :Person counter :votes index :name end class Venue < Ohm::Model attribute :name collection :events, :Event end class Person < Ohm::Model attribute :name end
All models have the
id attribute built in, you don't need to declare it.
This is how you interact with IDs:
event = Event.create :name => "Ohm Worldwide Conference 2031" event.id # => 1 # Find an event by id event == Event # => true # Trying to find a non existent event Event # => nil # Finding all the events Event.all.to_a # => [<Event:1 name='Ohm Worldwide Conference 2031'>]
This example shows some basic features, like attribute declarations and querying. Keep reading to find out what you can do with models.
Ohm::Model provides 4 attribute types:
and 2 meta types:
attribute is just any value that can be stored as a string. In the
example above, we used this field to store the event's
name. You can
use it to store numbers, but be aware that Redis will return a string
when you retrieve the value.
set in Redis is an unordered list, with an external behavior similar
to that of Ruby arrays, but optimized for faster membership lookups.
It's used internally by Ohm to keep track of the instances of each model
and for generating and maintaining indexes.
list is like an array in Ruby. It's perfectly suited for queues
and for keeping elements in order.
counter is like a regular attribute, but the direct manipulation
of the value is not allowed. You can retrieve, increase or decrease
the value, but you can not assign it. In the example above, we used a
counter attribute for tracking votes. As the incr and decr operations
are atomic, you can rest assured a vote won't be counted twice.
It's a special kind of attribute that references another model. Internally, Ohm will keep a pointer to the model (its ID), but you get accessors that give you real instances. You can think of it as the model containing the foreign key to another model.
Provides an accessor to search for all models that
reference the current model.
The attributes declared with
attribute are only persisted after
Operations on attributes of type
possible only after the object is created (when it has an assigned
id). Any operation on these kinds of attributes is performed
immediately. This design yields better performance than buffering
the operations and waiting for a call to
For most use cases, this pattern doesn't represent a problem. If you are saving the object, this will suffice:
if event.save event.comments.add(Comment.create(body: "Wonderful event!")) end
Working with Sets
Given the following model declaration:
class Event < Ohm::Model attribute :name set :attendees, :Person end
You can add instances of
Person to the set of attendees with the
event.attendees.add(Person.create(name: "Albert")) # And now... event.attendees.each do |person| # ...do what you want with this person. end
attendees is a Set, it exposes two sorting
methods: sort returns the elements
id, and sort_by receives
a parameter with an attribute name, which will determine the sorting
order. Both methods receive an options hash which is explained below:
Order direction and strategy. You can pass in any of the following:
- ASC ALPHA (or ALPHA ASC)
- DESC ALPHA (or ALPHA DESC)
It defaults to
Important Note: Starting with Redis 2.6,
work with integers or floating point data types. If you need to sort
by an alphanumeric field, add the
The offset and limit from which we should start with. Note that
this is 0-indexed. It defaults to
limit: [0, 10] will get the first 10 entries starting from offset 0.
Key or Hash key with which to sort by. An important distinction with
using sort and
sort_by is that
converts the passed argument with the assumption that it is a hash key
and it's within the current model you are sorting.
Post.all.sort_by(:title) # SORT Post:all BY Post:*->title Post.all.sort(by: :title) # SORT Post:all BY title
Tip: Unless you absolutely know what you're doing, use
when you want to sort your models by their
id, and use
A key pattern to return, e.g.
Post:*->title. As is the case with
:by option, using sort and
sort_by has distinct differences in
sort_by does much of the hand-coding for you.
Post.all.sort_by(:title, get: :title) # SORT Post:all BY Post:*->title GET Post:*->title Post.all.sort(by: :title, get: :title) # SORT Post:all BY title GET title
Ohm lets you declare
collections to represent associations.
class Post < Ohm::Model attribute :title attribute :body collection :comments, :Comment end class Comment < Ohm::Model attribute :body reference :post, :Post end
After this, every time you refer to
post.comments you will be talking
about instances of the model
Comment. If you want to get a list of IDs
you can use
Doing ais actually just a shortcut for the following:
# Redefining our model above class Comment < Ohm::Model attribute :body attribute :post_id index :post_id def post=(post) self.post_id = post.id end def post Post[post_id] end end
The only difference with the actual implementation is that the model is memoized.
The net effect here is we can conveniently set and retrieve
and also search comments using the
The reason aand a go hand in hand, is that a collection is just a macro that defines a finder for you, and we know that to find a model by a field requires an to be defined for the field you want to search.
# Redefining our post above class Post < Ohm::Model attribute :title attribute :body def comments Comment.find(post_id: self.id) end end
The only "magic" happening is with the inference of the
index that was used
in the other model. The following all produce the same effect:
# easiest, with the basic assumption that the index is `:post_id` collection :comments, :Comment # we can explicitly declare this as follows too: collection :comments, :Comment, :post # finally, we can use the default argument for the third parameter which # is `to_reference`. collection :comments, :Comment, to_reference # exploring `to_reference` reveals a very interesting and simple concept: Post.to_reference == :post # => true
Anis a set that's handled automatically by Ohm. For any index declared, Ohm maintains different sets of objects IDs for quick lookups.
Event example, the index on the name attribute will
allow for searches like
Event.find(name: "some value").
Note that the methods find and except need a corresponding index in order to work.
You can find a collection of records with the
# This returns a collection of users with the username "Albert" User.find(username: "Albert")
# Find all users from Argentina User.find(country: "Argentina") # Find all active users from Argentina User.find(country: "Argentina", status: "active") # Find all active users from Argentina and Uruguay User.find(status: "active").combine(country: ["Argentina", "Uruguay"]) # Find all users from Argentina, except those with a suspended account. User.find(country: "Argentina").except(status: "suspended") # Find all users both from Argentina and Uruguay User.find(country: "Argentina").union(country: "Uruguay")
Note that calling these methods results in new sets being created on the fly. This is important so that you can perform further operations before reading the items to the client.
Uniques are similar to indices except that there can only be one record per entry. The canonical example of course would be the email of your user, e.g.
class User < Ohm::Model attribute :email unique :email end u = User.create(email: "email@example.com") u == User.with(:email, "firstname.lastname@example.org") # => true User.create(email: "email@example.com") # => raises Ohm::UniqueIndexViolation
Ohm is rather small and can be extended in many ways.
A lot of amazing contributions are available at Ohm Contrib make sure to check them if you need to extend Ohm's functionality.
Ohm 2 breaks the compatibility with previous versions. If you're upgrading an existing application, it's nice to have a good test coverage before going in. To know about fixes and changes, please refer to the CHANGELOG file.