Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: Passing local variables from Controller to View

I was trying to pass a local variable from controller to a view:

# pages_controller.rb
def my_method
  render template: "pages/new_page", message: @message
end

# new_page.html.erb
<% if message %>
  Do something
<% else %>
  Do something else
<% end %>

But this gives me undefined local variable or method message error, whereas this works correctly in case of partials.

I know this can be fixed like this:

def my_method
  render template: "pages/new_page", locals: { message: @message }
end

I was just eager to know, how this works without locals in case of partials.

like image 370
Rajkaran Mishra Avatar asked Aug 30 '25 17:08

Rajkaran Mishra


2 Answers

It's all part of the (not always clear or clearly documented) magic of render. Per a comment in the code (github):

If no options hash is passed or if :update is specified, then:

If an object responding to render_in is passed, render_in is called on the object, passing in the current view context.

Otherwise, a partial is rendered using the second parameter as the locals hash.

It's the last part of the comment that is relevant. When you use the shorthand form of render to render a partial implicitly, Rails just assumes that the hash that follows are meant to be passed as locals. So:

<%= render 'my_partial', message: 'I hate bananas' %>

will work fine and message ends up as a local in your partial.

As soon as you specify an option to render (including, ironically, :partial), you need to explicitly pass a locals hash to pass locals:

# BAD
render template: 'foo/bar', message: 'I hate bananas'
render partial: 'my_partial', message: 'I hate bananas'
render 'full_view', message: 'I hate bananas'  # no options, but not a partial

# GOOD
render template: 'foo/bar', locals: { message: 'I hate bananas' }
render 'full_view', locals: { message: 'I hate bananas' }
render partial: 'my_partial', locals: { message: 'I hate bananas' }

(I don't really hate bananas.)

This part is just opinion, but I don't think the implicit locals in one specific use case is worth it. I tend to just always wrap partial locals explicitly as :locals to save remembering stuff. Note also that some folks would argue that Rails convention is to use instance vars for full views rather than locals, but it is totally valid syntactically.

like image 84
rmlockerd Avatar answered Sep 02 '25 15:09

rmlockerd


The short answer is that render is an overloaded function and can be called in a "short form", that is

render 'number_display', value: 4

which automagically translates to

render :partial => 'number_display', :locals => { :value => 4 }

The long answer can be found here.

Generally speaking, the short form is always going to render a partial, while the second example could specify different options:

The primary options are:

:partial - See ActionView::PartialRenderer[...] for details.

:file - Renders an explicit template file. Add :locals to pass in, if so desired. It shouldn’t be used directly with unsanitized user input due to lack of validation.

:inline - Renders an ERB template string.

:plain - Renders provided text and sets the content type as text/plain.

:html - Renders the provided HTML safe string, otherwise performs HTML escape on the string first. Sets the content type as text/html.

:json - Renders the provided hash or object in JSON. You don't need to call .to_json on the object you want to render.

:body - Renders provided text and sets content type of text/plain.

like image 42
randmin Avatar answered Sep 02 '25 16:09

randmin