Since I spend a lot more time with Rails 3 codebase, I am more and more amazed by its modularity. Rails 3 is much more extensible than I would have ever thought. As a part of my rubysoc project I wanted to port merb-parts to Rails 3. Some of you may never heard of merb parts and ask what the hell are parts? Do I need it? Parts are a bit like controllers, but much more lightweight and without overhead of handling requests. I like to think about parts as powerful partials. If you have a partial and you would like to add a bit more logic to it or fetch something from database, you could probably benefit from using parts. Let’s go with a simple example:
# app/parts/articles_part.rb
class ArticlesPart < Parts::Base
def index
@articles = Article.limit(params[:limit] || 10).order("created_at DESC")
end
end
# app/parts/views/articles_part/index.html.erb
<ul>
<% @articles.each do |article| %>
<li><%= article.title %></li>
<% end %>
</ul>
This simple part will fetch last params[:limit] articles or 10 if limit is not provided. Let’s use it in our view:
<%= part(ArticlesPart => :index, :limit => 5) %>
Such call will render list of last 5 articles.
The question is: how much lines of code does it take to implement something like that with ability to render views, layouts, :inline, use helpers, filters and much more? Over 100 lines of code including part() helper and a railtie (railtie is used to plug it into rails) – if you don’t believe me grab the repo and check it yourself.
How is it possible? Let’s look at the implementation of Parts::Base:
require 'parts/default_layout'
module Parts
class Base < AbstractController::Base
attr_reader :params
include AbstractController::Layouts
include AbstractController::Translation
include ActionController::Helpers
include AbstractController::Rendering
include ActionController::ImplicitRender
include DefaultLayout
include AbstractController::Callbacks
def initialize(controller, params)
@params = controller.params.dup
@params.merge!(params) unless params.empty?
self.formats = controller.formats
end
def self.inherited(klass)
super
klass.helper :all
end
end
end
That’s all… ? Yes!
As you cans see Parts::Base inherits from AbstractController::Base, which gives it really basic functionality. Additionaly a few helpers are included to add a bit more behavior. The only mixin that I needed to create myself is Parts::DefaultLayouts which ensures that layout with the name of the part is rendered by default, unless :layout => false or there is no such layout in layouts directory.
Ok, that all is nice and dandy, but how does it work internally?
The implementation of such pattern can be demonstrated with such code:
class Foo
def foo
puts 'foo'
end
end
module Bar
def foo
puts 'bar'
super
end
end
module Baz
def foo
puts 'baz'
super
end
end
class Omg < Foo
include Bar
include Baz
def foo
puts 'omg'
super
end
end
Omg.new.foo #=> omg
# baz
# bar
# foo
There are 2 modules (Bar and Baz) and 2 classes (Foo and Omg) implementing foo method here. Omg class inherits from Foo and additionaly modules Bar and Baz are included in Omg. This code takes advantage of ruby object model. How does it work?
When you instantinate Omg object with Omg.new and call method foo on it, it looks for foo method in current class and its superclasses. So first method that will be actually called is Omg#foo. This method is calling super, so ruby will look for method foo also in Omg’s superclass. At first you could think that it’s Foo, but internally Ruby treats modules as superclasses. That said, the next superclass will be the last included module, which is Baz. After that Bar’s and Foo’s methods will be invoked.
This one of the best patterns I have seen in Ruby so far. Not only it allows to extend objects easily, but also to reuse small chunks of code making the whole thing more modular. If Rails would stop at standard inheritance that we know from Java and other similar OO languages, creating something like parts would be much harder. In Parts::Base I used a few modules from AbstractController, but also a module from ActionController. With implementation without mixins, I would need to take all or nothing approach.
If you’re a Ruby developer, please think about that pattern while writing your next library. It can really help to make your code easier to extend and reuse. I was amazed how easy it was to implement all that stuff and I love the way that Rails is going!