This is to "officially" announce an RCR that I posted to RCR archive
two days ago. I realize the RCR itself is a bit dense and techincial,
so (with thanks to ES) I thought it might be a good idea to provide a
little summary and some examples of what it's all about and why it's a
such a good approach to AOP for Ruby.

You can read the RCR #321 <a
href="http://www.rcrchive.net/rcr/show/321">here</a>. To touch on it's
history: The RCR was developed by Peter Vanbroekhoven and myself over
the course of the last two years[1]. In that time we covered a lot of
territory with regards to AOP, and the RCR itself has undergone a great
deal of scrunity, revision and refinement.

To summaraize the RCR's senitment: The transparent subclass, witch we
have dubbed the Cut, is the best basis for adding AOP to Ruby because
it is fully harmonious with OOP. This is unlike other solutions which
are add-on abstractions tossed on top of the underlying OOP framework.
With cuts one has a building block to construct all sorts of AOP
systems, from simple method hooks to full blown context-oriented
programs. Put simply, the cut is a foundation for AOP, just as the
class is a foundation for OOP.

To demonstrate what you can do with cuts, I present a few simple
examples. I'll start with a very basci one: how to take a prexiting
class and wrap a user interface around it. Nothing fancy. I'll just use
a very small class and the console.

  class Race

    attr_accessor :distance, :speed, :track

    def initialize( distance, speed )
      @distance =  distance
      @speed = speed
      @track = 0
    end

    def reset
      @track = 0
    end

    def run
      while track < distance do
        self.track += 1;
        sleep( 1.0/speed )
      end
      track
    end

  end

Simple enough. We can run a race:

  Race.new( 10, 2 ).run

Though it goes about it's racey busniess just fine, we have no
indication of any progress of the race. But that's okay actually; it
keeps the Race class clean and focused on its core functionality.
Instead we can use a cut to watch the race.

  cut Race::ConsoleViewer < Race
    def track=(x)
      r = super(x)
      print "\e[0E"
      print "+" * r
      $stdout.flush
      r
    end
  end

  Race.new( 10, 2 ).run

This outputs an exra '+' at a time, finishing with 10:

  ++++++++++

So we see the progress of the race "live" without having to change the
Race class itself in any way. And we can just as easily throw any other
type of interface (gtk, qt, fox, wx, html, and so) around the Race
class in the same manner.

Now lets try a slightly more advance example; one made by Peter quite
some time ago, which demostrates the basis of how cuts could be used
for creating logging aspects. Lets say we have an ftp server class:

  class FTPServer

    def login(username, passwd)
      # code for logging in, return ID for the connection
      return connID
    end

    def logout(connID)
      # code for logging out
    end

    def upload(connID, filename, data)
      # code for writing a file
    end

    def download(connID, finename)
      # code for reading a file, returns the data read
    end

  end

We can create an aspect for it:

  module FTPLogging

    def login(username, *args)
      connID = super
      log.print("#{connID}: #{username} logging in on #{Time.new}\n")
      connID
    end

    def logout(connID)
      result = super # In case logout returns some result
      log.print("#{connID}: logging out on #{Time.new}\n")
      result
    end

    def upload(connID, filename, *args)
      result = super # In case logout returns some result
      log.print("#{connID}: uploading #{filename} on #{Time.new}\n")
      result
    end

    def download(connID, filename)
      data = super
      log.print("#{connID}: downloading #{filename} on #{Time.new}\n")
      data
    end

  end

  if logging_enabled
    cut FTPLoggingCut < FTPServer
      include FTPLogging
    end
  end

Notice the use of a separate module to house the aspect. That way it is
reusable. In this case the aspect's design was made to match the
duck-type of the target class, FTPServer, so we don't need to redirect
any advice in the cut. Though if we wanted to use the aspect on a class
not having the same interface we could easily redirect the advice on a
case by case basis. But even better, using an annotations system we
could create a routine to cut any class so annotated.

Finally, here's a example of how one might use cuts for dynamic
context-oriented programming.

  class Account
    attr_reader :id, :current
    def initialize( id, current )
      @id
      @current = current
    end
    def debit( amount )
      @current -= amount
    end
    def credit( amount )
      @current -= amount
    end
  end

  module Activiation
    def active? ; @active ; end
    def activate ; @active = true ; end
    def deactivate ; @active = false ; end
  end

  cut AccountLogging < Account
    extend Activation
    def debit( amount )
      r = super
      if AccountLogging.active?
        log "Debited account #{id} #{amount} with result #{r}"
      end
      r
    end
  end

  cut AccountDatabase < Account
    extend Activation
    def debit( amount )
      super
      if AccountDatabase.active?
        DB.transaction {
          record_transaction( -amount )
          record_total
        }
      end
    end
    def credit( amount )
      super
      if AccountDatabase.active?
        DB.transaction {
          record_transaction( amount )
          record_total
        }
      end
    end
    def record_transaction( amount )
      type = amount > 0 ? 'credit' : 'debit'
      DB.exec "INSERT INTO transactions (account,#{type}) VALUES
(#{id},#{amount.abs});"
    end
    def record_total
      DB.exec "UPDATE accounts SET total=#{current} WHERE id=#{id};"
    end
  end

Notice how we can activate and deactivate the aspects on the fly with
the activation module. If we wished we could go even further and create
a completely general purpose (and relatively efficient) context
switching mechinisms.

Oh, one more quick example taken from the RCR itself for those
wondering where the pointcut is, and how one might cross-cut a whole
slew of classes:

  ObjectSpace.each_object(Class) { |c|
    if c.instance_methods(false).include?(:to_s)
      Cut.new(c) do
        def :to_s
          super.upcase + "!"
        end
      end
    end
  end
  "a lot of shouting for joy".to_s  #=> "A LOT OF SHOUTING FOR JOY!"

Okay, I think that should give a pretty good taste of what "AOP can do
for you" and how Cuts provide a solid, yet easy to use, foundation for
employing AOP on a wide scale. To understand more about why Cuts are
right for the task, covering all the criteria of AOP including
*introduction* and *inspection*, please give the RCR a read.

We hope you find our proposal rewarding and will show your supprt on
RCRchive.

Thanks,
T.


[1] I want to also thank Jamis Buck and everyone else who has spent
time exploring AOP for Ruby with us. Thank You!