Dynamic Persistence
for Dynamic Language

Rubernate User Guide

Table of Content

Getting Started
Persisten Objects
  Database Structure
  Dynamic Persistence
  Supported Types
Module Rubernate
Query Language
Class Runtime
TODO: ...


This guide shortly introduce you to main concepts of Rubernate. You'll see how Rubernate differs from traditional ORMs, how it can be used to manage peristence of Ruby objects and which advantages and disadvantages it has. It's very simple to use you can create and modify persistent classes without bother about database structure.


Rubernate depends on Ruby/DBI and Log4r so you should have them installed. Also you should have appropriate DBI driver for MySQL or Oracle.
Rubernate Gem can be got from project page. To install Rubernate use:

  gem install Rubernate-0.1.2.gem
Then you should create and initialize database that you want to use in your project. Rubernate creates two additional tables in your database. Fill free to change settings of these tables - add/remove indices and etc.

Data base can be initialized by following way

  require 'rubernate'

  Rubernate.config :mysql|:oracle|:pg, db_url , db_user, db_password

This program prints full initialization script and tries to run it. You can get this script here.

Getting Started

It is better to start witch example and take a look how Rubernate differs from usual ORMs. We'll use a common example - a Person persistent class.

  require 'rubernate'  
  class Person
    persistent :name, :surname
    def initialize name, surname = name
      self.surname = surname
That's all. Persistent class is created. You can get and use it.

Let's look what really happened:

  persistent :name, :surname
This line adds two attributes named 'name' and 'surname' to class Person (like attr_accessor) and makes them and whole class persistent. This means that you can store instances of this class and its subclasses to database and load them back. Moreover persistent adds several useful methods to class on which it is invoked. The most important of them are:
  • primary_key - read only property indicates primary key of object in database.
  • attach - attaches new object to session, primary key will be generated and image of that object will be stored in database.
  • remove! - removes object from database.

Let's create instance of class Person in database.
But first we should setup connection to database. It can be done using Rubernate.config. This method is invoked once per application to configure Rubernate. It accepts following parameters:

  1. Type of database, only :mysql and :oracle are supported now.
  2. Url of database
  3. User name or nil if authentication is disabled
  4. Password or nil if authentication is disabled
Rubernate uses Log4r so we can use it to track events happens in runtime. The easiest way to setup logger is - by hand in code. Next example demonstrates it.
  Rubernate.config :mysql, 'dbi:Mysql:rubernate_db:localhost', nil, nil
  Rubernate::Log.level = Log4r::DEBUG
      :filename => $0.sub('.rb', '.log'),
      :trunc => true)       
  Rubernate.session do
    homer ='Homer', 'Simpson').attach    
    puts "I'm in database now, my primary key is #{homer.primary_key}"
Done. Homer has been stored in database as attach has been invoked. Method Rubernate.session manages transaction. Module Rubernate will be described later.

Now we can load Homer by two ways. If we remember his primary key we can use Rubernate.find_by_pk, else we can use Rubernate.find_by_query and find object by name.

  Rubernate.session do
    homers = Rubernate.find_by_query 'Select :o; Where == :name',
        :name => 'Homer'
    puts 'No objects have been found' if homers.empty?
    puts "Homer has been found #{homers[0].primary_key}." if homers.size == 1
    puts "There are #{homers.size} persons with name Homer" if homers.size > 0
You can get full text of this example here. Rubernates query language will be explained later in this guide.

Persistent Objects

Note, that have created persistent class in previous example, we don't ever remembered about databases tables and other SQL stuff. This level of simplicity is achieved because of special databases structure used by rubernate.

Database Structure

All objects managed by rubernate are stored only in two tables r_objects and r_params which look like following

  r_objects       # This table hold records about all objects.      
    object_pk     # Objects primary key
    object_class  # Objects class
  r_params        # This table holds objects attributes values.    
    object_pk     # Objects primary key
    flags         # Parameters flags
    name          # Parameters name
    int_value     # Integer value
    flt_value     # Float value
    str_value     # String value
    dat_value     # Date and Time values
    ref_value     # Reference to other persistent object

Dynamic Persistence

Let's find out which advantages and disadvantages gives such approach to object persistense. An obvious advantage is great flexibility. Developing your persistent classes you are not longer restricted by database structure. You can even completely forget about it, just use function persistent if you want to make some attribute persistent.

