Building Interfaces and Abstract Classes in Ruby
So back in the dark ages of my career, pre-2006, I spent a long time coding Java. Yeah, I know, please don’t judge. Anyway, In Java, for those of you who are unaware were two constructs that I occasionally wish I had in Ruby, those are Interfaces and Abstract Classes. The difference between these two constructs is subtle, but important.
In Java an Interface is a basically a blueprint of methods that the class who implements the Interface needs to implement. For example:
interface Bicycle {
void changeGear(int newValue);
void speedUp(int increment);
void applyBrakes(int decrement);}
public class ACMEBicycle implements Bicycle { public void changeGear(int newValue) { // do some work here } public void speedUp(int increment) { // do some work here } public void applyBrakes(int decrement) { // do some work here }
}Here we have a Bicycle Interface that says there are three methods that need to be implemented. It is then the responsibility of the ACMEBicycle class to implement those methods. Now, an Abstract Class in Java is similar to an Interface in that it too is a blueprint of methods that the extending class may or may not need to implement. There in lies one of the differences between the two. Let’s take a look at the same example, but this time we want to implement the same behavior of all of our extending classes for the applyBrakes method:
abstract class Bicycle { abstract public void changeGear(int newValue);
abstract public void speedUp(int increment);
public void applyBrakes(int decrement) { // do some work here } }
public class ACMEBicycle extends Bicycle { public void applyBrakes(int decrement) { // do some work here }
}An Abstract Class is a great way to provide a mix of fully implemented methods as well as providing subclasses with a mixture of methods that need to be implemented by the extending class.
The really powerful part of all of this is two fold. First, the Java compiler will happily yell at you and fail if it finds that you haven’t implemented some of the methods that you were told you had to. Second, you can easily see the methods that you need to document right there, you can even copy/paste their definitions right into your class so you can start to fill them out.
So, how does this bring us over to Ruby? Great question. I’d like to take a few moments and explore a few ways we can get some of this power in Ruby.
Unfortunately, or fortunately depending on how you look at it (I see it as a mixed blessing), there is no compiler in Ruby, so we don’t really have a good way of having the system yell at us if we don’t implement the methods we were supposed to. But, there is still plenty we can do to help those who are implementing our classes both know what they need to implement and to find out what they haven’t implemented when their program is executing.
Here is one implementation on we can gain a bit of that functionality back in Ruby:
module AbstractInterface class InterfaceNotImplementedError < NoMethodError end def self.included(klass) klass.send(:include, AbstractInterface::Methods) klass.send(:extend, AbstractInterface::Methods) end module Methods def api_not_implemented(klass) caller.first.match(/in \`(.+)\'/) method_name = $1 raise AbstractInterface::InterfaceNotImplementedError.new("#{klass.class.name} needs to implement '#{method_name}' for interface #{self.name}!") end end end
class Bicycle include AbstractInterface # Some documentation on the change_gear method def change_gear(new_value) Bicycle.api_not_implemented(self) end # Some documentation on the speed_up method def speed_up(increment) Bicycle.api_not_implemented(self) end # Some documentation on the apply_brakes method def apply_brakes(decrement) # do some work here end end
class AcmeBicycle < Bicycleend
bike = AcmeBicycle.newbike.change_gear(1) # AbstractInterface::InterfaceNotImplementedError: AcmeBicycle needs to implement 'change_gear' for interface Bicycle!What we’ve done here is to inject a Module into our Bicycle class to give it a nice error it can raise and a little bit of help building a nice error message for the user. Then in our Bicycle class we define all the methods we want and in the ones we need the end user to define we can call the api_not_implemented method and it will raise the AbstractInterface::InterfaceNotImplementedError error for us.
We could simplify this a bit by having a nice little helper macro that we can use to build these methods, like this:
module AbstractInterface class InterfaceNotImplementedError < NoMethodError end def self.included(klass) klass.send(:include, AbstractInterface::Methods) klass.send(:extend, AbstractInterface::Methods) klass.send(:extend, AbstractInterface::ClassMethods) end module Methods def api_not_implemented(klass, method_name = nil) if method_name.nil? caller.first.match(/in \`(.+)\'/) method_name = $1 end raise AbstractInterface::InterfaceNotImplementedError.new("#{klass.class.name} needs to implement '#{method_name}' for interface #{self.name}!") end end module ClassMethods def needs_implementation(name, *args) self.class_eval do define_method(name) do |*args| Bicycle.api_not_implemented(self, name) end end end end end
class Bicycle include AbstractInterface needs_implementation :change_gear, :new_value needs_implementation :speed_up, :increment # Some documentation on the apply_brakes method def apply_brakes(decrement) # do some work here end end
class AcmeBicycle < Bicycleend
bike = AcmeBicycle.newbike.change_gear(1) # AbstractInterface::InterfaceNotImplementedError: AcmeBicycle needs to implement 'change_gear' for interface Bicycle!That approach certainly makes our code look a bit cleaner, I’m not denying that, however it has one really big flaw, at least for me anyway, it doesn’t give us a good to place to hang our documentation hat. In the previous approach we had actual methods that we could then document and that documentation would then show up in RDoc when it’s outputted. With the latter approach, however, we can document the hell out of the needs_implementation calls we have in the Bicycle class, but they won’t ever show up in the documentation. That means that users of our library have to crack open the actual code itself to see what it they are expected to implement.
Another approach we could’ve taken, which I bother to demonstrate here as I don’t think it offers a better approach is to have the needs_implementation method collect up the names of those methods and use method_missing to report that the method needs to be implemented. I mention it here only for completeness, but it definitely is not the best solution to this problem.
Finally, I would like to note that, as far as I can see, there is no way in Ruby to create a callback hook for when a class has been defined. If there was in fact such a hook we could use to it immediately notify the end user that they have forgotten to implement certain methods. Perhaps in Ruby 2.0??? That’s just pure hope on my part.
That’s it. I hope you enjoyed our brief (*cough*) look through implementing Interface and Abstract Classes in Ruby. I hope you’ve enjoyed it.
* PS, yes, I’m aware I didn’t talk about multiple vs. single inheritance in either Java or Ruby, nor did I talk about the fact that in Ruby you can’t really have Abstract Classes. I thought that was all a bit much for an already rather lengthy post as it was. Perhaps another day.


February 8th, 2011 at 4:31 pm
Interesting and inspiring post! Here’s my stab at it: https://github.com/shuber/interface
February 8th, 2011 at 5:13 pm
Thanks Sean! You got the spirit of the post, it’s really just some food for thought and to get people thinking.
February 9th, 2011 at 4:56 am
Why? You still need testing, or you still risk running into exceptions at runtime. And if you leave the methods undefined in the class in question, you’ll get an exception when used with test cases that triggers the use of the unimplemented methods anyway. So the only difference is that you get different exceptions.
This seems like an attempt at writing Java in Ruby, defeating much of the purpose of using Ruby in the first place.
February 9th, 2011 at 12:29 pm
There’s a hook called inherited, that get’s called when on a super class when it’s inherited. May that work for you?
http://apidock.com/ruby/Class/inherited
February 9th, 2011 at 12:51 pm
I agree with Vidar. If you want the statically typedness of Java, use Java. Test Driven Development lets you handle errors far better than trying to Javafy Ruby. Still, this is a good post – some interesting stuff for sure.
February 9th, 2011 at 1:48 pm
@Jorge, the inherited, and it’s Module cousin included, are great methods that allow you to do some fantastic meta programming tricks. However, they both suffer from the same problem, they are usually at the start of the class definition. Example:
User < ActiveRecord::Baseis where the inherited method would get called, so it is unaware of all the method definitions that are to follow in the class you’re defining. Does that make sense? I’m saying it be great to have aclass_definedhook, that would get loaded whenever theendon a class definition gets executed.February 9th, 2011 at 1:57 pm
@Paul and @Vidar, I agree if you want a statically typed language, than Java’s a pretty good one. With that said one of things I never liked about Java was the fact it was statically typed. I’m not talking about static typing or duck typing in this post, however. What I am talking about is giving people who are implementing some library a nice template and contract to help them develop too. Testing is, of course, one of the most important parts of the development process, just read some other articles on my blog for my feelings towards testing, but you have to know what you are supposed to be testing and what needs to be implemented.
Too many times I’ve seen developers post something like this in their README files for a library:
Library X is easy to extend and customize! Just implement your own version of Class Y!That’s great, but what am I supposed to implement for Class Y? What methods? What do they do? Where’s the documentation? What if the library is updated and new methods need to be implemented? I’m simply talking about a way to help ensure people implementing these classes know what they’re implementing and they are alerted when they’ve missed something.
I’m advocating Javafying Ruby, far from it, but it is important to remember that other languages have good features too, and that we as a community should keep our eyes out for those good features and steal the hell out of them!
Finally, and I don’t think I made this clear in the original post, this is post was more of a thought exercise. “How would we implement Interfaces and Abstract Classes in Ruby?” That sort of thing.
February 9th, 2011 at 2:00 pm
Oh, and I would love to see library developers start shipping ‘shared_examples_groups’ for their libraries. That way when you go to implement Class Y you can plug in the shared_example_group for it and know that you’re doing the right thing because of those examples.
February 9th, 2011 at 7:44 pm
@Mark, Completely agree and I can definitely see the benefit of using it to test library extensions e.g.
class CustomModel
implements ActiveModel
…
end
I’ve updated https://github.com/shuber/interface with interface testing assertions so you can do something like
class CustomModelTest < Test::Unit::TestCase
def test_should_implement_active_model
assert_implements_interface CustomModel.new, ActiveModel
end
# or test all interfaces
def test_should_implement_interfaces
assert_implements_interfaces CustomModel.new
end
end
By 'shared_examples_groups' do you mean something like ActiveModel::Lint::Tests (http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/)?
February 11th, 2011 at 3:03 pm
This is very interesting. I was really hoping you could do something like this in your Interface module:
def self.included(klass)
missing = Interface::Methods.methods – klass.methods
raise Interface::InterfaceNotImplementedError.new(“#{klass.class.name} needs to implement ‘#{missing.first}’ for interface #{self.name}!”) unless missing.size == 0
klass.send(:include, Interface::Methods)
klass.send(:extend, Interface::Methods)
end
But unfortunately, at the time of include, our klass’s methods have not yet loaded, so it would always error out. It could possibly work if we included the Interface module at the *bottom* of our class, but I that’s really not an acceptable solution.
March 1st, 2011 at 1:28 am
You can get documentation for needs_implementation -calls by using YARD and writing a custom construct plugin for it. Check http://yardoc.org/features.html under the “Custom Constructs and Extensibility” heading.