Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to add a custom folder to the "partials path"?

I have several of my partials in a folder named partials, and I render them into my view using render 'partials/name_of_my_partial' and that's okay.

Anyhow, is it possible to set up things in a way than I could just use render 'name_of_my_partial' and rails automatically check this partials folder?

Right now, I'm having a Missing partial error.

like image 773
Erik Escobedo Avatar asked Sep 03 '11 15:09

Erik Escobedo


2 Answers

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)
like image 193
numbers1311407 Avatar answered Oct 13 '22 20:10

numbers1311407


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

like image 34
pdobb Avatar answered Oct 13 '22 20:10

pdobb