Problem with Multiple Controller View Paths

Hi, I'm pretty new to hacking Rails internals, but I've recently
switched to edge for several reasons. One of which is the use of the
multiple controller view paths announced at
http://weblog.rubyonrails.com/2007/2/4/new-feature-for-rails-2-0-multiple-controller-view-paths

Anyway, I've developed a plugin that prepends an additional path much
as described in the article. The problem is that it is working for
layouts, but not regular templates. If I remove the regular template
from the default views directory then it does work, but when it exists
it is not being overriden correctly.

I'm working on debugging this issue and creating a patch, but I
figured someone might be able to point me more quickly in the right
direction. I have stuck debug information in a few places, so I can
verify that the following is happening:

* ActionController::Base.view_paths is being set correctly up front in
an around_filter before the action executes.
* AcionView::Base.find_base_path_for does not immediately pick up this
change even though it was made earlier in the execution. Both the
layout and the template are 'found' using the default path during the
first dozen executions of this method (while it's searching different
extensions).
* When the actual render is performed (ie. after the logger output
render content/index is printed) the template is located from the
default path as well, but when it searches for layouts/application,
suddenly the full paths I specified up front are used and it finds the
correct template.

I'm a little rough on the whole rendering flow, so I'm hoping someone
can point me in the right direction with a quick insight.

I, for one, am not sure what kind of behavior are you trying to describe. Can you pastie us some code?
It may be useful for you if you observed and played with unit tests for this:
action_pack/test/controller/view_paths_test.rb
You can try and make a failing tests from you observations.
-M

After closer examination, the problem is even weirder. As far as I
can tell, ActionView::Base.view_paths is ONLY set in the initialize
function. However, I put a debug statement in there and
ActionView::Base is initialized only once, yet somehow the value of
@view_paths changes in the middle of the execution. I reviewed the
original patch and it looks like the layout module finds it's own
template outside of ActionView, which would explain why it finds the
right layout, but it doesn't explain how @view_paths is being updated
inside ActionView::Base where my debugging statement is.

Okay, good idea, I'll work on a test.

I could be wrong but I seem to recall that you can't set view_paths on
a per-request basis (this may also be an old limitation that's been
changed/removed).

I recall that when I looked at it I concluded that the view class was
being set-up before before_filters fire.

Sorry I can't be more specific - hopefully it's not a red herring.

Trevor

Well it's certainly true in practice because of exactly what you say,
ActionView::Base is initialized early. However all that would be
necessary for it to work is to reference the canonical view_paths from
ActionController.

I know it's kind of a dangerous feature because if people add paths
without removing them, the controller would get slower and slower, but
it enables a whole new realm of functionality that I desperately need.

Can you describe step by step:

  1. how did you set up view_paths,
  2. what did you expect when you changed something,
  3. what happened in turn.
    There is no code that can change view_paths. There isn’t even a writer for the instance variable, just reader! Are you sure you’re debugging right? Can you provide a failing test?

From quick observation of controller code I think you can, but I can not imagine why would one do that.

AC::Base#process is called on a per-request basis. It calls initialize_template_class, which instantiates a new ActionView::Base instance, passing it current view_paths as argument. If view_paths change, new requests should therefore pick it up.

I may be also wrong, this was just a quick observation. But I still don’t understand what the problem is, Gabe didn’t explain it too well.

My use case is an app that is fully customizable to client's needs by
overriding any of the templates, still able to use default templates
(ie. Rails 1.2.3 view_root is insufficient, I had been using
theme_support plugin, but it was a nasty hack) . The search path is
set per request based on the domain that fielded the request.

I thought my explanation was pretty thorough, although maybe all the
details are confusing things, and the fact that layouts happen to work
is tangential (although it's a big part of my confusion)

The essential problem is this:

ActionView::Base is initialized early on in the request cycle with the
view_paths that exist at the time. If they are set by a plugin during
initialization as is demonstrated in the blog entry and the tests,
then it's fine.

I've fixed the issue by writing a method ActionView::Base.view_paths
to reference @controller.view_paths if @controller exists so it will
always use the current paths. I'm working on a test to demonstrate
the problem and solution.

Okay, I've submitted my test and fix as separate patches under a new
ticket at http://dev.rubyonrails.org/ticket/8582

Let me get your feedback, I can think of a couple other ways to solve
this, but this seemed like the safest way.