optional local variables in rails partial templates: how do I get out of the (defined? foo) mess?

Ruby on-RailsPartials

Ruby on-Rails Problem Overview


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?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

I do this:

<% some_local = default_value if local_assigns[:some_local].nil? %>

Solution 2 - Ruby on-Rails

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.

Solution 3 - Ruby on-Rails

How about

<% foo ||= default_value %>

This says "use foo if it is not nil or true. Otherwise assign default_value to foo"

Solution 4 - Ruby on-Rails

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.

Solution 5 - Ruby on-Rails

In my case, I use:

<% variable ||= "" %>

in my partial.
I don't have idea if that is good but for my is OK

Solution 6 - Ruby on-Rails

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.

Solution 7 - Ruby on-Rails

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') %>

Solution 8 - Ruby on-Rails

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] %>

Solution 9 - Ruby on-Rails

Ruby 2.5

Erb

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 %>
...

Solution 10 - Ruby on-Rails

More intuitive and compact:

<% some_local = default_value unless local_assigns[:some_local] %>

Solution 11 - Ruby on-Rails

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.

Solution 12 - Ruby on-Rails

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbrahnView Question on Stackoverflow
Solution 1 - Ruby on-RailsjonniiView Answer on Stackoverflow
Solution 2 - Ruby on-RailsPablo CanteroView Answer on Stackoverflow
Solution 3 - Ruby on-RailshgmnzView Answer on Stackoverflow
Solution 4 - Ruby on-RailsgamovView Answer on Stackoverflow
Solution 5 - Ruby on-RailsMoises PortilloView Answer on Stackoverflow
Solution 6 - Ruby on-RailsmicrospinoView Answer on Stackoverflow
Solution 7 - Ruby on-RailssethcallView Answer on Stackoverflow
Solution 8 - Ruby on-RailsDaniel OCallaghanView Answer on Stackoverflow
Solution 9 - Ruby on-RailsdimpiaxView Answer on Stackoverflow
Solution 10 - Ruby on-RailsmuirbotView Answer on Stackoverflow
Solution 11 - Ruby on-RailsHaris KrajinaView Answer on Stackoverflow
Solution 12 - Ruby on-RailsJaime ChamView Answer on Stackoverflow