Reputation: 12366
I've been a bad kid and used the following syntax in my partial templates to set default values for local variables if a value wasn't explicitly defined in the :locals hash when rendering the partial --
<% foo = default_value unless (defined? foo) %>
This seemed to work fine until recently, when (for no reason I could discern) non-passed variables started behaving as if they had been defined to nil (rather than undefined).
As has been pointed by various helpful people on SO, http://api.rubyonrails.org/classes/ActionView/Base.html says not to use
defined? foo
and instead to use
local_assigns.has_key? :foo
I'm trying to amend my ways, but that means changing a lot of templates.
Can/should I just charge ahead and make this change in all the templates? Is there any trickiness I need to watch for? How diligently do I need to test each one?
Upvotes: 254
Views: 94840
Reputation: 1510
introduced a nice feature called strict_locals
, it allows us to explicitly tell which locales are accepted in a partial.
Put this magic comment on top of your file:
<%# locals: (title: "Default title", subtitle: nil, other_required_param:) %>
This way, Rails will raise an ArgumentError, missing local: :other_required_arg
when not given the parameter.
See the docs.
If you need to use the generated helpers in a partial that is rendered as a collection of items, remember to put it on top of the file.
For instance:
_product.html.erb
<%# locals: (product_iteration:, product_counter:) %>
Collection size: <%= product_iteration.size %>
Item index: <%= product_counter %>
Upvotes: 7
Reputation: 12667
It's possible, but you must to declare your default values in the scope.
VARIABLE the word for replacement.
# index.html.erb
...
<%= render 'some_content', VARIABLE: false %>
...
# _some_content.html.erb
...
<% VARIABLE = true if local_assigns[:VARIABLE].nil? %>
<% if VARIABLE %>
<h1>Do you see me?</h1>
<% end %>
...
Upvotes: 1
Reputation: 838
In my case, I use:
<% variable ||= "" %>
in my partial.
I don't have idea if that is good but for my is OK
Upvotes: 11
Reputation: 28312
I do this:
<% some_local = default_value if local_assigns[:some_local].nil? %>
Upvotes: 364
Reputation: 6357
Since local_assigns
is a hash, you could also use fetch with the optional default_value
.
local_assigns.fetch :foo, default_value
This will return default_value
if foo
wasn't set.
WARNING:
Be careful with local_assigns.fetch :foo, default_value
when default_value
is a method, as it will be called anyway in order to pass its result to fetch
.
If your default_value
is a method, you can wrap it in a block: local_assigns.fetch(:foo) { default_value }
to prevent its call when it's not needed.
Upvotes: 186
Reputation: 7781
I know it's an old thread but here's my small contribution: i would use local_assigns[:foo].presence
in a conditional inside the partial.
Then i set foo
only when needed in the render call:
<%= render 'path/to/my_partial', always_present_local_var: "bar", foo: "baz" %>
Have a look at te official Rails guide here. Valid from RoR 3.1.0.
Upvotes: 5
Reputation: 2897
This is a derivative of Pablo's answer. This allows me to set a default ('full'), and in the end, 'mode' is set in both local_assigns and an actual local variable.
haml/slim:
- mode ||= local_assigns[:mode] = local_assigns.fetch(:mode, 'full')
erb:
<% mode ||= local_assigns[:mode] = local_assigns.fetch(:mode, 'full') %>
Upvotes: 3
Reputation: 3859
I think this should be repeated here (from http://api.rubyonrails.org/classes/ActionView/Base.html):
If you need to find out whether a certain local variable has been assigned a value in a particular render call, you need to use the following pattern:
<% if local_assigns.has_key? :headline %>
Headline: <%= headline %>
<% end %>
Testing using defined? headline will not work. This is an implementation restriction.
Upvotes: 13
Reputation: 15266
If you do not want to pass local variable to partial each time you call it you do this:
<% local_param = defined?(local_param) ? local_param : nil %>
This way you avoid undefined variable
error. This will allow you to call your partial with/without local variables.
Upvotes: 0
Reputation: 116
I think a better option that allows for multiple default variables:
<% options = local_assigns.reverse_merge(:include_css => true, :include_js => true) %>
<%= include_stylesheets :national_header_css if options[:include_css] %>
<%= include_javascripts :national_header_js if options[:include_js] %>
Upvotes: 1
Reputation: 2081
More intuitive and compact:
<% some_local = default_value unless local_assigns[:some_local] %>
Upvotes: 0
Reputation: 1504
A helper can be created to look like this:
somearg = opt(:somearg) { :defaultvalue }
Implemented like:
module OptHelper
def opt(name, &block)
was_assigned, value = eval(
"[ local_assigns.has_key?(:#{name}), local_assigns[:#{name}] ]",
block.binding)
if was_assigned
value
else
yield
end
end
end
See my blog for details on how and why.
Note that this solution does allow you to pass nil or false as the value without it being overridden.
Upvotes: -6
Reputation: 13306
How about
<% foo ||= default_value %>
This says "use foo
if it is not nil or true. Otherwise assign default_value
to foo"
Upvotes: 90