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.