Yehuda Katz is a member of the Ruby on Rails core team, and lead developer of the Merb project. He is a member of the jQuery Core Team, and a core contributor to DataMapper. He contributes to many open source projects, like Rubinius and Johnson, and works on some he created himself, like Thor.
Rails 3: The Great Decoupling
July 19th, 2009
In working on Rails 3 over the past 6 months, I have focsed rather extensively on decoupling components from each other.
Why should ActionController care whether it’s talking to ActionView or just something that duck-types like ActionView? Of course, the key to making this work well is to keep the interfaces between components as small as possible, so that implementing an ActionView lookalike is a matter of implementing just a few methods, not dozens.
While I was preparing for my talk at RubyKaigi, I was trying to find the smallest possible examples that demonstrate some of this stuff. It went really well, but I noticed a few areas that could be improved even further, producing an even more compelling demonstration.
This weekend, I focused on cleaning up those interfaces, so we have small and clearly documented mechanisms for interfacing with Rails components. I want to focus on ActionView in this post, which I’ll demonstrate with an example.
$:.push "rails/activesupport/lib" $:.push "rails/actionpack/lib" require "action_controller" class Kaigi < ActionController::Http include AbstractController::Callbacks include ActionController::RackConvenience include ActionController::Renderer include ActionController::Layouts include ActionView::Context before_filter :set_name append_view_path "views" def _action_view self end def controller self end DEFAULT_LAYOUT = Object.new.tap {|l| def l.render(*) yield end } def _render_template_from_controller(template, layout = DEFAULT_LAYOUT, options = {}, partial = false) ret = template.render(self, {}) layout.render(self, {}) { ret } end def index render :template => "template" end def alt render :template => "template", :layout => "alt" end private def set_name @name = params[:name] end end app = Rack::Builder.new do map("/kaigi") { run Kaigi.action(:index) } map("/kaigi/alt") { run Kaigi.action(:alt) } end.to_app Rack::Handler::Mongrel.run app, :Port => 3000
There’s a bunch going on here, but the important thing is that you can run this file with just ruby, and it’ll serve up /kaigi and /kaigi/alt. It will serve templates from the local “/views” directory, and correctly handle before filters just fine.
Let’s look at this a piece at a time:
$:.push "rails/activesupport/lib" $:.push "rails/actionpack/lib" require "action_controller"
This is just boilerplace. I symlinked rails to a directory under this file and required action_controller. Note that simply requiring ActionController is extremely cheap — no features have been used yet
class Kaigi < ActionController::Http include AbstractController::Callbacks include ActionController::RackConvenience include ActionController::Renderer include ActionController::Layouts include ActionView::Context end
I inherited my class from ActionController::Http. I then included a number of features, include Rack convenience methods (request/response), the Renderer, and Layouts. I also made the controller itself the view context. I will discuss this more in just a moment.
before_filter :set_nameThis is the normal Rail before_filter. I didn’t need to do anything else to get this functionality other than include AbstractController::Callbacks
append_view_path "views"Because we’re not in a Rails app, our view paths haven’t been pre-populated. No problem: it’s just a one-liner to set them ourselves.
The next part is the interesting part. In Rails 3, while ActionView::Base remains the default view context, the interface between ActionController and ActionView is extremely well defined. Specifically:
- A view context must include ActionView::Context. This just adds the compiled templates, so they can be called from the context
- A view context must provide a _render_template_from_controller method, which takes a template object, a layout, and additional options
- A view context may optionally also provide a _render_partial_from_controller, to handle
render :partial => @some_object - In order to use ActionView::Helpers, a view context must have a pointer back to its original controller
That’s it! That’s the entire ActionController<=>ActionView interface.
def _action_view self end def controller self end
Here, we specify that the view context is just self, and define controller, required by view contexts. Effectively, we have merged the controller and view context (mainly just to see if it could be done ;) )
DEFAULT_LAYOUT = Object.new.tap {|l| def l.render(*) yield end }
Next, we make a default layout. This is just a simple proc that provides a render method that yields to the block. It will simplify:
def _render_template_from_controller(template, layout = DEFAULT_LAYOUT, options = {}, partial = false) ret = template.render(self, {}) layout.render(self, {}) { ret } end
Here, we supply the required _render_template_from_controller. The template object that is passed in is a standard Rails Template which has a render method on it. That method takes the view context and any locals. For this example, we pass in self as the view context, and do not provide any locals. Next, we call render on the layout, passing in the return value of template.render. The reason we created a default is to make the case of a layout identical to the case without.
def index render :template => "template" end def alt render :template => "template", :layout => "alt" end private def set_name @name = params[:name] end
This is a standard Rails controller.
app = Rack::Builder.new do map("/kaigi") { run Kaigi.action(:index) } map("/kaigi/alt") { run Kaigi.action(:alt) } end.to_app Rack::Handler::Mongrel.run app, :Port => 3000
Finally, rather than use the Rails router, we just wire the controller up directly using Rack. In Rails 3, ControllerName.action(:action_name) returns a rack-compatible endpoint, so we can wire them up directly.
And that’s all there is to it!
Note: I’m not sure if I still need to say this, but stuff like this is purely a demonstration of the power of the internals, and does not reflect changes to the public API or the way people use Rails by default. Everyone on the Rails team is strongly committed to retaining the same excellent startup experience and set of good conventional defaults. That will not be changing in 3.0.

