matz / netlab.co.jp (Yukihiro Matsumoto) writes:

> | * For distributed applications, and even for local applications, a remote
> |method invocation mechanism (known as RPC or RMI) might be desirable. this
> |includes serialization, virtual objects by delegation, message streams,
> |callback on return, and authorization (specifying what you can do, not
> |what you cannot). Ideal for client-server, sandboxing, and for avoiding
> |threads (threadophobia) where event queues are not appropriate. [3-10
> |classes]
> 
> dRuby (distributed Ruby) accopmplish at least part of your desire.
> The greatest problem of eRuby is no English document is available.
> Try http://www2a.biglobe.ne.jp/~seki/ruby/drb-1.1.tar.gz.  It's all
> written in Ruby.

We have a some information and examples dRuby in the book. Here's the
info, along with some related stuff on Marshalling the dRuby examples
are at the end). Hope you don't mind wading through the LaTeX! (And
please--this stuff hasn't yet been reviewed or proof-read. The errors
are there on purpose to give the reviewers something to do ;-)


     %-------------------------------------------------------------
     \section{Marshalling and Distributed Ruby}
     %-------------------------------------------------------------

     Java features the ability to \emph{serialize} objects, so that
     you might store them off somewhere and reconstitute them when
     needed.  You might use this facility, for instance, to save a
     tree of objects that represent some portion of applicatiomn
     state---a document, a CAD drawing, piece of music, and so on.

     Ruby implements the serialization---the marshalling---of objects using 
     the \M{Marshal} module.  Saving an object and some or all of its
     components is done using the method \MMM{Marshal.dump}.  Typically
     you will dump an entire object tree starting with the given object as
     a root; all objects that root object references will be dumped as
     well.

     But not all objects can be dumped: bindings, procedure objects,
     instances of class \C{IO}, and singleton objects cannot be saved
     outside of the running Ruby environment (a \Ei{TypeError} will be
     raised if you try).

     Here's a short example:

     \begin{ruby}
       class Note
         def initialize(val)
           @val = val
         end
         def value
           @val
         end
         def to_s
           @val.to_s
         end
       end

       class Chord
         def initialize(arr)
           @arr = arr
         end
         def value
           @arr
         end
         def to_s
           @arr.join('-')
         end
       end

       c = Chord.new( [Note.new("C"), Note.new("E"), Note.new("Bb")] )
       File.open("test_marshal", "w+") { |f| f.write Marshal.dump(c) }

       # Later, in another process:
       f = File.new("test_marshal")
       blob = f.read
       root = Marshal.load(blob)
       puts root
     \end{ruby}

     %-----------------------------------------
     \subsection{Custom Serialization Strategy}
     %-----------------------------------------

     But what if you want to define the serialization format for a
     particular object yourself?  What if your class contains
     instances of \C{Binding}, or \C{Proc}, or even just a regular
     \C{Object} that you do not wish to serialize?  Ruby being Ruby,
     of course, you can define your own methods to serialize a class.

     For instance, here is a sample class that defines its own
     serialization, using \METHOD{\_dump} and \METHOD{\_load}. For
     whatever reasons, \C{Special} doesn't want to save one of its
     internal data members, named ``\VAR{@volatile}''.

     \begin{ruby}
       class Special
         def initialize(valuable)
           @valuable = valuable
           @volatile = "Goodbye"
         end

         def _dump(depth)
           @valuable.to_str
         end

         def Special._load(str)
           result = Special.new(str);
         end

         def to_s
           "#{@valuable} and #{@volatile}"
         end
       end

       a = Special.new("Hello, World")
       data = Marshal.dump(a)
       obj = Marshal.load(data)
       puts obj

     \end{ruby}

     For more details, see the reference section on \M{Marshal}
     beginning \vpageref{ref:marshal}.

     %-----------------------------------------
     \subsection{Distributed Ruby}
     %-----------------------------------------

     Since we have the capability to serialize an object or a set of
     objects into a form suitable for out-of-process storage, we can also
     use this capability for the \emph{transmission} of objects from one
     process to another.  Couple this capability with a simple mechanism
     for remote message sends, and \emph{voil\`{a}}: you have a distributed
     object system.  To save you the trouble of having to write the code,
     may we suggest downloading the Distributed Ruby library (drb) from the
     archives.

     Using drb, a Ruby process may act as a server, as a client, or both.

     A server starts a service by associating an object with a given port.
     Threads are created internally to handle incoming requests on that
     port, so don't just exit after starting the service.

     \begin{ruby}[norun]
       require 'drb'

       class TestServer
         def doit
           "Hello, Distributed World"
         end
       end

       aServerObject = TestServer.new
       DRb.start_service('druby://localhost:9000', aServerObject)
       DRb.thread.join # Don't exit just yet!
     \end{ruby}

     The client starts off, and may optionally start on a given port as
     well, and may associate an object with the port. We are acting solely
     as a client in this case, so we don't bother. The client makes a new
     local object that acts as a proxy to the given remote object using
     \CF{DRbObject}, and life goes on normally.

     \begin{ruby}[norun]
       require 'drb'
       DRb.start_service()
       obj = DRbObject.new(nil, 'druby://localhost:9000')
       # Now use obj
       p obj.doit
     \end{ruby}

     The client connects to the server, calls the method \CF{doit} which
     returns a string that the client prints out:

     \begin{ruby}[norun]
       "Hello, Distributed World"
     \end{ruby}

     The initial \nil{} argument to \CF{DRbObject} indicates that we want
     to attach to a new distributed object.  We could also provide an
     existing object instead.

     Yawn, you say, sounds like Java's RMI or CORBA or whatever.  Yes, it
     is a functional distributed object mechanism.  But it is written in
     just 200 lines of Ruby code.  No C, nothing fancy.  Just plain old
     Ruby code.  Of course, there's no naming service, or trader service,
     or anything like you'd see in CORBA, but it is simple and reasonably
     fast.  On the 233MHz test system, this sample code runs at about 50
     remote message calls per second.