Attributes made by persistent behave like ordinary attributes made by attr_accessor with one exception there is no private variable @attr_name created. You shoud access these attributes only by methods self.attr_name and self.attr_name=. Using persistent classes managed by ORMs, you can assign to attributes only values agreed with types of appropriate tables columns. Rubernate don't impose such strong constraint. Feel free to assign to persistent attributes values of any of supported types. It's fairly consistent with dynamic nature of Ruby language. Lets consider some examples illustrates this. We'll add new persistent attribute family to class Person.

  class Person
    persistent :name, :surname, :family
    def initialize name, surname = name
      self.surname = surname


Attribute family has been added, but we have still not decided how to represent family. The simplest way is to use Array.
  homers_pk = nil
  Rubernate.session do
    homer ='Homer', 'Simpson').attach  = ['Marge', 'Simpson').attach,'Burt', 'Simpson').attach,'Liza', 'Simpson').attach   ]

    homers_pk = homer.primary_key
We have created three new Persons and assign array with them it to Homers family property. Array will be successfully stored to database. But it istoo rough to representfamily as Array because it is hard to recognize who is who.


Hash appears to be more appropriate structure.
  Rubernate.session do
    homer = Rubernate.find_by_pk  homers_pk  = {
      'wife'     =>[0], # Marge
      'son'      =>[1], # Burt
      'daughter' =>[2]  # Liza


Hash is more convenient. But the best way is to create new class to represent family.
  class Family
    persistent :name, :father, :mother, :sons, :daughters  
    def initialize name = name
    # Returns all children of family
    def children
      sons + daughters
    def to_s
      "Father: #{father}\nMother: #{mother}\nChildren: #{children.join(', ')}"
It is not thought-out but for example it well enough.
  Rubernate.session do
    homer = Rubernate.find_by_pk homers_pk
    simpsons ='Simpsons').attach
    simpsons.father = homer
    simpsons.mother =['wife']
    simpsons.sons = [['son']] 
    simpsons.daughters = [['daughter']]

    puts simpsons = simpsons
If you want to all family members to be removed along with Family object use callback method on_remove. It can be something like this
  class Family
    # Removes all members along with family object
    def on_remove
      father.remove! if father
      mother.remove! if mother
      sons.each {|son| son.remove!} if sons
      daughters.each {|dt| dt.remove!} if daughters
Complete test of this example can be got here.

Supported Types

This table summarizes supported types and the way they are stored in database.
TypeField of r_params
Integer  int_value
Float  flt_value
String  str_value
Date  dat_value (converted during loading/storing)
Time  dat_value
Reference to Persistent Object  ref_value
Array of References  ref_value, index is stored in int_value
Hash of References,
with key of type:
Integer, String, Date or Time
 rev_value, key is stored in appropriate column


There are some drawback in the way Rubernate stores objects.

  • The most important of them is performance lack. TODO: As well as Ruby :) ...
  • Queries complexity. In comparsion with ordinary tables, SQL queries to Rubernates tables can be quite complex. For example query that finds all objects of class Person with property name equal 'Homer' can looks like following
      select o_.* from r_objects o_
        left outer join r_params o_name on (o_.object_pk = o_name.object_pk)
        where = 'name' and
          o_.object_class = 'Person' and
          o_name.str_value = 'Homer'
    However such queries can be much more flexible then queries to usual flat tables. They allow you, for example, to query all objects of certain class or its subclasses. Rubernates Query Language described later simplifies queries. See how previous example was built here.
  • Weak reference integrity. TODO: ...

Module Rubernate

Rubernate is main module of the library. It contains several useful methods most of them are available both as instance and as module methods. So you can include this module into your classes to access them. All persistent classes inclued this module by default.


This method performs configuration of Rubernate. It accepts four parameters

  1. db - type of databaes (:mysql or :oracle).
  2. url - url of database.
  3. user - databases user name
  4. password - databases users password


Prints database initialization sql script and tries to execute it. If database has already been initialized nothing happens with it. Else all necessary tables will be created and rubernte will be able to work properly. You can get sql script printed by this method, make any changes you need (add/change indexes, database engine for mysql and etc.) and run agains database by hand. Rubernate will still work with it.

  Rubernate.config :mysql, 'dbi:Mysql:rubernate_db:localhost', 'db_user', 'db_password'


This method creates new database transaction and object of type Rubernate::Runtime that manages that transaction. Returns created Runime object or pass it to block if given. All changes of persistent objects made in session boundaries will be stored to database. You should manually commit or rollback session by invoking corresponding method on Runtime object. If you use block, session do it for you unless exception rises.


  runtime = Rubernate.session 
  # Do smth...
  runtime.commit # Commit transaction
  Rubernate.session do |runtime|
  # Do smth...
  end # Commit transaction
Rolling back.
    Rubernate.session do
      raise 'I want to rollback transaction'
    puts 'Transaction rolledback'


