Another Dispatch: AbstractController

On Monday, Carl started pairing with me on Rails 3. This week, we worked on further fleshing out the AbstractController. A few principles we're following:

  • The AbstractController should be a low-level API. Nobody should be using the AbstractController directly, and subclasses of AbstractController (like ActionController::Base) are expected to provide their own #render method, since rendering means different things depending on the context.
  • However, AbstractController should provide enough facilities to avoid reinventing the wheel. For instance, the subclasses might be responsible for figuring out which layout to use, but should not need to know how to render a template with a layout.
  • It is a common desire to be able to add new options to render. This should be possible in an isolated way without interfering with other parts of the controller. We achieved this by making it possible to do:
```ruby module ActionController module Layouts def render_to_string(options) options[:_layout] = options[:layout] || _layout super end
def _layout
  # implementation here
end

end
end

class ActionController::Base < ActionController::HTTP
include ActionController::Layouts
end

In this case, options[:_layout] is used by AbstractController's Layouts module. If you are implementing a subclass of AbstractController and want to make it possible to supply or use a layout, simply make a new module that sets the :_layouts key in the options hash and super. It may well turn out that all subclasses have the same logic for implicit layouts. If that's the case, we can move the _layout up into AbstractController and let subclasses invoke it.

The specifics aren't particularly important (yet), because we're still working out what exactly needs to be modular, and what is more appropriately shared. But the bottom line is that we're working toward some simple APIs that people can use to build their own customized controllers as well as extend built-in controllers without fear of breaking something else. Effectively, the API is "take in options hash, modify it if appropriate (possibly adding in keys to be used by AbstractController), super".

Of course, we will need to document what the AbstractController keys are (so far we have :_prefix to specify the path to the template name to use, and :_layout to specify the layout to use). The specifics of all of this may change, and a crucial part of this is to cleanly document the architecture and API (my biggest frustration with the similarly architected current ActionController::Base is just how difficult is it to discover what's going on), but I think we're on the right track.

Finally, we've begun adding a large number of happy-path tests using Rack::Test that have been very effectively in helping to design and develop the more modular code. I'll have more on this in the next couple of days, but the tests we've been writing have been very cool.