Be careful with multiple default params in Ruby when last is an options Hash!

Any bug that takes a LONG time to solve always has some good story to it!

The bug in question had actually two bugs:

  1. You have to be very careful with regards to multiple optional parameters for method calls.
  2. jquery-ujs assumed that all radio buttons would be tagged as required if one of them is. That doesn’t have to be the case.

The rails view helpers are full of code that has multiple default value params.

radio_button_tag has this API

radio_button_tag(name, value, checked = false, options = {}) ⇒ Object

It’s very easy to do something like:

radio_button_tag("my_radio_button", 1, required: false)

And you’d think required: false is going to the options. Instead, it gets set as truthy for the radio button being checked!

The point is that {required: false}, a Hash, is used for the checked optional param, and there is nothing that makes it to the options for the call to radio_button_tag.

When you have 2 optional params, you have to fill a value in for the second param, or else what you think might be going to the 3rd param is really going to the second param:

irb                                                                                                                                                                                                                                                                         [21:14:28]
2.1.5 :001 > def test(a, b=false, c={})
2.1.5 :002?>   puts "a is #{a}, b is #{b}, c is #{c}"
2.1.5 :003?>   end
 => :test
2.1.5 :004 > test(1, required: true)
a is 1, b is {:required=>true}, c is {}
 => nil

With named params, the bug could not have happened. However, in the case of view helpers, we need the params hash to allow any params. This bit of console code shows what’s happening:

2.1.5 :005 > def test_named(a, b=false, required: false)
2.1.5 :006?>   puts "a is #{a}, b is #{b}, required is #{required}"
2.1.5 :007?>   end
 => :test_named
2.1.5 :008 > test_named(1, required: true)
a is 1, b is false, required is true

Interestingly, this bug, revealed a bug in the jquery-ujs code:

Finally, in the past month, I simplified the API of React on Rails for v3 to skip having multiple optional params:

react_component(name, options ={})

rather than:

react_component(name, props={}, options ={})

The bottom line is that having optional params before a final options hash is error prone!

1 Like