From: Trans Date: 2005-10-19T08:56:59+09:00 Subject: [ANN] [RCR] Cut-based AOP 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 here. 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!