Saving Singletons

Let’s take a look at a concrete example of a problem mentioned earlier, namely, the inability to use marshaling to save and load a singleton. In singleton_m.rb I have created an instance of Object, ob, and then extended it in the form of a singleton class that is given the additional method, xxx:

singleton_m.rb

ob = Object.new

class << ob
    def xxx( aStr )
        @x = aStr
    end
end

The problem arises when I try to save this data to disk using Marshal.dump. Ruby displays an error message: “singleton can’t be dumped (TypeError).”

YAML and Singletons

Before considering how you might deal with this, let’s briefly take a look at how YAML would cope in this situation. The program singleton_y.rb tries to save the singleton that I created a moment ago using YAML.dump, and, unlike Marshal.dump, it succeeds—well, sort of:

singleton_y.rb

# YAML version of singleton-save
ob.xxx( "hello world" )

File.open( 'test.yml', 'w' ){ |f|
    YAML.dump( ob, f )
}

ob.xxx( "new string" )

File.open( 'test.yml' ){ |f|
    ob = YAML.load(f)
}

If you look at the YAML file that is saved, test.yml, you’ll find that it defines an instance of a plain-vanilla Object to which a variable named x is appended that has the string value hello world:

--- !ruby/object
x: hello world

That’s all well and good. However, when you reconstruct the object by loading the saved data, the new ob will be a standard instance of Object, which happens to contain an additional instance variable, @x. Since it is no longer the original singleton, this ob will not have access to any of the methods (here the xxx method) defined in that singleton. So, although YAML serialization is more permissive about saving and loading data items that were created in a singleton, it does not automatically re-create the singleton itself when the saved data is reloaded.

Marshal and Singletons

Let’s now return to the Marshal version of this program. The first thing I need to do is find a way of at least making it save and load data items. Once I’ve done that, I’ll try to figure out how to reconstruct singletons on reloading.

To save specific data items, I can define the marshal_dump and marshal_load methods as explained earlier (see limit_m.rb). These should normally be defined in a class from which the singleton derives, not in the singleton itself. This is because, as already explained, when the data is saved, it will be stored as a representation of the class from which the singleton derives. This means that although you could indeed add marshal_dump to a singleton derived from class X, when you reconstruct the object, you will be loading data for an object of the generic type X, not of the specific singleton instance.

This code creates a singleton, ob, of class X, saves its data, and then re-creates a generic object of class X:

singleton_m2.rb

class X
    def marshal_dump
        [@x]
    end

    def marshal_load(data)
        @x = data[0]
    end
end

ob = X.new

class << ob
    def xxx( aStr )
        @x = aStr
    end
end

ob.xxx( "hello" )
p( ob )

File.open( 'test2.sav', 'w' ){ |f|
    Marshal.dump( ob, f )
}

ob.xxx( "new string" )
p( ob )
File.open( 'test2.sav' ){ |f|
    ob = Marshal.load(f)
}

p( ob )

The code here uses Marshal.dump to save an object, ob, of class X and then calls the singleton method, xxx, to assign a different string to the @x variable before reloading the saved data using Marshal.load and using this data to re-create the object. The contents of ob are displayed using p() before it is saved, then again after a new string is assigned to it, and finally once again when it is reloaded. This lets you verify that @x is assigned the value that was saved when the reloaded object is reconstructed:

#<X:0x2b86cc0 @x="hello">       # value when saved
#<X:0x2b86cc0 @x="new string">  # new value then assigned
#<X:0x2b869f0 @x="hello">       # value after saved data loaded

In terms of the data it contains, the object saved and the object reloaded are identical. However, the object that is reloaded knows nothing about the singleton class. The method xxx that the singleton class contains forms no part of the reconstructed object. The following, then, would fail:

ob.xxx( "this fails" )

This Marshal version of the code is equivalent to the YAML version given earlier. It saves and restores the data correctly, but it does not reconstruct the singleton. How, then, is it possible to reconstruct a singleton from saved data? There are, no doubt, many clever and subtle ways in which this might be accomplished. I shall, however, opt for a very simple technique:

singleton_m3.rb

FILENAME = 'test2.sav'

class X
    def marshal_dump
        [@x]
    end

    def marshal_load(data)
        @x = data[0]
    end
end

ob = X.new

# a) if File exists, load data into ob - a generic X object
if File.exists?(FILENAME) then
    File.open(FILENAME){ |f|
        ob = Marshal.load(f)
    }
else
    puts( "Saved data can't be found" )
end
# b) Now transform ob in a singleton
class << ob
    def xxx=( aStr )
        @x = aStr
    end

    def xxx
        return @x
    end
end

This code first checks whether a file containing the saved data can be found. (This sample has been kept deliberately simple—in a real application you would of course need to write some exception-handling code to deal with the possibility of reading in invalid data.) If the file is found, the data is loaded into an object of the generic X type.

Only when this has been done is this object “transformed” into a singleton in the usual way. In other words, the object is loaded, and then the code beginning class << ob executes (simply because the singleton-creation code occurs after the loading code and so is executed in sequence by the Ruby interpreter). This provides the object with the additional xxx singleton method. You can then save the new data back to disk and reload and re-create the modified singleton, as explained earlier, at a later stage:

if ob.xxx == "hello" then
   ob.xxx = "goodbye"
else
   ob.xxx = "hello"
end

File.open( FILENAME, 'w' ){ |f|
    Marshal.dump( ob, f )
}

If you wanted to save and load singletons in a real application, the singleton “reconstruction” code could, naturally, be given its own method so that you don’t have to rely upon its position in your code as in the previous example.

singleton_m4.rb

def makeIntoSingleton( someOb )
   class << someOb
      def xxx=( aStr )
         @x = aStr
      end

      def xxx
         return @x
      end
   end
   return someOb
end
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.217.198