Ruby is a mighty fine language and my daily driver at work. Overall I've been pretty happy with its features, ecosystem and libraries for web development, however my previous dabbles into Python have left me longing for a yet unimplemented feature.

The problem

Consider the following pieces of code:

    [][0]
    {}[:ruby]
    [][0]
    {}['python']

A less experienced developer might expect both code snippets throwing exceptions for the out of bounds and missing key access. This assumption holds true for the Python variant, the Ruby variant however happily returns nil for both.

While one may argue that this behaviour can be convenient, I consider it to be a potential source for the dreaded undefined method for nil:NilClass runtime error. The official recommendation is to use the #fetch method to raise exceptions for erroneous access on both arrays and hashes. I could of course comply with that suggestion and laboriously check where the convenience is safe to have and where not, but what if I were to desire a feature reminiscent of JavaScript's use strict mode instead?

The solution

It turns out that it is surprisingly easy to make Ruby behave this way thanks to its excellent metaprogramming facilities. The standard method of achieving the desired result is opening classes and modifying their methods, but it's problematic as it is applied globally and can therefore clash with code relying on the behaviour (or other code monkeypatching the same classes in incompatible ways).

This is why I will instead try my hand at the "Refinements" feature of the runtime which was introduced with Ruby 2.1.0. It allows one to change the behaviour of existing classes and selectively enabling it for any scope. The other missing component of this technical demonstration is alias_method, a facility for adding aliases to class methods. By aliasing the :fetch to the :[] method in a refinement, the desired effect can be obtained easily:

    autoload :ItsATrap, 'its_a_trap'

    module ItsATrap
      refine Array do
        alias_method :[], :fetch
      end

      refine Hash do
        alias_method :[], :fetch
      end
    end

All that is left to do is packaging the module providing the refinement as a Ruby gem. The official guide recommends the following file structure:


    .
    ├── its_a_trap.gemspec
    └── lib
        └── its_a_trap.rb

Here's the missing metadata file:

    Gem::Specification.new do |s|
      s.name        = 'its_a_trap'
      s.version     = '0.0.0'
      s.date        = '2018-02-15'
      s.summary     = 'It\'s a trap!'
      s.description = 'Strict mode for your Ruby files'
      s.author      = 'Vasilij Schneidermann'
      s.email       = 'v.schneidermann@gmail.com'
      s.files       = ['lib/its_a_trap.rb']
      s.homepage    = 'https://github.com/wasamasa/its_a_trap'
      s.license     = 'MIT'
    end

gem build its_a_trap.gemspec; gem install its_a_trap-0.0.0.gem will then install the created gem locally, that way it can be used in your own code like this:

    require 'its_a_trap'
    using ItsATrap

    [][0] # throws IndexError
    {}[:foobar] # throws KeyError

Monkeypatching in Ruby - Done!

And that's about it! Following these steps gives one a library that can be selectively enabled for debugging purposes and disabled for release candidates.