Why Ruby’s Mixins Gives Rails an Advantage over Java Frameworks
Much has been made over Paul Graham’s famous posting about how Lisp gave his startup Viaweb an advantage over the competition. Graham’s thesis is that there are features in the Lisp language that could be leveraged to make his programming team more productive and better able to respond to customer needs.
The idea that a programming language will make your team more productive is the Holy Grail of software development. Many languages have been promoted as a cure all for productivity (C++ in the 80s, Java in the 90s, Ruby now, Lisp perpetually), and each turns out to have their benefits and weaknesses. But there is no doubt that certain features in languages can lead to leaps in improvements in productivity. For example, having built in garbage collection is a sine qua non for modern development languages. No one can argue (at least no one can argue well) that garbage collection does not improve developer productivity for 99.9% of development efforts.
What is Mixin?
Since many readers may not be aware of what a mixin is, I will try to describe it briefly. Ruby is an object-oriented language. With all object-oriented languages, the designers had to decide whether or not to support multiple inheritance. Perl and C++ designers allow for multiple inheritance, Java does not allow for multiple inheritance but does allow for polymorphism through the use of interfaces. Ruby’s designers opted to not allow multiple inheritance, but to allow for code from one module to be able to be included in another class.
So consider the following code:
module BarModule def hello_world puts "Hello World" end end class BaseClass def class_method puts "In class method" end end class Foo < BaseClass include BarModule end f = Foo.new f.class_method f.hello_world
In this example, we are creating a module named BarModule with a hello_world method, a class named BaseClass, and then another class Foo that extends BaseClass and includes BarModule. The class Foo will then have both the methods from BarModule and BaseClass, but it will only BE an instance of BaseClass as that is its only parent. Somewhat different than what a Java developer would do, but it makes sense. Running this file will result in the following output:
$ ruby foo.rb In class method Hello World $
So that is a basic example of what a mixin is.
Adding send() into the equation
Another interesting and powerful thing about Ruby is that all method calls are actually message passing calls. So for example, we could rewrite:
f = Foo.new f.class_method f.hello_world
to
f = Foo.send(:new) f.send(:class_method) f.send(:hello_world)
and the results would be exactly the same! Again a little weird if new to Ruby, but it this language feature can lead to some very interesting and powerful results.
Combining include and send
Now let’s combine these two methods. What if I wanted to take my BarModule and apply it to a class that is not sign, say the ruby base class String.
String.send(:include, BarModule) s = "Arbitrary String" s.hello_world
Running the above code would produce:
$ ruby include-bar-module-on-string.rb Hello World $
That’s right, at runtime, I was able to add an arbitrary method onto the base String class. That method is now available to any _String_s that I instantiate within my application.
The Implication of this Feature on Rails
Because of this mixin feature, a developer can add arbitrary methods and modify behavior of core classes at runtime. This is amazingly powerful if you are trying to write plugins and extensions to the framework. Because you can add functionality to existing objects, users can install your plugin and start taking advantage of new functionality without having to make changes to the objects that they are instantiating in their application. In frameworks written in other languages, such as Java, plugging in new functionality means that you need to change how your objects are instantiated. This will require code changes and/or potentially configuration changes (if you are using a dependency injection framework like Spring). Hard to develop, hard to maintain, and a pain for plugin developers to support. But because of the mixin feature, Rails plugin developers can customize the base objects and the users of the plugins do not have to change any of their code or configuration logic. A good example of this can be seen in the Row Version Rails plugin. This particular plugin puts a created_at, UPDATED_AT and a row_version on every row inserted into the database. It requires ZERO code change to make this happen. You just install the plugin and go. It works by adding hooks into the ActiveRecord::Base (the base persistence class in Rails) so that when records are saved, the correct information is put in to those fields. A very easy and powerful plugin to install and use.
Conclusions
The point to take away from this is not that Ruby on Rails rocks and Java sucks. Far from it. But choosing a framework with lots of extensions that can take care of many of the mundane tasks allows your developers to spend more of their time focused on the problems of the user and not on common problems. The mixin feature of Ruby allows for the development of easy to use but powerful plugins that will be hard for any non-Ruby based framework to compete with.