Is there a way to add a custom folder to the "partials path"?
In rails 3.0 this a bit of a challenge, but looking into it I found that in rails 3.1, they've changed how path lookup works, making it much simpler. (I don't know what exact version they changed it though, it may have been much earlier).
In 3.1, this is relatively simple because they've introduced a way to send multiple prefixes to the path lookup. They are retrieved via the instance method _prefixes
.
It's easy to tack on an arbitrary prefix to this, for all controllers, by simply overriding it in the base controller (or in a module you include in your base controller, whichever).
So in 3.1.x (where lookup uses multiple prefixes):
class ApplicationController
...
protected
def _prefixes
@_prefixes_with_partials ||= super | %w(partials)
end
end
Prior to this change, a single prefix was used for lookup, which made this a lot more complicated. This may have not been the best way, but I solved this problem in the past by rescuing from missing template errors with an attempt to look up the same path with my "fallback" prefix.
In 3.0.x (where lookup uses a single path prefix)
# in an initializer
module YourAppPaths
extend ActiveSupport::Concern
included do
# override the actionview base class method to wrap its paths with your
# custom pathset class
def self.process_view_paths(value)
value.is_a?(::YourAppPaths::PathSet) ?
value.dup : ::YourAppPaths::PathSet.new(Array.wrap(value))
end
end
class PathSet < ::ActionView::PathSet
# our simple subclass of pathset simply rescues and attempts to
# find the same path under "partials", throwing out the original prefix
def find(path, prefix, *args)
super
rescue ::ActionView::MissingTemplate
super(path, "partials", *args)
end
end
end
ActionView::Base.end(:include, YourAppPaths)
I haven't been able to find other resources than this SO question on this topic, so I'm posting about my efforts here.
In Rails 3.2+ (Also tested in 4.2), one can access and modify lookup_context.prefixes
from within a controller action. So, to modify lookup of template & partial prefixes to include another path you can do this:
class MyObjectsController < ApplicationController
def show
# WARNING: Keep reeding for issues with this approach!
unless lookup_context.prefixes.first == "partials"
lookup_context.prefixes.prepend "partials"
end
end
end
This way, if there is a show.html.erb template in the app/views/partials/ folder then it will be rendered. And the same goes for any partials referenced in show.html.erb.
What's going on here?
The method lookup_context
returns an object of type ActionView::LookupContext
, which is the object responsible to hold all information required to lookup templates in ActionView
. Of note, it also gives you access to lookup_context.view_paths
, which is not what is being asked for in this question but sounds like it should be.
WARNING
Modification of the lookup_context.prefixes
array is cached for all future requests. Therefore, in order to use it problem-free, it's best to make sure we also remove any prefixes we add.
So, is there an easy way to do this?
Sure. For my own projects, I've created a module which I can include in any controller that needs this ability (or just include it in ApplicationController
). Here's my code:
# Helper methods for adding ActionView::LookupContext prefixes on including
# controllers. The lookup_context.prefixes collection is persisted on cached
# controllers, so these helpers take care to remove the passed-in prefixes
# after calling the block.
#
# @example Expected Usage:
# around_action only: :show do |_, block|
# prepend_lookup_context_prefixes("my_optional_name_space/my_objects", &block)
# end
#
# around_action only: %i[index edit update] do |_, block|
# append_penultimate_lookup_context_prefixes("my_optional_name_space/my_objects", &block)
# end
module PrefixesHelper
# Prepends the passed in prefixes to the current `lookup_context.prefixes`
# array, calls the block, then removes the prefixes.
#
# @param [Array<String>] prefixes
def prepend_lookup_context_prefixes(*prefixes, &block)
lookup_context.prefixes.prepend(*prefixes)
block.call
remove_lookup_context_prefixes(*prefixes, index: 0)
end
# Sets the penultimate (2nd-to-last) prefixes in the current
# `lookup_context.prefixes` array, calls the block, then removes the prefixes.
#
# @param [Array<String>] prefixes
def append_penultimate_lookup_context_prefixes(*prefixes, &block)
lookup_context.prefixes.insert(-2, *prefixes)
block.call
remove_lookup_context_prefixes(*prefixes.reverse, index: -2)
end
# Removes the passed in prefixes from the current `lookup_context.prefixes`
# array. If index is passed in, then will only remove prefixes found at the
# specified index in the array.
#
# @param [Array<String>] prefixes
# @param [Integer] index
def remove_lookup_context_prefixes(*prefixes, index: nil)
prefixes.each do |prefix|
if index
if lookup_context.prefixes[index] == prefix
lookup_context.prefixes.delete_at(index)
end
else
lookup_context.prefixes.delete(prefix)
end
end
end
end
As mentioned in the comments in this module, the expected usage for this module is to call the methods contained within via an around_filter
call in your controller. This way, the module will take care of removing any prefixes it adds after the controller action has yielded.
For example:
around_filter only: :show do |_, block|
prepend_lookup_context_prefixes("my_optional_name_space/my_objects", &block)
end
Or:
around_filter only: %i[index edit update] do |_, block|
append_penultimate_lookup_context_prefixes("my_optional_name_space/my_objects", &block)
end
I've also included the PrefixesHelper
module posted here into a gem that I use to add a few nice extensions such as these to my Rails apps. In case you'd like to use it too, see here: https://github.com/pdobb/core_extensions