I was recently reading Martin Fowler's Refactoring. While I was reading Introduce Local Extension (p. 164) all I could think of was how simple it is to implement in Ruby.
As Fowler says in the motivation section writers of classes and libraries are not omniscient. Inevitably some class that you're using will be missing some feature you want. Using Introduce Local Extension you can add that additional functionality when you need it.
In a statically typed language, like Java, this refactoring would involve creating either a wrapping class or a descendent of the original class. In Ruby we will accomplish the same thing using a mixin.
In this example we need to determine if a time is inclusively between two times. In a static language utility methods like this frequently end up in utility classes consisting of a collection of static methods. Introduce Local Extension allows us to keep the functionality together with the data, our date object.
First we create a module that has our new method in it.
module TimeUtils def between_inclusive? (time1, time2) self >= time1 && self <= time2 end end
With the module complete we can use it to introduce this extension to any instance of Time.
time = Time.now time.extend(TimeUtils) puts time.between_inclusive?(time - 3600, time + 3600)
Using this methodology you can add a significant amount of functionality to an object at runtime. This is a good strategy to use, if you do not wish to add the methods to the original class. (Remember Ruby has open classes. We could just have easily re-opened Time and simply add between_inclusive? to it.)
We will again add the between_inclusive? method to an instance of Time. In this example we'll use Ruby's ability to add a method to an instance of a class.
time = Time.now def time.between_inclusive? (time1, time2) self >= time1 && self <= time2 end puts time.between_inclusive?(time - 3600, time + 3600)
This method isn't as DRY or as flexible as using a Module. It is also hard to test. I would personally stay away from it, unless you have a specific need.
With that caution I have used this method for doing a local extension along with Ruby's ability to alias methods to add validations to an ActiveRecord model that covered a rarely used edge case. Using the local extension in that case kept my model class far cleaner, and allowed all of the logic dealing with the edge case to be kept in one place.
model # An instance of an ActiveRecord model. # Open the singleton class of model. class << model alias_method :old_validate, :validate def validate old_validate # Add local validation as needed ... end end model.validate
In this particular instance we have injected behavior into the validate method that normally would not be there, we have preserved the existing validate method, calling it from our new validate method. This allows us to use introduce local extension and preserve integration with ActiveRecord. (NB: Again I'll caution it should be a rare case where you need to use this technique. If you start using it commonly you're doing something wrong.)
Since Ruby has open classes you are free to add functionality to any class you want. If you wanted between_inclusive? to be available on every instance of Time in your application you can simply add it.
# Place this in a file you require when your application starts. class Time def between_inclusive? (time1, time2) self >= time1 && self <= time2 end end
Now you can call between_inclusive? on any instance of Time in your application.
time = Time.now puts time.between_inclusive?(time - 3600, time + 3600)
Using the Ruby's dynamic features, mixins and singleton classes, we are able to easily introduce local extensions in our code. Unlike in a static language there is no need for a subclass or a wrapper using delegation. Using the module method our code can be kept DRY and testable.