Persisting Objects with Madeleine

Problem

You want to store objects in RAM and persist them between independent executions of the program. This will let your program recall its state indefinitely and access it very quickly.

Solution

Use the Madeleine library available as the madeleine gem. It transparently persists any Ruby object that can be serialized with Marshal. Unlike a conventional database persistence layer, Madeleine keeps all of its objects in RAM at all times.

To use Madeleine, you have to decide which objects in your system need to be serialized, and which ones you might have saved to a database traditionally. Heres a simple Madeleine-backed program for conducting yes/no polls, in which agreement adds one to a total and disagreement subtracts one:

	#!/usr/bin/ruby -w
	# poll.rb
	require 
ubygems
	require madeleine

	class Poll
	 attr_accessor :name
	 attr_reader :total

	 def initialize(name)
	 @name = name
	 @total = 0
	 end

	 def agree
	 @total += 1
	 end

	 def disagree
	 @total -= 1
	 end
	end

So far theres been no Madeleine code, just a normal class with instance variables and accessors. But how will we store the state of the poll between invocations of the polling program? Since instances of the Poll class can be serialized with Marshall, we can wrap a Poll object in a MadeleineSnapshot, and keep it in a file:

	poll = SnapshotMadeleine.new(poll_data) do
	 Poll.new(Is Ruby great?)
	end

The system accessor retrieves the object wrapped by MadeleineSnapshot:

	if ARGV[0] == agree
	 poll.system.agree
	elsif ARGV[0] == disagree
	 poll.system.disagree
	end

	puts "Name: #{poll.system.name}"
	puts "Total: #{poll.system.total}"

You can save the current state of the object with take_snapshot:

	poll.take_snapshot

Here are a few sample runs of the poll.rb program:

	$ ruby poll.rb agree
	Name: Is Ruby great?
	Total: 1

	$ ruby poll.rb agree
	Name: Is Ruby great?
	Total: 2

	$ ruby poll.rb disagree
	Name: Is Ruby great?
	Total: 1

Discussion

Recall this piece of code:

	poll = SnapshotMadeleine.new(poll_data) do
	 Poll.new(Is Ruby great?)
	end

The first time that code is run, Madeleine creates a directory called poll_data. Then it runs the code block. The result of the code block is the object whose state will be tracked in the poll_data directory.

On subsequent runs, the poll_data directory already exists, and Madeleine loads the current state of the Poll object from the latest snapshot in the directory. It doesn run the code block.

Here are the contents of poll_data after we run the program three times:

	$ ls poll_data
	000000000000000000001.snapshot
	000000000000000000002.snapshot
	000000000000000000003.snapshot

Every time we call poll.take_snapshot, Madeleine serializes the Poll object to a snapshot file in poll_data. If the data ever gets corrupted, you can remove the corrupted snapshot files and revert to a previous version of the data.

A clever trick for programs like our poll application is to use Kernel#at_exit to automatically save the state of an object when the program ends. This way, even if your program is killed by a Unix signal, or throws an exception, your data will be saved.[5]

[5] Of course, these things might happen when your data is in an inconsistent state and you don want it to be saved.

	at_exit { poll.take_snapshot }

In applications where a process runs indefinitely, you can save snapshots at regular intervals by spawning a separate thread:

	def save_recurring_snapshots(madeleine_object, time_interval)
	 loop do
	 madeleine_object.take_snapshot
	 sleep time_interval
	 end
	end

	Thread.new { save_recurring_snapshots(poll, 24*60*60) }

See Also

  • Recipe 3.12, "Running a Code Block Periodically"
  • Recipe 13.2, "Serializing Data with Marshal"
  • The Madeleine design rules document lays out the conditions your code must meet if you want to snapshot it with Madeleine (http://madeleine.sourceforge.net/docs/designRules.html)
  • The RDoc documentation for Madeleine (http://madeleine.sourceforge.net/docs/api/)
  • For more on the technique of object prevalence, see the web site for the Prevayler Java project, especially the "Articles" section (http://www.prevayler.org/wiki.jsp)


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net