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.

Today’s Dispatch: Weaning ActionView off of content negotiation

I’ve been on “vacation” for the past few days and haven’t had a ton of time to work on continuing refactoring, but I did manage to make a bit more progress, and I figured I’d share.

In Rails 2.2, ActionView had a fair number of content negotiation responsibilities. In particular, ActionView::Base had a method called template_format that looked like this:

def template_format
  if defined? @template_format
    @template_format
  elsif controller && controller.respond_to?(:request)
    @template_format = controller.request.format.to_sym
  else
    @template_format = :html
  end
end

Effectively, ActionView had a small content-negotiation responsibility, that effectively entailed grabbing the first acceptable format and using that. Thankfully, this particular piece of code was usually supplanted by Rails’ respond_to code, which performed more proper content-negotiation, and set the template format directly: @response.template.template_format = mime_type.to_sym.

A number of other places, including the JavaScript helpers, also set the template format directly, which meant that for the most part, the template_format method was just a placeholder that was set by other parts of the system. However, there were more than a few cases where the default template_format method was getting used. Instead of having ActionView call back into the request object, I wanted to modify ActionView’s initialization so that ActionController could feed it the list of acceptable formats directly.

With some work, I was able to make the modification, weaning ActionView off of content negotiation. Interestingly, this is related to my overall refactoring, which currently uses a four-element tuple to represent templates (path, acceptable extensions, prefix, and a partial flag). Now that ActionView uses a list of formats internally, it can easily pass them into find_by_parts, which handles figuring out which template to use based on the ordered list of extensions.

Just a note: find_by_parts is almost certainly a temporary creation to facilitate refactoring. However, for the purposes of refactoring, it is a convenient way to make sure that all pieces of the puzzle are passing around compatible values. In the end, we will probably be passing around Template objects, and this intermediate refactoring step will help us get all of our ducks in a row so that we can get there.

A fun diff that sort of demonstrates where I’m going with this is:

   def _pick_partial_template(partial_path) #:nodoc:
-    if partial_path.include?('/')
-      path = File.join(File.dirname(partial_path), "_#{File.basename(partial_path)}")
-    elsif controller
-      path = "#{controller.class.controller_path}/_#{partial_path}"
-    else
-      path = "_#{partial_path}"
+    unless partial_path.include?('/')
+      prefix = (controller && controller.class.controller_path)
     end
-
-    self.view_paths.find_template(path, self.template_format)
+    view_paths.find_by_parts(partial_path, formats, prefix, true)
   end

As you can see, by normalizing all path lookup to the four-element tuple, which includes merging path prefixes and appending “_” to partials, I was able to remove almost all of the code of this method, now requiring only that the controller_path is specified as a prefix if no existing prefix is supplied as part of the path (that’s so render :partial => “foo” inside of the TestController looks for “test/_foo”).

Again, I probably won’t be able to do a ton of work over the next few days, but I’ll blog when I can. Happy holidays folks!

3 Responses to “Today’s Dispatch: Weaning ActionView off of content negotiation”

Perhaps OT: This kind of reminds me of a hack I’ve been using in Rails 2 that I know won’t be supported in Rails 3. Whenever I’ve wanted to reuse a template across multiple formats, say for example so it can be displayed for both regular html format as well as for mobile phones, I’ve just named the template to *.rhtml, and voila, instant template reuse. I’m sure everybody can agree that this is a terrible idea, and it’s common knowledge that this will be deprecated in 3.0 (but if I’m still working on this app in 5 months, somebody needs to shoot me).

I noticed in your commits that you’ve modified format to be a formats array, which got me to thinking about supporting multiple formats with the same template. Will this be workable in Rails 3 (not as in my hackish example above, but with a cleaner, supported approach)?

And once again, thanks for giving some commentary on the commits, it’s great to know where Rails 3 is going.

The reason for the formats array is to support multiple, prioritized formats. So you can ask for :html, :xml, :all, and the first one of those will be used. There’s no actual public API for this (yet?) — it’s triggered by Accept header handling when multiple mimes are provided.

I’m interested in hearing what you had in mind.

Just out of interest, is there any plans to make a public API for this at some point.

I ended up stumbling here, because I have a few pages on a project that need rendering with alternative markup, so I’ve registered a MIME alias in config/initializers/mime_types.rb for HTML (bit like the iphone example they give).

What would be handy is the ability to fall back on rendering HTML mime templates when templates for my specific mime alias can not be found.

For now I’m getting round this by omitting the MIME type from the template extension, but this is “catch all”, feels a bit dirty given it the catch all is global for all MIME types (and thus should be an issue).

(I probably one day will have a peek at the internals for this probably come up with a little short term hack until such API does arise… And on a unreleated note, excellent work here and all the other places with cleaning up Rails 3.x, I have to say I find the source far more palatable these days :D)

Leave a Reply

Archives

Categories

Meta