Design by contract for Ruby
Here's a nice little module Brian and I hacked together a few weeks ago. It adds basic design by contract capabilities to classes with a fairly clean syntax (yay for Ruby's flexibility!)
Take a look for yourself:
When the DesignByContract module is included in a class, the pre and post methods become available. The parameters to these methods are a symbol representing the method to add the pre or post condition to, the message to print if the condition fails and a block that implements the condition.
The method name and message are optional, though. If the method name is missing, the condition will applied to the first method definition following the pre/post declaration. Note that if the method name is present, the condition can be defined anywhere in the class definition, as shown in the example above.
The condition block receives all the arguments passed to the method it applies to. They must return a boolean value indicating whether the condition succeeded. A minor difference between pre and post blocks is that the post block receives the result returned by the method as the first argument. This makes it possible to validate the results against the inputs such as in:
Get it here!
In a nutshell, the pre and post methods intercept the existing method using the technique I described a few weeks ago (i.e., no poluting the class' namespace with aliased methods). The new methods test the provided condition before or after delegating the call to the original method and raise an exception if the condition fails.
There is a small caveat, though. If pre/post are called before the method is first created, it cannot be intercepted. To get around this the module uses an alternative trick. Whenever the module is included, it hooks into the method_added callback of class it's included into. If pre/post are called and the corresponding method does not exist, the interception is scheduled until the method is added.
Take a look for yourself:
class Foo
include DesignByContract
pre(:sqrt, "value must be >= 0") { |v| v >= 0 }
pre("divisor must be != 0") { |dividend, divisor| divisor != 0 }
post { |result, a, b| a - b * result < 1e-3 }
def div(dividend, divisor)
dividend / divisor
end
def sqrt(value)
Math.sqrt value
end
post(:sqrt, "error is greater than expected") { |result, value|
result * result - value < 1e-3
}
end
include DesignByContract
pre(:sqrt, "value must be >= 0") { |v| v >= 0 }
pre("divisor must be != 0") { |dividend, divisor| divisor != 0 }
post { |result, a, b| a - b * result < 1e-3 }
def div(dividend, divisor)
dividend / divisor
end
def sqrt(value)
Math.sqrt value
end
post(:sqrt, "error is greater than expected") { |result, value|
result * result - value < 1e-3
}
end
When the DesignByContract module is included in a class, the pre and post methods become available. The parameters to these methods are a symbol representing the method to add the pre or post condition to, the message to print if the condition fails and a block that implements the condition.
The method name and message are optional, though. If the method name is missing, the condition will applied to the first method definition following the pre/post declaration. Note that if the method name is present, the condition can be defined anywhere in the class definition, as shown in the example above.
The condition block receives all the arguments passed to the method it applies to. They must return a boolean value indicating whether the condition succeeded. A minor difference between pre and post blocks is that the post block receives the result returned by the method as the first argument. This makes it possible to validate the results against the inputs such as in:
post { |result, dividend, divisor| dividend - divisor * result < 1e-3 }
Get it here!
How it works
In a nutshell, the pre and post methods intercept the existing method using the technique I described a few weeks ago (i.e., no poluting the class' namespace with aliased methods). The new methods test the provided condition before or after delegating the call to the original method and raise an exception if the condition fails.
There is a small caveat, though. If pre/post are called before the method is first created, it cannot be intercepted. To get around this the module uses an alternative trick. Whenever the module is included, it hooks into the method_added callback of class it's included into. If pre/post are called and the corresponding method does not exist, the interception is scheduled until the method is added.
Labels: ruby
10 Comments:
Hey Ian,
It's good to hear somebody found this code helpful. And yeah, feel free to use it however you like.
BTW, I just made the license for that code explicit. I'm making it available under the MIT license. Let me know if that's not suitable for your use and I'll change it.
Very good! But I noticed that the pre/post invocation order is not guaranteed.
where is the invariant?
Erik,
I just uploaded a new version that fixes that issue.
Get it here: http://utils.ning.com/ruby/dbc.rb
Thanks for this. It looks like a very clean, natural implementation.
This doesn't seem to work for private methods. Nothing happens at all when i put a pre in front of a private.
I'd like to use this to state contracts between internal private methods as well as for public interfaces. How do i do that?
Ditto the comment about disabling it for release code.
This is a great module for ruby code, I think I want to add some functionality to a rails addon for rails developers, to give controllers, models, and views some basic requires/ensures capability. There'll probably be a tool as well that'll allow you to generate an app-specific cheat sheet of what each component makes use of... Would it be possible for me to commit some stuff to rdbc?
That's a helpful code, thanks.
------------------------------------------
Interior Design Furniture Scotland
Pandeglang is one of many places in Indonesia are eligible for tourist destination.
Kenali dan Kunjungi Objek Wisata di Pandeglang Objek Wisata di Pandeglang Kenali dan Kunjungi Objek Wisata di Pandeglang Pantai Carita Seni Saman Rampak Bedug
araç sorgulama
sorgulama
ehliyet
açıköğretim
bağkur sorgulama
ssk sorgulama
emekli sandığı
cinsellik
radyo dinle
korku
evlilik
hikaye
gazeteler
ilan
ssk
iş ilanları
bağkur
gazete oku
Post a Comment
<< Home