Reputation: 4932
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.
Upvotes: 4
Views: 5331
Reputation: 948
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.
Upvotes: 1
Reputation: 4116
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.
Upvotes: 6
Reputation: 101
You’re correct that the controller’s instance variables are accessible in the associated view. Though you don’t seem to be following the Rails framework. I’m unsure if I understood your doubt fully and will attempt my best to explain it in one go.
# pages_controller.rb
def home
@message = Message.count
end
# home.html.erb
<%= render “shared/navbar” %>
<% if @message > 10 %>
# Do something
<% else %>
# Do something else
<% end %>
Upvotes: 0