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. |