Zermelo
Ruby ActiveModel-based ORM for Redis
Install / Use
/learn @flapjack/ZermeloREADME
zermelo
Zermelo is an ActiveModel-based Object-Relational Mapper for Redis, written in Ruby.
Installation
Add this line to your application's Gemfile:
gem 'zermelo', :github => 'flapjack/zermelo', :branch => 'master'
And then execute:
$ bundle
Or install it yourself as:
$ gem install zermelo
Requirements
-
Redis 2.4.0 or higher (as it uses the multiple arguments provided in 2.4 for some commands). This could probaby be lowered to 2.0 with some branching for backwards compatibility.
-
Ruby 1.8.7 or higher.
Usage
Initialisation
Firstly, you'll need to set up zermelo's Redis access, e.g.
Zermelo.redis = Redis.new(:host => '127.0.0.1', :db => 8)
You can optionally set Zermelo.logger to an instance of a Ruby Logger class, or something with a compatible interface, and Zermelo will log the method calls (and arguments) being made to the Redis driver.
Class ids
Include zermelo's Zermelo::Records::Redis module in the class you want to persist data from:
class Post
include Zermelo::Records::Redis
end
and then create and save an instance of that class:
post = Post.new(:id => 'abcde')
post.save
Behind the scenes, this will run the following Redis command:
SADD post::attrs:ids 'abcde'
(along with a few others which we'll discuss shortly).
Simple instance attributes
A data record without any actual data isn't very useful, so let's add a few simple data fields to the Post model:
class Post
include Zermelo::Records::Redis
define_attributes :title => :string,
:score => :integer,
:timestamp => :timestamp,
:published => :boolean
end
and create and save an instance of that model class:
post = Post.new(:title => 'Introduction to Zermelo',
:score => 100, :timestamp => Time.parse('Jan 1 2000'), :published => false)
post.save
An :id => :string attribute is implicitly defined, but in this case no id was passed, so zermelo generates a UUID:
HMSET post:03c839ac-24af-432e-aa58-fd1d4bf73f24:attrs title 'Introduction to Zermelo' score 100 timestamp 1384473626.36478 published 'false'
SADD post::attrs:ids 03c839ac-24af-432e-aa58-fd1d4bf73f24
which can then be verified by inspection of the object's attributes, e.g.:
post.attributes.inspect # == {:id => '03c839ac-24af-432e-aa58-fd1d4bf73f24', :title => 'Introduction to Zermelo', :score => 100, :timestamp => '2000-01-01 00:00:00 UTC', :published => false}
Zermelo supports the following simple attribute types, and automatically validates that the values are of the correct class, casting if possible:
| Type | Ruby class | Notes | |------------|-------------------------------|-------| | :string | String | | | :integer | Integer | | | :float | Float | | | :id | String | | | :timestamp | Integer or Time or DateTime | Stored as a float value | | :boolean | TrueClass or FalseClass | Stored as string 'true' or 'false' |
Complex instance attributes
Zermelo also provides mappings for the compound data structures supported by Redis.
So if we add tags to the Post data definition:
class Post
include Zermelo::Records::Redis
define_attributes :title => :string,
:score => :integer,
:timestamp => :timestamp,
:published => :boolean,
:tags => :set
end
and then create another Post instance:
post = Post.new(:id => 1, :tags => Set.new(['database', 'ORM']))
post.save
which would run the following Redis commands:
SADD post:1:attrs:tags 'database' 'ORM'
SADD post::attrs:ids 1
Zermelo supports the following complex attribute types, and automatically validates that the values are of the correct class, casting if possible:
| Type | Ruby class | Notes | |-------------|---------------|---------------------------------------------------------| | :list | Enumerable | Stored as a Redis LIST | | :set | Array or Set | Stored as a Redis SET | | :hash | Hash | Stored as a Redis HASH | | :sorted_set | Enumerable | Stored as a Redis ZSET |
Structure data members must be primitives that will cast OK to and from Redis via the driver, thus String, Integer and Float.
Redis sorted sets are also supported through zermelo's associations (recommended due to the fact that queries can be constructed against them).
Validations
All of the validations offered by ActiveModel are available in zermelo objects.
So an attribute which should be present:
class Post
include Zermelo::Records::Redis
define_attributes :title => :string,
:score => :integer
validates :title, :presence => true
end
but isn't:
post = Post.new(:score => 85)
post.valid? # == false
post.errors.full_messages # == ["Title can't be blank"]
post.save # calls valid? before saving, fails and returns false
produces the results you would expect.
Callbacks
ActiveModel's lifecycle callbacks are also supported, and zermelo uses similar invocations to ActiveRecord's:
before_create, around_create, after_create,
before_update, around_update, after_update,
before_destroy, around_destroy, after_destroy
As noted in the linked documentation, you'll need to yield from within an around_* callback, or the original action won't be carried out.
Detecting changes
Another feature added by ActiveModel is the ability to detect changed data in record instances using ActiveModel::Dirty.
Locking around changes
Zermelo will lock operations to ensure that changes are applied consistently. The locking code is based on redis-lock, but has been extended and customised to allow zermelo to lock more than one class at a time. Record saving and destroying is implicitly locked, while if you want to carry out complex queries or changes without worring about what else may be changing data at the same time, you can use the lock class method as follows:
class Author
include Zermelo::Records::Redis
end
class Post
include Zermelo::Records::Redis
end
class Comment
include Zermelo::Records::Redis
end
Author.lock(Post, Comment) do
# ... complicated data operations ...
end
Loading data
Assuming a saved Post instance has been created:
class Post
include Zermelo::Records::Redis
define_attributes :title => :string,
:score => :integer,
:timestamp => :timestamp,
:published => :boolean
end
post = Post.new(:id => '1234', :title => 'Introduction to Zermelo',
:score => 100, :timestamp => Time.parse('Jan 1 2000')), :published => false)
post.save
which executes the following Redis calls:
HMSET post:1234:attrs title 'Introduction to Zermelo' score 100 timestamp 1384473626.36478 published 'false'
SADD post::attrs:ids 1234
This data can be loaded into a fresh Post instance using the find_by_id(ID) class method:
same_post = Post.find_by_id('1234')
same_post.attributes # == {:id => '1234', :score => 100, :timestamp => '2000-01-01 00:00:00 UTC', :published => false}
You can load more than one record using the find_by_ids(ID, ID, ...) class method (returns an array), and raise exceptions if records matching the ids are not found using find_by_id!(ID) and find_by_ids!(ID, ID, ...).
Class methods
Classes that include Zermelo::Record have the following class methods made available to them.
|Name | Arguments | Returns |
|-------------------------|---------------|---------|
|all | | Returns a Set of all the records stored for this class |
|each | | Yields all records to the provided block, returns the same Set as .all(): Enumerable#each |
|collect / map | | Yields all records to the provided block, returns an Array with the values returned from the block: Enumerable#collect |
|select / find_all | | Yields all records to the provided block, returns an Array with each record where the block returned true: Enumerable#select |
|reject | | Yields all records to the provided block, returns an Array with each record where the block returned false: Enumerable#reject |
|ids | | Returns a Set with the ids of all stored records |
|count | | Returns an Integer count of the number of stored records |
|empty? | | Returns true if no records are stored, false otherwise |
|destroy_all
Related Skills
node-connect
349.7kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
109.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
349.7kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
349.7kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。

