You can check if an array contains a particular element using the include?(obj)
method.
browsers = ['chrome', 'safari', 'firefox']
browsers.include?('chrome') # true
browsers.include?('explorer') # false
This code is already very simple and readable. But, can we go one step further and 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. This method is defined on the BasicObject
class, which all Ruby classes inherit from, and the Ruby interpreter calls it when it can't find the method on an object.
class ArrayChecker < Array
def method_missing(name, *args)
if name.end_with?("?")
any?(name[0..-2])
else
super
end
end
end
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 ?
. This method returns true
if the array contains the value passed.
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.
browsers = [:chrome, :safari, :firefox]
browsers.chrome? # true
browsers.explorer? # false
Use ActiveSupport::ArrayInquirer
Luckily, if you're using Ruby on Rails, you don't have to write the above code yourself. The Active Support library provides an ArrayInquirer
class which handles it for you. It also supports both string and symbol values.
Wrapping an array in 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.
The inquiry Helper
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.horse? # => false
pets.any?(:cat, :horse) # => true
pets.any?(:horse, :cow) # => 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 as a readable way to check the contents of an array.
Hope you found this post helpful.