A year ago today, we announced that Rails and Merb would merge. At the time, there was much skepticism about the likelihood of the success of this endeavor. Indeed, The most common imagery invoked by those who learned about our plans was a unicorn. At RailsConf last year (well into the effort), both DHH and I used unicorns in our talks, poking fun at the vast expectations we’d set, and the apparent impossibility of achieving everything we’d said we wanted to achieve for 3.0.
A year has gone by, so it’s a good time to reflect on how well we’ve done at achieving those expectations. Over the next few days, I’ll take each bullet point that I provided in my original post, and go into detail about the progress we’ve made on that front.
I’ve given a few recent talks on these topics, so some of you may already have seen some of this, but I wanted to get it down in writing for those who hadn’t. I’ve also added new information, some of which was omitted because it was difficult to explain in a talk, and some of which is too current for any of my recent talks.
Modularity
Rails will become more modular, starting with a rails-core, and including the ability to opt in or out of specific components. We will focus on reducing coupling across Rails, and making it possible to replace parts of Rails without disturbing other parts. This is exactly what Merb means when it touts “modularity”.
We’ve spent a significant amount of time on this step which has been really fruitful. I’ll give a few specific examples.
ActiveSupport
First, we’ve gone through ActiveSupport, making it viable to cherry-pick specific elements. This means that using ActiveSupport’s inflector, time extensions, class extensions, or anything your heart desires is now possible without having to personally track the dependency graph. Here’s an example of what I mean, from the to_sentence method in ActiveSupport from Rails 2.3:
module ActiveSupport #:nodoc: module CoreExtensions #:nodoc: module Array #:nodoc: module Conversions def to_sentence(options = {}) ... options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) ... end ... end end end end
As you can see, to_sentence has an implicit requirement on assert_valid_keys, which means that in order to cherry-pick active_support/core_ext/array/conversions, you are forced to work through the file, find any unsatisfied dependencies, and be sure to require them as well. And of course, the structure of these dependencies could easily change in a future version of Rails, so relying on what you’d found would be unsafe. In Rails 3, the top of that same file looks like:
require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/reverse_merge' require 'active_support/inflector'
This is because we’ve gone through the entire ActiveSupport library, found the unsatisfied dependencies, and made them explicit. As a result, you can pull out the specific libraries you want for a small project, and not get the full weight of ActiveSupport.
Even better, other parts of Rails now explicitly declare the dependencies they have on ActiveSupport. So for instance, the code that adds logging support to ActionController has the following lines on top:
require 'active_support/core_ext/logger' require 'active_support/benchmarkable'
This means that all of Rails knows what parts of ActiveSupport are needed. For simplicity, Rails 3 ships with all of ActiveSupport still provided, so you’ll be able to use things like 3.days or 3.kilobytes without interruption. However, if you want more control over what gets included, that’s possible. You can declare config.active_support.bare = true in your configuration and we’ll pull in only the parts of ActiveSupport explicitly needed for the parts of Rails that you use. You’ll still need to include the fancy parts if you want to use them – 3.days wont work out of the box with bare enabled.
ActionController
Another area that really needed an overhaul was ActionController. Previously, ActionController had a number of disparate elements all in one place. When we looked closely, we found that there were really three discrete components masquerading as one.
First, there was the dispatching functionality. This included the dispatcher itself, routing, middleware, and rack extensions. Second there was generic controller code that was meant to be reused elsewhere, and was in fact reused in ActionMailer. Finally, there was the subset of controller code that brought those two concerns together: code that handled requests and responses through a controller architecture.
In Rails 3, each of those components has been separated out. The dispatcher functionality has been moved into ActionDispatch, with the code inside tightened up and really made a conceptual component. The parts of ActionController that were meant to be reused by non-HTTP controllers was moved into a new component called AbstractController, which both ActionController and ActionMailer inherit from.
Finally, ActionController itself has gotten a significant overhaul. Essentially, we’ve isolated every standalone component, and made it possible to start with a stripped-down controller and pull in just the components you want. Our old friend ActionController::Base simply starts with that same stripped-down controller and pulls everything in. For instance, take a look at the beginning of the new version of that class:
module ActionController class Base < Metal abstract! include AbstractController::Callbacks include AbstractController::Logger include ActionController::Helpers include ActionController::HideActions include ActionController::UrlFor include ActionController::Redirecting include ActionController::Rendering include ActionController::Renderers::All include ActionController::Layouts include ActionController::ConditionalGet include ActionController::RackDelegation include ActionController::Logger include ActionController::Benchmarking include ActionController::Configuration
All we’re doing here is pulling in every available module, so the default experience of Rails is the same as before. However, the real power of what we’ve done here is the same as what we’ve done in ActiveSupport: every module declares its dependencies on other modules, so you can pull in Rendering, for instance, without having to wonder what other modules need to be included and in what order.
The following is a perfectly valid controller in Rails 3:
class FasterController < ActionController::Metal abstract! # Rendering would be pulled in by layouts, but I include # it here for clarity include ActionController::Rendering include ActionController::Layouts append_view_path Rails.root.join("app/views") end class AwesomeController < FasterController def index render "so_speedy" end end
And then, in your routes, it would be perfectly valid to do:
MyApp.routes.draw do match "/must_be_fast", :to => "awesome#index" end
Essentially, ActionController::Base has become just one way to express your controllers. Think of it like Rails Classic, with the ability to roll your own if you’re not so into that taste. It’s really easy to mix and match too: if you wanted to pull in before_filter functionality to FasterController, we could simply include AbstractController::Callbacks.
Note that without doing anything else, including those modules pulled in AbstractController::Rendering (the subset of rendering functionality shared with ActionMailer), AbstractController::Layouts, and ActiveSupport::Callbacks.
This makes it really possible to trivially pull in just the specific functionality you need in performance-sensitive cases without having to use an entirely different API. If you need additional functionality, you can easily just pull in additional modules or eventually upgrade to the full ActionController::Base without needing to rip anything apart along the way.
This, in fact, is a core idea of Rails 3: there are no monolithic components, only modules that work seamlessly together in a great package of defaults. This allows people to continue using Rails as they have used it successfully in previous versions, but really leverage the codebase for alternative uses. No more functionality locked away in non-reusable forms.
One nice immediate benefit of all of this is that ActionMailer gets all of the functionality of ActionController in a clean, intentional way. Everything from layouts and helpers to filters is using the identical code that ActionController uses, so ActionMailer can never again drift away from the functionality of ActionController (as ActionController itself evolves).
Middleware gets a helping hand too. ActionController::Middleware, which is middleware with all of the powers of ActionController, allows you to pull in whatever ActionController features you want (like Rendering, ConditionalGet, robust Request and Response objects, and more) as needed. Here’s an example:
# The long way class AddMyName < ActionController::Middleware def call(env) status, headers, body = @app.call(env) headers["X-Author"] = "Yehuda Katz" headers["Content-Type"] = "application/xml" etag = env["If-None-Match"] key = ActiveSupport::Cache.expand_cache_key(body + "Yehuda Katz") headers["ETag"] = %["#{Digest::MD5.hexdigest(key)}"] if headers["ETag"] == etag headers["Cache-Control" = "public"] return [304, headers, [" "]] end return status, headers, body end end
# Using extra Rack helpers class AddMyName < ActionController::Middleware include ActionController::RackDelegation def call(env) self.status, self.headers, self.response_body = @app.call(env) headers["X-Author"] = "Yehuda Katz" # but you can do more nice stuff now self.content_type = Mime::XML # delegates to the response response.etag = "#{response.body}Yehuda Katz" response.cache_control[:public] = true self.status, self.response_body = 304, nil if request.fresh?(response) response.to_a end end
# Using ConditionalGet helpers class AddMyName < ActionController::Middleware # pulls in RackDelegation include ActionController::ConditionalGet def call(env) self.status, self.headers, self.response_body = @app.call(env) headers["X-Author"] = "Yehuda Katz" self.content_type = Mime::XML fresh_when :etag => "#{response.body}Yehuda Katz", :public => true response.to_a end end
In all, I really think we’ve delivered on our promise to bring significant modularity improvements to Rails (and then some). In fact, I think the level of success that we’ve had with this version exceeds most people’s expectations of a year ago, and is solidly in golden unicorn territory. Enjoy!
Next, I’ll talk about bringing performance improvements to Rails 3. Hopefully it won’t go by too fast. :)