AJ (fujin), Posted July 20, 2009, 2:14 am
Looking nice, dude. Certainly more merby.
AJ (fujin), Posted July 20, 2009, 2:15 am
Meant to say, also; it’s great that you can wire controllers directly upto Rack like that.
wycats, Posted July 20, 2009, 2:27 am
@AJ it’s actually a significant improvement on the Merb architecture :) But yeah — I’ve been about clean public interfaces for a couple of years.
stoimen, Posted July 20, 2009, 3:13 am
Nice! Thanks!
Christian Seiler, Posted July 20, 2009, 6:14 am
What about ActiveRecord stuff like validations? Will it still be a feature of AR or extracted so you can use it to add validations to just any class (somethign I find to be useful).
Chetan, Posted July 20, 2009, 6:27 am
I agree with the Merby Feel.
Since I derailed and started my learning with Merb rather than Rails, it perfectly resonates.
Great job, wycats
Craig Buchek, Posted July 20, 2009, 10:21 am
One thing I’m looking to do is set a fall-back view directory. So if I’m in PostsController#index, the template in app/views/default/index.html.erb will be used if app/views/posts/index.html.erb isn’t found. In Rails 2.x, I wasn’t able to find a hook that would allow doing this very cleanly. Is there a good place in Rails 3 that I can hook this in?
wycats, Posted July 20, 2009, 10:25 am
@craig So you’re saying you want to use a fallback *prefix* in the same view path? In Rails 3, you can override _determine_template to provide the fallback path. I have to think about how that would impact layouts and get back to you with the right solution, but that would be the right hook point.
wycats, Posted July 20, 2009, 10:30 am
@Christian I talked about this some at RubyKaigi, and the answer is yes. Josh has been working on ActiveModel, which lets you use Rails callbacks, validations, state machine, etc. in plain old Ruby objects. You can check out my slides at http://files.me.com/wycats/o2snu8
Neil, Posted July 21, 2009, 3:43 am
Is there an example of a layout file somewhere?
James, Posted July 21, 2009, 4:43 am
Great work!
Do you have any plans to use modules instead of inheritance for the basic ActiveRecord / ActionController functionality?
RailsGirl, Posted July 22, 2009, 5:25 am
@wycats,
http://files.me.com/wycats/o2snu8
Your link gives me a corrupted pdf file. Pl. provide a fresh link
Thanks
SoftMind, Posted July 22, 2009, 6:38 am
@wycats @railsgirl
I have uploaded the new file at Rapidshare.
Here’s the link:- http://bit.ly/QyuPK
Hope this helps all.
Gaspard Bucher, Posted July 23, 2009, 4:15 am
Frameworks and CMS like zena will greatly benefit from this kind of clear internal interfaces. Guessing and discovering how to sneak into view context while in the controller was a pain and subject to break as any new version of rails came out.
Thanks to this decoupling, it seems things will get much easier.
G.
Peter, Posted July 23, 2009, 9:00 pm
So I am looking to start a new website and I’m going to join the web revolution. But I’m stuck in the middle.
Do I learn Merb or Rails?
Matthijs Langenberg, Posted July 25, 2009, 7:44 pm
@Peter, go for Rails. It’s easier to learn, there are plenty examples, good documentation and books available. After you are familiar with Rails and want to go more advanced, Rails 3.0 is probably available to you. :-)
katz, Posted July 31, 2009, 10:00 am
Great work. Many thanks to you and everyone in the core team. Got MEAP for Rails 3 in Action just now. I’m trying to learn to the concepts behind Merb. I really don’t know the difference, honestly.
ara.t.howard, Posted August 1, 2009, 9:24 pm
yehuda -
you say
“In order to use ActionView::Helpers, a view context must have a pointer back to its original controller”
but why is that? frameworks like ramaze have always have had features like Session.current, Controller.current, View.current, which are immensely useful (they are Thread local vars btw). i personally always have a before filter that sets the current controller in my rails app. why no make the interface even the smaller – it’s the controller that renders the view so why shouldn’t it provide a context (current_controller) under which the view could be rendered?
if you think about it this elegantly also elimnates the need for the _render…controller and _render_…partial interfaces since the template merely need ask (where context==@controller || context==@object)
if context.partial?
or
if context.controller?
or some such. put another way, if simple public interfaces are good, then recursively simple public interfaces are better. aka – the view interface can be collapsed if the context itself has a simple interface and the template can ask it’s context questions.
i think one can make a very, very strong argument that the minimal/best interface to any stack component is ‘.call(*args)’ and it’s certainly possible here too.
food for thought. i am very, very happy about the direction rails is going these days – keep up the good work.
ara.t.howard, Posted August 1, 2009, 9:26 pm
@Christian – check out the validations file in the sequel dist for extraction of that feature. i use it in several of my projects. ‘sudo gem install assistance’
Dayane Farias, Posted September 16, 2009, 10:02 pm
Hello!! I’m from Brazil and I’m reading an article yours translated to portuguese by a student brazilian named Helton Duarte. This is excellent! It’s a great begin! Congratulations!I’d like to see more translations like that.
http://heltonduarte.com/2009/09/12/minhas-10-coisas-favoritas-sobre-ruby/
:)
KYle, Posted January 7, 2010, 11:42 am
I’m getting my rails addiction back… Nevr thought of rails 3 will be this good