The Life‑Changing Magic of Ruby and Rails

Write Readable Internal APIs using Array Inquirer

The ArrayInquirer class provided by Rails' Active Support framework provides a readable way to check the contents of an array.

Array Inquirer
Array Inquirer

Sometimes, you need to check if an array contains a particular element. Ruby provides the include?(obj) method which does this.

browsers = ['chrome', 'safari', 'firefox']

browsers.include?('chrome') # true

browsers.include?('explorer') # false

This code is already very simple and readable. Still, can we go any further? Can we just call a method named chrome? on the array itself?

browsers = ['chrome', 'safari', 'firefox']

browsers.chrome? # should return true
browsers.explorer? # should return false

With a little bit of metaprogramming in Ruby, we absolutely can.

Extending Array

Let's create a new class that extends Ruby's Array class and override the method_missing method on it.

class ArrayChecker < Array
  def method_missing(name, *args)
    if name.end_with?("?")
      any?(name[0..-2])
    else
      super
    end
  end
end

The method_missing method is defined on the BasicObject class and the Ruby interpreter calls it when it can't find the method on an object. By default, the interpreter raises an error when this method is called. However, you can override this method to provide more dynamic behavior and handle undefined methods.

class Person
  def method_missing(symbol, *args)
    puts "You called '#{symbol}' method which doesn't exist."
  end
end

ak = Person.new
ak.name 
# You called 'name' method which doesn't exist.

The ArrayChecker class overrides the method_missing method and checks if the method that was called ends with ?. If it does, then it calls the any? method on the array, passing the method name without the ?.

For example, if you call browsers.chrome? where browsers is an instance of the ArrayChecker class, the Ruby interpreter will first check if there's a method named chrome? on ArrayChecker.

Since ArrayChecker doesn't include chrome? method, Ruby will call the method_missing, which in turn calls browsers.any?('chrome') , checking if the array includes a value named 'chrome'. If yes, it returns true, returning false otherwise.

This allows us to write the following code, which I argue is more readable than the basic constructs provided by the Array class.

browsers = [:chrome, :safari, :firefox]

browsers.chrome? # true

ActiveSupport::ArrayInquirer

Luckily, if you're using Ruby on Rails, you don't have to write the above code yourself, since the Active Support library provides an ArrayInquirer class which handles it for you.

Wrapping an array in an ArrayInquirer provides a friendlier way to check its contents:

browsers = ActiveSupport::ArrayInquirer.new([:chrome, :firefox])

browsers.chrome?    # => true
browsers.firefox?   # => true
browsers.explorer?  # => false

In addition, this class also overrides the any? method, so it checks for both the stringified and symbolized form of any element in the array.

browsers = ActiveSupport::ArrayInquirer.new([:chrome, :firefox])

browsers.any? # true
browsers.any?(:chrome, :firefox) # true
browsers.any?(:explorer, :firefox) # true
browsers.any?(:explorer, :desktop) # false

browsers.any?('chrome') # true
browsers.any?('explorer') # false

You can check the implementation of this class on Github.

In addition, Active Support opens the Array class and adds the inquiry method to it. That way, you don't have to write ActiveSupport::ArrayInquirer.new(array) every time. Instead, you can simply call array.inquiry method to get the same effect.

pets = [:cat, :dog].inquiry

pets.cat?     # => true
pets.ferret?  # => false

pets.any?(:cat, :ferret)  # => true
pets.any?(:ferret, :alligator)  # => false

Here's the implementation of the Array#inquiry method. It instantiates the ArrayInquirer class, passing self which is the array itself.

class Array
  def inquiry
    ActiveSupport::ArrayInquirer.new(self)
  end
end

So this concludes our exploration of the ArrayInquirer class. You can use this class for a readable way to check the contents of an array.

I hope you learned something new. I sure did. Let me know if you liked this post or have any feedback. I look forward to hearing from you.

Subscribe to Akshay's Blog

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe