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.
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.
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_sentencemethod in ActiveSupport from Rails 2.3:
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:
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:
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.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.
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:
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:
And then, in your routes, it would be perfectly valid to do:
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
Note that without doing anything else, including those modules pulled in
AbstractController::Rendering (the subset of rendering functionality shared with ActionMailer),
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:
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. :)