T
Trans
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!
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!