Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #7889 (closed defect: duplicate)

Opened 3 years ago

Last modified 1 year ago

ActionController::Base#view_paths unusable for dynamic template roots

Reported by: julik Assigned to: core
Priority: normal Milestone: 1.x
Component: ActionPack Version: edge
Severity: major Keywords:
Cc: court3nay@gmail.com

Description

The recent changeset to template lookup introducet the instance-based view_paths. The assumption has probably been that it might be used by the developer for switching template roots at runtime, depending on context.

For instance, on an application servicing domain1, domain2 and domain3, each domain might have it's own template root. That gets substituted in a pre_filter. The natural way to do this with view_paths would be to override the instance method and inject the template directory into the result of super.

However, this is totally useless because for some weird reason view_paths gets called even before the controller recieves a request. It also gets called before any of the user's filters take effect.

It has therefore become impossible to override the template roots per request. The use of a convoluted class attr assignment for view_paths class attribute makes the situation even worse. Currently the only way I could circumvent this is by using

class MyController < ...
   def view_paths
    @tamperer ||= StringPromise.new(self)
    super + [@tamperer]
  end
end

class StringPromise
  attr_accessor :controller
  def initialize(ctr); @controller = ctr; end
  def to_str
    RAILS_ROOT + '/app/magazine-views/' + @controller.magazine.slug
  end
  def to_s; to_str; end
end

Which seems totally ass-backwards but works. Can this breakage be resolved without refactoring the whole rendering pipeline? What has now become the recommended way of doing template root substitution per request?

Change History

03/31/07 18:54:26 changed by RSL

I'm hesitant to change the status of this ticket but you can [rather] easily set the template root dynamically via ActionController#controller_path. In the controllers I've needed a different template path for I used this code...

class << self
  def controller_path
    "relative/path/to/templates"
  end

  def layout
    super "relative/path/to/layouts"
  end
end

Make sure you use relative paths though or any errors you raise while compiling the template won't raise in the normal way due to issues with ActionView#find_base_path_for. All you'll see is a blank page and it was rather time-consuming to figure out what the problem was. Relative paths though are rock solid.

Hope that helps.

04/17/07 21:27:00 changed by court3nay

The reason is because ActionView::Base gets instantiated like this, which is before the filters get run. Note: initialize_template_class(response) is where view_paths is set for AV::Base. Note the second request will actually work since we've set view_paths.

465:
      def process(request, response, method = :perform_action, *arguments) #:nodoc:
        initialize_template_class(response)
        assign_shortcuts(request, response)
        initialize_current_url
        assign_names
        forget_variables_added_to_assigns

        log_processing
        send(method, *arguments)

        assign_default_content_type_and_charset

        response.request = request
        response.prepare!
        response
      ensure
        process_cleanup
      end

RSL: if you're doing theming, this doesn't work, since generally the theme relies on some of the request parameters (which aren't available to the class methods).

For example; this *should* work : {{

self.class.view_paths = File.expand_path("#{RAILS_ROOT}/themes/#{store.id}/views")

}}

but only does on the second request.

04/17/07 21:33:49 changed by court3nay

  • cc set to court3nay@gmail.com.

The fix for this (there goes my morning) is quite simple, actually.

1. ActionView::Base, make view_paths an attr_accessor, not an attr_reader 2. in application controller, do this:

      self.class.view_paths = File.expand_path("#{RAILS_ROOT}/themes/#{store.id}/views")
      @template.view_paths = File.expand_path("#{RAILS_ROOT}/themes/#{store.id}/views")

or however you want to do it. self.class is action controller, and @template is the instance of action view.

06/08/07 18:09:30 changed by bitsweat

  • status changed from new to closed.
  • resolution set to fixed.

06/10/07 11:08:54 changed by justinfrench

  • status changed from closed to reopened.
  • resolution deleted.

reopening this... as of revision [6980], view_paths is still an reader, not an accessor. [6509] sets it as a reader, even though the commit message says it's being set as an accessor, so it seems like it's just human error

06/10/07 16:37:20 changed by bitsweat

  • status changed from reopened to closed.
  • resolution set to duplicate.

11/26/08 14:30:19 changed by storney

The fix for this (there goes my morning) is quite simple, actually.

1. ActionView::Base, make view_paths an attr_accessor Faqs, not an attr_reader 2. in application controller, do this:

self.class.view_paths = File.expand_path("#{RAILS_ROOT}/themes/#{store.id}/views") @template.view_paths = File.expand_path("#{RAILS_ROOT}/themes/#{store.id}/views")

or however you want to do it. self.class is action controller, and @template is the instance of actionview.