# Proposed refactor on OrderedOptions#method\_missing

**URL:** <https://discuss.rubyonrails.org/t/proposed-refactor-on-orderedoptions-method-missing/79652>\
**Category:** rubyonrails-core\
**Tags:** patch, activesupport\
**Created:** [December 31, 2021, 2:39pm UTC](https://discuss.rubyonrails.org/t/proposed-refactor-on-orderedoptions-method-missing/79652 "2021-12-31T14:39:52Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![bodacious](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/bodacious/32/4529_2.png) [@bodacious](https://discuss.rubyonrails.org/u/bodacious)\
**Post date:** [December 31, 2021, 2:39pm UTC](https://discuss.rubyonrails.org/t/proposed-refactor-on-orderedoptions-method-missing/79652/1 "2021-12-31T14:39:52Z")

</div>

Hi all 👋

I’ve been reading through the Rails code over the holidays and noticed an opportunity for a possible reactor that could improve performance in some cases.

I’ve pushed my changes out to Github (on my own fork) but—as directed in the Contribution Guidelines—I’m opening the topic for discussion here first.

Please feel free to review the changes I’ve made in this commit: [https://github.com/Bodacious/rails/commit/a490727fd0363d8e2153f8b2d4bf54cc2a9c02f5](https://github.com/Bodacious/rails/commit/a490727fd0363d8e2153f8b2d4bf54cc2a9c02f5)

My thinking was that _getting_ values from an OrderedOptions instance is probably more common than either _setting_ a value or _getting_ with a bang. I think this is particularly true while code is being executed at runtime, since things like configuration values tend not to change once the app has booted.

I tried a few permutations to optimise for this case, and benchmarked them using Benchmark/IPS.

The commit I’ve linked above was the best performer I could come up with today.

Here are some benchmark results from my own machine. In all cases, only the `method_missing` method was changed. I’ve omitted the rest of the methods from the examples for brevity.

```ruby
# Current implementation
class VariantA < Hash
  # ...
  def method_missing(name, *args)
    name_string = +name.to_s
    if name_string.chomp!("=")
      self[name_string] = args.first
    else
      bangs = name_string.chomp!("!")

      if bangs
        self[name_string].presence || raise(KeyError.new(":#{name_string} is blank"))
      else
        self[name_string]
      end
    end
  end
  # ...
end

# Fastest
class VariantB < Hash
  # ...
  def method_missing(name, *args)
    return self[name] unless name.end_with?("=") || name.end_with?("!")

    name_string = name.to_s
    if name_string.slice!(-1) == "="
      return self[name_string] = args.first
    end

    self[name_string].presence || raise(KeyError.new(":#{name_string} is blank"))
  end
  # ...
end

class VariantC < Hash
  # ...
  def method_missing(name, *args)
    return self[name] unless name.end_with?("=") || name.end_with?("!")

    name_string = name.to_s
    name_string.slice!(-1)
    if name.end_with?("=")
      return self[name_string] = args.first
    end

    self[name_string].presence || raise(KeyError.new(":#{name_string} is blank"))
  end
  # ...
end

test_lambda = lambda do |test_class|
  h = test_class.new
  # Extracted from example in docs
  h.boy = 'John'
  h.girl = 'Mary'
  h.boy
  h.girl
  h.dog
end

Benchmark.ips do |test|
  test.iterations = 1

  test.report("Variant A") do
    test_lambda.call(VariantA)
  end
  test.report("Variant B") do
    test_lambda.call(VariantB)
  end
  test.report("Variant C") do
    test_lambda.call(VariantC)
  end
  test.compare!
end

```

## Results

The benchmark results on my machine look like this:

```ruby
Warming up --------------------------------------
           Variant A 16.082k i/100ms
           Variant B 20.174k i/100ms
           Variant C 19.369k i/100ms
Calculating -------------------------------------
           Variant A 164.454k (± 2.9%) i/s - 836.264k in 5.089449s
           Variant B 201.225k (± 1.7%) i/s - 1.009M in 5.014209s
           Variant C 192.310k (± 2.0%) i/s - 968.450k in 5.037718s

Comparison:
           Variant B: 201225.1 i/s
           Variant C: 192310.2 i/s - 1.05x (± 0.00) slower
           Variant A: 164453.9 i/s - 1.22x (± 0.00) slower

```

I would welcome feedback on this, and whether or not the community would welcome a PR with these changes.

Thanks, and happy New Year!
