In the previous article, we dived into ActiveSupport::StringInquirer
class and superpowers it gives to String
objects. After I had published that article I decided to take another look at the ActiveSupport
module and to my surprise, I found something even more interesting, ActiveSupport::ArrayInquirer
class1.
If you read the previous article you might have already guessed what ArrayInquirer
does. Directly speaking, it gives superpowers to Array
objects.
What does ActiveSupport::ArrayInquirer
do?
To understand it better, let’s start with a simple array of names:
names = ['Tom', 'Adam', 'Igor']
If I wanted to check if the array contains my name I would use one of the handy methods from Enumerable
Ruby module2:
names.include?("Igor") => true
According to a comment3 placed directly inside ActiveSupport::ArrayInquirer
class:
Wrapping an array in an `ArrayInquirer` gives a friendlier way to check its string-like contents.
Let’s check if using the class would make finding my name easier:
names_with_superpowers = ActiveSupport::ArrayInquirer.new(names) => NameError: uninitialized constant ActiveSupport::ArrayInquirer from (pry):2:in `<main>'
The class has been officially present in Ruby on Rails source code since version 5.0.0, so to make the above code working I had to switch to an up-to-date version of Rails first:
names = ['Tom', 'Adam', 'Igor'] => ['Tom', 'Adam', 'Igor'] names_with_superpowers = ActiveSupport::ArrayInquirer.new(names) => ['Tom', 'Adam', 'Igor'] names_with_superpowers.class => ActiveSupport::ArrayInquirer names_with_superpowers.Igor? => true names_with_superpowers.Rob? => false
And Voila! Similarly to ActiveSupport::StringInquirer
the class adds some useful methods to Array
objects using metaprogramming.
To understand it fully let’s analyse its body step-by-step:
module ActiveSupport class ArrayInquirer < Array def any?(*candidates) if candidates.none? super else candidates.any? do |candidate| include?(candidate.to_sym) || include?(candidate.to_s) end end end private def respond_to_missing?(name, include_private = false) (name[-1] == "?") || super end def method_missing(name, *args) if name[-1] == "?" any?(name[0..-2]) else super end end end end
ArrayInquirer
inherits fromArray
so all its methods are available toArrayInquirer
objects.Igor?
method is not defined inArray
class nor inArrayInquirer
, somethod_missing
is executed as a fallback.method_missing
checks if a method name ends with?
.- In our case, it ends with
?
, soany?
public method is called withIgor
as an argument (Igor[0..-2]
) - Because
any?
method received an argumentelse
part ofif
block is executed. - The
else
block checks if the array includes the passed argument(s) either as a string or as a symbol. - The
['Tom', 'Adam', 'Igor']
array includes'Igor'
string sonames_with_superpowers.Igor?
returns true finally.
Behind the scenes, the class uses the same include?
method I used initially 🙂
String
class, Array
also defines inquiry
method4 which wraps the current array in theArrayInquirer
class.In addition to magic methods like Igor?
you can also use the public any?
method on ArrayInquirer
object to check if at least one of passed arguments is present in an array:
names_with_superpowers.any?("Igor") => true names_with_superpowers.any?("Rob", "Igor") => true names_with_superpowers.any?("Rob", "Bob") => false # Neither "Rob", nor "Bob" is present in the array
Summary
Thanks to spending some time on analysing Ruby on Rails source code I found two new classes that I potentially can use.
At the same time, I would like to give you the very same advice I gave in the previous article. Please always do some benchmarking before you decide to use either StringInquirer
or ArrayInquirer
. They may be slow.
Interestingly enough, Rails uses the ArrayInquirer
class only in one place5 so far. request.variant
returns ArrayInquirier
object, so instead of writing:
request.variant.include?(:phone)
You can write:
request.variant.phone?
Or:
request.variant.any?(:phone)
I do not recommend using any?
in this very case, though 🙂
Footnotes
- https://github.com/rails/rails/blob/master/activesupport/lib/active_support/array_inquirer.rb
- https://ruby-doc.org/core-2.5.3/Enumerable.html
- https://github.com/rails/rails/blob/master/activesupport/lib/active_support/array_inquirer.rb#L4
- https://api.rubyonrails.org/classes/Array.html#method-i-inquiry
- https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/mime_negotiation.rb#L93