Performing Higher-Level Data Access

 
   

Ruby Way
By Hal Fulton
Slots : 1.0
Table of Contents
 


Frequently we want to store and retrieve data in a more transparent manner. The Marshal module offers simple object persistence, and the PStore library builds on that functionality. Finally, the dbm library is used like a hash stored permanently on disk. It does not truly belong in this section, but it is rather too simple to put in the database section.

Simple Marshaling

In many cases, we would like to create an object and simply save it for use later. Ruby provides rudimentary support for such object persistence or marshaling. The Marshal module enables programs to serialize and unserialize Ruby objects in this way:

 

 # array of elements [composer, work, minutes] works = [["Leonard Bernstein","Overture to Candide",11],          ["Aaron Copland","Symphony No. 3",45],          ["Jean Sibelius","Finlandia",20]] # We want to keep this for later... File.open("store","w") do |file|   Marshal.dump(works,file) end # Much later... File.open("store") do |file|   works = Marshal.load(file) end 

This technique does have the shortcoming that not all objects can be dumped. If an object includes an object of a fairly low-level class, it cannot be marshaled; these include IO, Proc, and a few others. Singleton objects also cannot be serialized.

More Complex Marshaling

Sometimes we want to customize our marshaling to some extent. Creating _load and _dump methods will enable you to do this. These hooks are called when marshaling is done so that you are handling your own conversion to and from a string.

In this example, a person has been earning five percent interest on his beginning balance since he was born. We don't store the age and the current balance since they are a function of time:

 

 class Person   def initialize(name,birthdate,beginning)     @name = name     @birthdate = birthdate     @beginning = beginning     @age = (Time.now - @birthdate)/(365*86400)     @balance = @beginning*(1.05**age)   end   def _dump(depth)     # (We ignore depth here)     @name + ":" + @birthdate + ":" + @beginning   end   def _load(str)     a, b, c = str.split(":")     Person.new(a,b,c)   end   # Other methods... end 

When an object of this type is saved, the age and current balance will not be stored; when the object is "reconstituted," they will be computed.

Performing Limited "Deep Copying" Using Marshal

Ruby has no "deep copy" operation. The methods dup and clone may not always work as you would initially expect. An object may contain nested object references that turn a copy operation into a game of Pick Up Sticks.

We offer here a way to handle a restricted deep copy (it is restricted because it is still based on Marshal and has the same inherent limitations):

 

 def deep_copy(obj)    Marshal.load(Marshal.dump(obj)) end a = deep_copy(b) 

Better Object Persistence with PStore

The PStore library provides file-based persistent storage of Ruby objects. A PStore object can hold a number of Ruby object hierarchies. Each hierarchy has a root identified by a key. Hierarchies are read from a disk file at the start of a transaction and written back at the end. Here's an example:

 

 require "pstore" # save db = PStore.new("employee.dat") db.transaction do     db["params"] = { "name" => "Fred", "age" => 32,                     "salary" => 48000 } end # retrieve require "pstore" db = PStore.new("employee.dat") emp = nil db.transaction {  emp = db["params"] } 

Typically, within a transaction block we use the PStore object passed in. We can also use the receiver directly, however.

This technique is transaction oriented; at the start of the block, data is retrieved from the disk file to be manipulated. Afterward, it is transparently written back out to disk.

In the middle of a transaction, we can interrupt with either commit or abort; the former will keep the changes we have made, whereas the latter will throw them away. Refer to the longer example in Listing 4.2.

Listing 4.2 Using PStore
 require "pstore" store = PStore.new("objects") store.transaction do |s|   a = s["my_array"]   h = s["my_hash"]   # Imaginary code omitted, manipulating   # a, h, etc.   # Assume a variable named "condition" having   # the value 1, 2, or 3...   case condition     when 1       puts "Oops... aborting."       s.abort   # Changes will be lost.     when 2       puts "Committing and jumping out."       s.commit  # Changes will be saved.     when 3       # Do nothing...   end   puts "We finished the transaction to the end."   # Changes will be saved. end 

Within a transaction, you can also use the method roots to return an array of roots (or root? to test membership). Also, the delete method is available to remove a root. Here' an example:

 

 store.transaction do |s|   list = s.roots          # ["my_array","my_hash"]   if s.root?("my_tree")     puts "Found my_tree."   else     puts "Didn't find # my_tree."   end   s.delete("my_hash")   list2 = s.roots         # ["my_array"] end 

Using the dbm Library

The dbm library is a simple platform-independent, string-based hash, file-storage mechanism. It stores a key and some associated data, both of which must be strings. Ruby's dbm interface is built in to the standard installation.

To use this class, create a dbm object associated with a filename and work with the string-based hash however you want. When you have finished, you should close the file. Here's an example:

 

 require 'dbm' d = DBM.new("data") d["123"] = "toodle-oo!" puts d["123"]        # "toodle-oo!" d.close puts d["123"]        # RuntimeError: closed DBM file e = DBM.open("data") e["123"]                # "toodle-oo!" w=e.to_hash                # { "123"=>"toodle-oo!"} e.close e["123"]                # RuntimeError: closed DBM file w["123"]                # "toodle-oo! 

Here, dbm is implemented as a single class that mixes in Enumerable. The two (aliased) class methods, new and open, are singletons, which means you may only have one dbm object per data file open at any given time:

 

 q=DBM.new("data.dbm")   # f=DBM.open("data.dbm")  # Errno::EWOULDBLOCK:                         #   Try again - "data.dbm" 

There are 34 instance methods, many of which are aliases or similar to the hash methods. Basically, if you are used to manipulating a real hash in a certain way, there is a good chance you can apply the same operation to a dbm object.

The method to_hash will make a copy of the hash file object in memory, and close will permanently close the link to the hash file. Most of the rest of the methods are analogous to hash methods, but there are no rehash, sort, default, and default= methods. The to_s method just returns a string representation of the object ID.


   

 

 



The Ruby Way
The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming (2nd Edition)
ISBN: 0672328844
EAN: 2147483647
Year: 2000
Pages: 119
Authors: Hal Fulton

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