Finds object by primary key. Raises Rubernate::ObjectNotFoundException if there is no one. This method is both module method and instance method.


Finds objects by query. This method accepts two kind of queries:
  • Rubernate Query Language. In this case the second argument (if ther is one) must be a Hash that maps query parameter marker (Symbol) to query parameter value of any of supported types.
      persons = Rubernate.find_by_query 'Select :p; Where p.klass == Person, 
         == :name, p.surname.str == :surname', 
            :name => 'John',
            :surname => 'Smith'
    You can get this example here.
  • Database native SQL. In this case second argument shoud be an array of query parameters. There is only one restriction imposed on SQL queries passed to this method - it must return resulting objects primary keys in first column of result set. Other columns except first won't be read.
      persons = Rubernate.find_by_query "select p_.* from r_objects p_
            left outer join r_params p_name on (p_.object_pk = p_name.object_pk)
            left outer join r_params p_surname on (p_.object_pk = p_surname.object_pk)
          where = 'name' and
     = 'surname' and
              p_.object_class = 'Person' and
              p_name.str_value = ? and
              p_surname.str_value = ?", ['John', 'Smith']  
    You can get working script here.


This method retruns DBI::DatabaseHandle used in this session.

Query Language

As we have seen writing native sql queries to Rubernate database is too tedious. So there is query language built in. This language is just Ruby tailored to build SQL queries. Ordinary query on this language looks like following

  Select(:o, :f)
  Where ( And(Eq(,, 
              Eq(, :name)))
Or in short form
  Select :o, :f; Where ==, == :name
This query finds all objects 'o' whose attribute 'family' refers to other object 'p' whose attribute 'name' has certain value marked as ':name'. It will be translated to following sql
  select o_.* from 
      r_objects f_ left outer join r_params f_name   on (f_.object_pk = f_name.object_pk),
      r_objects o_ left outer join r_params o_family on (o_.object_pk = o_family.object_pk)
    where = 'family' and   = 'name' and
      o_family.ref_value = f_.object_pk and
      f_name.str_value = ?
All in this query are plain Ruby functions.
  • Select - accepts any number of Symbols that will be treated as virtual tables of objects. This means that they will appear in FROM sql clause. The first of them will be result of query i.e. it will appear in SELECT sql clause (see previous example). This function also creates corresponding methods in context of query so you can access them in query.
  • Where - accepts any number of expressions that will be treated as constraints and will be listed in WHERE sql clause through the AND operator.
  • [obj_name].[attr_name] - generates left outer join from r_object to r_param and appends condition on field 'name' of table r_params. E.g. will be translated to join and constraint like follow
      select *...
        from r_objects o_ left outer join r_params o_family on (o_.object_pk = o_family.object_pk)
        where = 'family'  # constraing on attribute name
  • [obj_name].[attr_name].[field] - access to certain field of r_params, [field] can be:
    pk - correspond to r_params.object_pk
    name -
    flags - r_params.flags
    int - r_params.int_value
    float - r_params.flt_value
    str - r_params.str_value
    time - r_params.dat_value
    date - r_params.dat_value
    ref - r_params.ref_value
    These functions are presented in class Rubernate::Queryes::RParam. For example: will be translated to
      ... = 'family' and o_family.ref_value
  • And - accepsts any number of expression and orders them through AND.
  • Or - the same as And exept it builds expresson with OR.
  • Eq or '==' - accepts two arguments and creates sql equality '=' clause. So == translates to
      ... o_family.ref_value = f_.object_pk
These and other functions can be found in module Rubernate::Queries::Operations and in class Rubernate::Queries::Query.

Class Runtime

Runtime is central class of the library. Instances of this class are created and associated witch each session. It holds objects loaded during session, and other session-related data. It's abstract class so actual working with database is implemented in subclasses.
  • begin()
  • - Begins session and starts transaction. This method is invoked by Rubernate.
  • commit()
  • - Stores all changes and commits transction.
  • rollback()
  • - Stores all changes and commits transction.
  • attach(object)
  • - Attaches new object to session and persists it in database. This method is actually invoked when you call attach on persistent object.
  • remove(object)
  • - Removes object. This method is actually invoked when you call remove! on persistent object.
  • find_by_pk(pk)
  • - Finds object by primary key. Raises ObjectNotFoundException if object was not found. There is also shortcut method in module Rubernate.find_by_pk
  • find_by_query(query, params)
  • - Finds objects by query (SQL/Query Language).
  • flush_modified()
  • - Stores all modified objects to database. This method is invoked before transaction commit and before each invokation of find_by_query(). You can invoke this method manually if you want to store all changes in your objects to database immediately.



    Copyright (C) 2006 Andrey Ryabov