Ryan Bigg

⟵ Posts

Ruby 3, Hashes and Keyword Arguments

09 Nov 2023

We debugged a fun one today.

There’s a method provided by Rails called tag, and it provides a way to write HTML tags.

We were using it like this:

def react_component(component_name, props, options = {})
  tag.div({
    data: {
      react_component: component_name,
      props: props.to_json,
    }
  }.merge(options)) { "" }
end

Did you spot the bug? We didn’t for a while. The symptom was that we were seeing completely blank <div></div> tag, when we were expecting them to have at least the data attributes populated.

The issue here has to do with how Ruby 3 has changed how it processes keyword arguments. In Ruby 2.7, the argument passed to react_component was interpreted as keyword arguments. In Ruby 3, it’s interpreted as a regular argument, where the value of that argument is a Hash object.

This means that when the TagHelper#method_missing method is called in Action View, the parameters of this are:

  • called: “div”
  • *args: [{data: { react_component: component_name, props: props.to_json }}]`
  • **options: {}

The fix for this is to tell Ruby that we mean to use keyword arguments here, rather than a Hash argument:

def react_component(component_name, props, options = {})
  react_options = {
    data: {
      react_component: component_name,
      props: props.to_json,
    }
  }.merge(options)

  tag.div(**react_options) { "" }
end