Getting to know Madeleine

On my quest for more ruby skills I’m currently trying to get to know Madeleine. No, not a girl, although this would be quite a welcome change for me actually. I’m talking about the object persistancy layer for Ruby.


Until now I wasn’t able to find a good tutorial for Madeleine so this will be probably as much an adventure for you as it will be for me. I will simply write what I found out about Madeleine so far and hope, that it will also help myself to get to know this beautiful g… system more.

For now let’s start with the purpose of Madeleine as far as I’ve gasped it: With Madeleine you can basically make a Ruby object persistant so that you won’t have to manually load it the next time again. Such a persistant object can be accessed for reading and writing. Both kinds have their own wrapper methods which makes it easier for Madeleine to determine when data is about to be changed. To save the data a snapshot system is used. Only changes from the last snapshot get into the new one.

Here also comes the first thing I’ve noticed that I want to change but haven’t found out how yet: The persistant object has the method take_snapshot which generates a new snapshot … no matter if anything was changed at all. Anybody else smells the stench of dying inodes here?

Ok, enough of the theory for now. Let’s get our hands dirty a little bit. So let’s install madeleine and include it into our new playground.rb.

require 'rubygems'
require_gem 'madeleine'

First we should decide what kind of data we want to make persistant. During this tutorial I will simply use a wrapper class for an array:

class TestContainer
    def initialize
        @data = []
    end
    def each
        @data.each{|field| yield field}
    end
    def << (element)
        @data << element
    end
end

As I’ve already mentioned before, Madeleine provides a wrapper which has two methods for accessing the data:

execute_query
... is used to access the object for reading while …
execute_command
... executes a command that can also change the state of the object. So I’d guess that it holds an exclusive lock on it.

Both methods take a socalled Command object as parameter. Nothing really special here: A Command object is simply an object offering an execute method. Since we have a data container let’s create two commands. One for iterating about the whole content and one for appending new stuff to the array.

class AppendCommand
    def initialize(data)
        @data = data
    end
    def execute(system)
        system << @data
    end
end
class QueryCommand
    def execute(system)
        system.each {|f| puts f}
    end
end

The execute method has one single argument that basically holds the object as we know it. So system in the AppendCommand and QueryCommand objects will simply be a TestContainer object.

Let’s bring the persistancy into all this, shall we?


if $0 == __FILE__
    madeleine = SnapshotMadeleine.new("storage") do
        TestContainer.new
    end
    if ARGV[0] == '-a'
        cmd = AppendCommand.new("#")
        madeleine.execute_command(cmd)
    else
        cmd = QueryCommand.new
        madeleine.execute_query(cmd)
    end
    madeleine.take_snapshot
end

We create or storage using the SnapshotMadeleine class which takes as first parameter the folder where the presistant object should be stored and a block returning the object we want to make persistant. In our example a new instance of a TestContainer. Now we can use the execute_query and execute_command methods on the persistant object using our two Command classes. Since we want to make this whole thing persistant after all, we execute the take_snapshot method before exiting the script.

To save some inodes you should probably create a policy about when to take snapshots. For example only every hour or if a modifying command has been executed ;)

In instiki they seem to have an additional logfile that holds all the changes to the object that haven’t been committed into a snapshot yet. I’m not sure though, if this is a feature of madeleine oder an addition by instiki.