Watch a Live Demo of Engine Yard AppCloud
The Engine Yard Newsletter
congratulation for the first anniversary of the big merge.
An awesome way to mark 1 year since the merge announcement. Keep em coming :-D
Nice writeup. Really looking forward to reading the next 5, and to finally getting my pony!
Do want. Ponies, rainbows and unicorns are autoloaded, right?
I think it's wonderful that you even considered doing this. My respect for the Rails and Merb developers skyrocketed when I've heard about it and now I'm a Rails fan!
Looks great, can't wait to use it :-)
rails 3 for christmas?
..in next year I am afraid…. Is there any timeline for Rails 3???
Good stuff. One suggestion: could you syntax-highlight the code next time? Monochrome code is several times more difficult to read.
Working on it :P
Good stuff as usual wycats. Looking forward to use Rails 3 very soon.
Thanks a lot for the write up.
Regarding "You’ll still need to include the fancy parts if you want to use them – 3.days wont work out of the box with bare enabled", for a sizable project that uses plugins, it is hard to imagine to enable bare mode.
Plugins should individually pull in the parts of ActiveSupport that *they* need. You'll only need to pull in parts of ActiveSupport that you're explicitly using — I agree that otherwise the idea would be untenable.
These implicit requirements are a huge headache with maintaining Ruby code. I prefer other languages that require each file to list its dependencies.
What's in the name Action/Active? Sound like ActiveDirectory and ActiveSync.. Strange branding in my eyes: the name is Rails, right? What about RailsController, RailsSupport, or Rails::Controller, Rails::Support? It simply makes more sense to me. Rubyists care for meaningful naming right?
I'm a merb lover: for its simplicity. Reading this write-up I think R3 is on its way to replace merb for me. The weird action/active naming will not hold me down. :)
Thanks for the write up!
It is a disgrace to see how Merb was completely and utterly abandoned by the whole core-team after they we're invited to work on rails. Lots of people who were using Merb in large production deployments got screwed pretty badly. If Rails is a ghetto, then I cannot begin to describe what the Merb -> Rails merger made Merb become. On a positive note, it made me see why people actually use Java and it's heavyweight frameworks for large projects. If something can go from having a large and vibrant community one day, to months without simple bugfixes and pure silence, then it is smart to stay far away from it for anything serious.
But yes, Rails 3 seems promising, and you have madesome awesome improvements. I think I will stick to Sinatra myself. I just hope no one tries to lure its core devs over to something else with promises of fame and glory. It is amazing to see how much can be done using Sinatra, with it's tiny and understandable codebase.
Merb is open source. If development has stalled, it was abandoned by the entire community, and not just the people who were working on it.
Matt Aimonetti a merb's core team member answered to the question "I just started a merb project, so what now?"
"Keep on using Merb, we won’t let you down." ~ Matt Aimonetti
The interview is here http://merbist.com/2008/12/23/rails-and-merb-merg... — Do not promise what you can't accomplish
I want to use it not get hyped about it.