rails: Layout can no longer access local variables

Steps to reproduce

After upgrading from Rails 5.0.6 to Rails 5.1.4 I am no longer able to render locals to a layout. Rendering to a template still works.

controllers/users_controller.rb

class UsersController < ApplicationController
  layout 'application'

  def index
    render locals: { user_name: 'John' }
  end
end

views/layouts/application.html.erb

...
<%= user_name %>
...

views/users/index.html.erb

...
<%= user_name %>
...

Expected behavior

Expected behavior is user_name appears in both the layout and the index template.

Actual behavior

ActionView::Template::Error: undefined local variable or method 'user_name'

is raised by layout.

System configuration

Rails version: 5.1.4

Ruby version: 2.4.2

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 11
  • Comments: 20 (8 by maintainers)

Commits related to this issue

Most upvoted comments

Confirm this is issue in Rails 5.2.2 as well, specifically when attempting to render outside of a controller, for example generating PDF reports from a background job.

ApplicationController.render(template: '/formats/report_template.html.erb', 
  layout: '/layouts/report.html.erb',
  locals: { report_name: 'test'}
)

Where report_name local is used in the layout, will fail with ActionView::Template::Error: undefined local variable or method `report_name’

Just to highlight something very important @cmcguff said, which I missed the first time around until I finally saw it in my own rspec specs, if a local named :foo is used in the layout file the ActionView::Template::Error occurs; but if you use local_assigns[:foo] in the layout, that works.

So local_assigns is not required in the template file, but is required for variables used in the layout file.

+1 for a permanent fix for this. I can also confirm this is an issue in 5.1.4 + 5.1.6

To add…

With a partial: _example.html.erb

...
<%= link_to('This is a link', link) unless link.empty? %>
...

This doesn’t work: show.html.erb

...
<%= render partial: 'example', locals: { example: example, link: example['link'].to_s } %>
...

But this works:

...
<%= render partial: 'example', example: example, link: example['link'].to_s %>
...

Using local_assigns works as well, but it feels like a step backwards if that’s the new norm.

Any news? It still doesn’t work on Rails 5.1.6.1

@composerinteralia I don’t follow — d6bac046922fcee05366d26d75349dde70d25f6b looks like a performance optimisation for looking up the layout, with no suggestion that it should affect the behaviour. Perhaps @tenderlove can confirm or deny that the behaviour change is intentional?

I was able to recreate this as well. I can look into it. In the meantime I think you can use local_assigns[:user_name] as a workaround.

Getting this in 5.2.1 What’s the status?

Based on d6bac04 I believe this is the intended behavior. If so, it is probably worth adding to the documentation.