Better Rails Partial Rendering
Here was my problem. I wanted to render a polymorphic array like this.
E.g. templates named
tigers/tiger as the case may be for each element in the array. As you probably guessed, my application didn’t actually use big cats, but a biological hierarchy is clear for the sake of our example.
This solution had been working perfectly, until I wanted to share these partials in another controller, under a
Public namespace, causing the renderer to look in e.g.
public/lions/lion rather than
lions/lion for instance. I finally tracked down the problem to this method.
def find_template(path, locals)
prefixes = path.include?(?/) ?  : @lookup_context.prefixes
@lookup_context.find_template(path, prefixes, true, locals, @details)
In searching for a partial, that Rails
find_template method will do one of two things:
/in it, then
prefixeswill be set to an empty array and it will search all the view directories for
path. E.g. if
lions/lion, then it will find a template like
- Otherwise, it will traverse the controller hierarchy, e.g. if you have
MeanLionsController < LionsController < ApplicationController, then a path of
lionwould look for a
app/views/lions/, then finally
In addition to this, the
app/views/ directory isn’t the only one it’ll look for. There’s a list of view paths, and you can prepend or append to it with the controller class methods
append_view_path. So the product of all these means there can be a considerable number of places to look.
When passing in array of objects to be rendered, the
path argument comes from the objects’
to_partial_path method, which is defined on
ActiveModel::Base as essentially
lions/lion (the plural then the singular model name). This means Rails doesn’t traverse the
In addition, if the controller is prefixed, then
path also gets the prefix, e.g.
lions/lion instead becomes
public/lions/lion. This makes it impossible for our two controllers to reference the same partials in the default listing rendering case. We really need to prevent this namespacing. Rails thankfully gives us a setting to exclude this namespace prefixing on
config.action_view.prefix_partial_path_with_controller_namespace = false
With this change, the
path is now
lions/lion instead of
public/lions/lion. So we can simply put our template at
app/views/lions/_lion and all is well.
One little problem
The thing is, what do we do when we want to use the namespaces, such as when the namespaced controller doesn’t need to share its partials? Why couldn’t we have
app/views/public/lions/_lion for instance? For this use case, we could use
prepend_view_path('app/views/public'). Whereas if two or more related controllers share a common special partial (related to zoos for instance), they could invoke
But if the polymorphic collection has both objects that need to render the standard
app/views/lions/_lion and also the
app/views/zoo/lions/_lion, then the only solution is to write a custom
to_partial_path method, either on the model itself, or on a presenter object.
In that case, a path without a
/ could be convenient if those controllers share a common ancestor. Suppose all three controllers inherit from
ZooController. Then a partial path such as
lion would look under
app/views/zoo/_lion in any of those controllers.
If you really don’t care about DRY, you can ultimately have infinite flexibility via the custom
to_partial_path method. By using presenters, you could even make different instances of the same model (e.g. different
Lion records) render completely different partials deeply nested in unrelated locations under
app/views. I recommend you avoid this unless absolutely necessary, but the flexibility is there if you need it.