Yehuda Katz is a member of the Ember.js, Ruby on Rails and jQuery Core Teams; he spends his daytime hours at the startup he founded, Tilde Inc.. Yehuda is co-author of best-selling jQuery in Action and Rails 3 in Action. He spends most of his time hacking on open source—his main projects, like Thor, Handlebars and Janus—or traveling the world doing evangelism work. He can be found on Twitter as @wycats and on Github.

Rails Edge Architecture

I’ve talked a bunch about the Rails 3 architecture in various talks, and it’s finally come together enough to do a full blog post on it. With that said, there’s a few things to keep in mind.

We’ve done a lot of work on ActionController, but have only mildly refactored ActionView. We’re going to tackle that next, but I don’t expect nearly as many internal changes as there were for ActionController. Also, we’ve been working on the ActionController changes for a few months, and have focused quite a bit on maintaining backward compatibility (for the user-facing API) with Rails 2.3. If you try edge and find that we’ve broken something, definitely let us know.

AbstractController::Base

One of the biggest internal changes is creating a base superclass that is separated from the notions of HTTP. This AbstractController handles the basic notion of controllers, actions, and action dispatching, and not much else.

At that level of abstraction, there are also a number of modules that can be added in, such as Renderer, Layouts, and Callbacks. The API in the AbstractController is very normalized, and it’s not intended to be used directly by end users.

For instance, the template renderer takes an ActionView::Template object and renders it. Developers working with Rails shouldn’t have to know about Template objects; this is simply an internal API to make it easier to work with the Rails internals and build things on top of Rails.

Another example is related to action dispatching. At its core, action dispatching is simply taking an action name, calling the method if it exists, or raising an exception if it doesn’t. However, other parts of Rails have different semantics. For instance, method_missing is used if the action name is not a public method, and the action is dispatched if no action is present but a template with the same name is present.

In order to make it easy to implement these features at different levels of abstraction, AbstractController::Base calls method_for_action to determine what method to call for an action. By default, that method simply checks to see if the action is present. In ActionController::HideActions, that method is overridden to return nil if actions are explicitly removed using hide_action. ActionController::Base further overrides it to return “default_render”, which handles the rendering.

That’s, in a nutshell, how the new architecture works: simple abstractions that represent a bunch of functionality that you can layer on if you need it.

ActionController::Http

ActionController::Http layers a Rack abstraction on top of the core controller abstraction so that actions can be called as Rack endpoints. On top of the core rendering functionality available in AbstractController::Base, ActionController provides rendering functionality that builds on top of HTTP constructs. For instance, the ActionController renderer knows how to set the content type based on the template it rendered. Additionally, ActionController implements more developer-friendly APIs. Instead of having to know about the ActionView::Template object, developers can just use the name of the template (pretty much the Rails 2.3 rendering API).

ActionController normalizes the developer’s inputs into the inputs that AbstractController is expecting, before calling super (ActionController::Http inherits from AbstractController::Base). Additional functionality is added via the ActionController::Rails2Compatibility module, which provides support for things like stripping a leading “/” off of template names, and ActionController::Base, which provides a final layer of normalization (for instance, normalizing render :action => “foo” and render “foo”). As a result, ActionController::Base ends up being identical to the fully-featured ActionController::Base that you used in Rails 2.3, and none of your app code needs to change.

As an example, let’s look at rendering. If you call render :template => “hello”, the first thing that happens is the normalization pass on ActionController::Base. Since we used a relatively normalized form, not much happens, and the options hash is passed up into the compatibility module, which checks to see if there’s a leading “/” in the template name. Since there isn’t, it passes the options hash up again into the AbstractController::Renderer module.

There, Renderer checks to see if we’ve already set the response body. If we have, a DoubleRenderError is raised. If not, render_to_body is called with the options hash. The first place in the chain we find render_to_body is in ActionController::Renderer, where it processes options like :content_type or :status.

It then calls super, passing control back into AbstractController, which promptly calls _determine_template, again with the options. The job of _determine_template is to take in some options and return the template to render. This hook point is provided so that other modules, like the Layouts module, can use the template that was looked up to control something they care about. In this case, the Layouts module wants to limit the search for a layout to the formats and locale of the template that was actually rendered.

