rails: ActionView is raising errors in the test environment after upgrading to Rails 6 (ActionView::Template::Error: undefined method `_app_views_xxxxx)

Expected behavior

ActionView template compilation is reliably executed in test and dev environments.

Actual behavior

Tests (and sometimes the dev server) fail intermittently with exceptions like

Exception: ActionView::Template::Error: undefined method `_app_views_layouts__dmca_html_slim___3627166963330339270_108200' for #<#<Class:0x00007fac1acfaa58>:0x00007fac1ecec170>
Did you mean?  _app_views_layouts__header_nav_html_slim__4252095906685398667_109480

In our existing Rails app that we recently upgraded from 5.2.4 to 6.0.3.4, we’re consistently facing build failures with the described exception. Rerunning a few times will sometimes make the issue go away.

System configuration

Rails version: Rails 6.0.3.4

Ruby version: ruby 2.7.2p137


Unfortunately, I can’t figure out steps to reproduce this on a new app 😦, but I have done some diving into the ActionView code to try to figure out what’s going on.

I’m running a file with controller tests, and when I run the tests with a particular seed, I’m able to consistently reproduce the error. Using pry-debugger and puts, I’ve worked out that ActionView template compilation seems to be the issue. For whatever reason, the ActionView::Template for the file has the @compiled flag set to true, but the method_name generated by the template has not been defined on the view.

When I step through the code here, https://github.com/rails/rails/blob/master/actionview/lib/action_view/template.rb#L152-L154 , I get to a state that I don’t understand:

    def render(view, locals, buffer = ActionView::OutputBuffer.new, add_to_stack: true, &block)
      instrument_render_template do
        compile!(view)
       # At this point, the template should have been compiled, and @compiled is set to true. But `view.respond_to?(method_name)` returns `false`. ??????
        view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, &block)
      end
    rescue => e
      handle_render_error(view, e)
    end

I’ve found two things that seem to stop the error.

First is adding a line before the compile! call above:

    def render(view, locals, buffer = ActionView::OutputBuffer.new, add_to_stack: true, &block)
      instrument_render_template do
        @compiled = false
        compile!(view)
        view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, &block)

For obvious reasons, this forces ActionView to (re)compile the template, and this prevents the error from being raised.

Second, I’ve found that slowing down our first call to render seems to stop the issue from happening. In the controller test I mentioned above, I’m testing FoosController#show. FoosController#show explicitly calls render "show" (for unrelated reasons). If I insert a sleep(0.1) call right before the render "show" line, the issue disappears. If I then change that sleep call to sleep(0.0), the error returns. I don’t know what that means, but it seems like a race condition.

@jhawthorn , I know you were working on the template compilation recently. Do you have any ideas what could be happening here?

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 2
  • Comments: 28 (6 by maintainers)

Most upvoted comments

This is also happening to me when running the tests. on

Rails version: Rails 6.0.3.6

Ruby version: ruby 2.6.6

I noticed that We have config.cache_classes = true in the test.rb file and we do NOT have config.action_view.cache_template_loading

When I added config.action_view.cache_template_loading = true , it started working again

The exception is something like this,

— Caused by: —

 # NoMethodError:
 #   undefined method `_app_views_XXXXXXXXXXX__4472450733415361230_47313648806920' for #<ActionView::Base:0x0000561022862e40>
 #   Did you mean?  _app_views__app_views_XXXXXXXXXXX__4472450733415361230_47313645039900
 #                  _app_views__app_views_XXXXXXXXXXX__4472450733415361230_47313624130720
 #                  _app_views__app_views_XXXXXXXXXXX__4472450733415361230_47313636373300
 #   /usr/local/bundle/gems/actionview-6.0.3.6/lib/action_view/base.rb:274:in `_run'

@davidwessman in case it’s helpful, I replaced some janky code rendering templates in a delayed job with a call to the controller to render the template and it fixed this problem for me. Thanks for the comment.

Similar issue when using ActionView::Base after upgrading to rails 6.1:

view = ActionView::Base.with_empty_template_cache.with_view_paths(paths)
view.render(template: 'label_styles/show')
# => undefined method _app_views_xxxx

After switched to a subclass with compiled_method_container, the error is gone:

class LabelStyleView < ActionView::Base
  def compiled_method_container
    self.class
  end
end
view = LabelStyleView.with_view_paths(paths)
view.render(template: 'label_styles/show')
# => works fine

I guess @davidwessman is right about the culprit:

the first time the partial is called it will cache the result as a method that is no longer defined in the second context

@yjukaku I don’t think the problem is related to production config (set to true and default (not in config)), but did find a problem in view_component. With that fix in place, the errors seem to be gone. Thanks for the help!