Jonathan Bennett

Using Dynamic Partials While Rendering

One of my favourite “magical” characteristics of Ruby on Rails is its naming conventions. We don’t need to argue about what the name of the database table that will hold information about a widget should be called, it’s widgets. Other frameworks, especially really old ones, didn’t do that.

Now, Rails is also great at letting you override and tweaks those options, but really, you almost never need to.

This same convention over configuration mindset continues throughout the framework, even to rendering stuff, which is what we are going to be looking at today.

Draw Me a Widget

With Rails, when you pull a record from the database, you can simply call <%= render @widget %> in your view to spit that thing out on the page. But what is it really doing? Well, assuming you haven’t changed any defaults, this is going to make a bunch of sane guesses:

  1. Your @widget is an instance of the Widget class so the partial we are going to use is widgets/_widget
  2. Your partial will expect a local variable named widget

This means <%= render @widget %> is effectively <%= render 'widgets/widget', locals: { widget: @widget } %>.

That’s a lot of extra code that you just don’t need. …unless you do.

I frequently view my widgets in different ways, and have different partials for them:

  • _widget.html.erb is the full page view
  • _widget_row.html.erb will be used in administrative table views
  • _widget_card.html.erb will be a concise view for use throughout the site
  • and others as is needed

This means I can’t use the simple render call, I need to do one with the partial defined: <%= render 'widgets/widget_row', widget: @widget %>. That’s slightly annoying but tolerable. It even works with arrays. Unless you are rendering an array of mixed things: <%= render @things %>. The problem here is that you need to do a different partial for each instance in the array potentially.

Well, I’m not going to let that stop me. Inspired by dom_id(@widget, :specialization) I decided to solve my rendering pain. Adding an additional helper gives me a flexible solution that feels like render and dom_id had a baby. Introducing polymorphic_render:

module ApplicationHelper
  def polymorphic_render(resource, suffix = nil, options = {})
    if resource.nil?
      return
    elsif resource.is_a? Array
      capture do
        resource.each { |res| concat polymorphic_render(res, suffix, options) }
      end
    elsif resource.respond_to? :to_partial_path
      path = [resource.to_partial_path, *Array(suffix)].compact_blank.join('_')
      name = File.basename(resource.to_partial_path)
      render options.merge(partial: path, object: resource, as: name)
    else
      # maybe Rails can figure it out
      render resource, options
    end
  end
end

The third branch is the workhorse of this method. It will wet the partial path to the normal partial path (widgets/widget) and append the suffixes to it. It gets the appropriate name of the instance, and it passes all that to the normal render method.

Basically it does all the manual stuff we normally do, but automatically.

This makes feed rendering code so nice. Instead of some nested monstrosity, its just a simple call to <%= polymorphic_render @feed.items, :feed %> and the correct things/thing_feed partial will be used.