This solves a long-standing problem in Rails where templates and layouts were looked up separately, so it was possible for an HTML layout to be implicitly wrapped around an XML template. No longer :)

The Layouts module gets called first with _determine_template. It calls super, allowing the default template lookup to occur, which populates options[:_template]. It then uses options[:_template] to look up the layout to use, populating options[:_layout]. I hadn’t mentioned it before, but AbstractController uses options with leading underscores, leaving undecorated options for subclasses like ActionController::Base.

After the template to render is determined, Renderer calls _render_template, which makes the call into ActionView.

I know it sounds rather complicated, but a graphic showing the relationships is forthcoming, which should clean things up. The nice thing is that there are several layers of abstraction, and while the final system is complex (mostly because the functionality is complex), it’s reasonably easy to understand the higher levels of abstraction on their own. It’s also easy to put various kinds of normalization into standard places, so the code you’re reading at any given point is code that expects normalized inputs. Since the normalization that Rails does can sometimes be quite gnarly (in the service of making the user experience really pleasant), separating that stuff out can reduce the amount of gnarliness in the internals themselves.

Finally, an important part of an architecture like this is making sure that there is great internal documentation (which we’ve already started with), and some visualizations that show what’s going on. If you were forced to track down the control flow above on your own for the first time, it would probably be non-trivial. So a key part of this architecture is making sure you never have to do that. I would also note that I’m not particularly good at expressing code reading in blog posts, so the process definitely sounds a lot more complex than it actually is ;)

16 Responses to “Rails Edge Architecture”

Thanks for outlining all this and for all your work!

Your work is remarkable, thx for what you’re doing…Rails 3 is going to be awesome :)

Thanks.

It seems i have to read the blog once again carefully to grasp few things.

Keep the good work up and thanks for posting the updates!!!

Is there any way to explicitly turn off the Rails2Compatibility module?

Interesting stuff, Yehuda. Thank you for the post.

That’s the idea. Right now (on edge), it’s very easy to build your own controller with just the features you need (and then inherit ApplicationController from that). The goal is to make the compatibility module optional.

You and the rest of the team are doing great work on this yehuda. I’m a bit more familiar with AR internals than AP, but your description sounds really good. I got a bit of the taste of the overgrown and tightly coupled abstractions as they existed when I created the Rails-Multisite plugin to allow productization of an by means of multiple views directories. Looking forward to upgrading the plugin to Rails 3.0 and checking out the tasty goodness.

Also, just in general it’s exciting that all these years later Rails is still able to do these kinds of massive refactorings.

Thanks for the great post on the architecture specification on Rails 3 !!

This kind of refactoring in such relatively short time always reminds me how much solid test base can do. Big thank you for making it easier to understand “what the hell is going on in there”.

Putting the Rails2Compatibility stuff in a separate module was genius. I like that a lot.

@DarkTatka – Good point on the testing. I need a good reminder like this every now and again… :-D

Thanks for the post, Yehuda. I can’t wait to see the diagrams and docs on the internals of Rails.

After years of existence, other framework developers would not dare do massive refactorings to get things right. What you’re doing shows a strong commitment to the success of Rails. The Rails community owes you guys big time :)

Quick q: will Rails 3 identify to the controller the route object that matched the request? This could be really useful for automatically generating link headers etc.

Thanks for your hard work!
You mentioned graphics outlining internal design of rails. Is it published anywhere?

You actually did a good job making it sound not too complex, Yehuda. I’m curious where is the best place to get solid documentation on Rails 3? I know of a few resources, but would certainly love to have more. Cheers.

Hey Yehuda,

first of all i would like to thank you, for your great work and your valuable contributions for the rails project.
Now to your, i know, older post:
i look for a graphical documentation of the rails architecture, because i agree with your argument, that it is too difficult the read the core by it self without a explanation. A good diagramm or flow-chart would be great, it creates a smoother access in the project, even for the academic world ;) otherwise they don’t believe it

Is there something in work?

Cheers

Leave a Reply

Archives

Categories

Meta