Rails' Hidden Gems: ActiveSupport StringInquirer

If you've ever checked the environment in your Rails app with Rails.env.production? you've used a fascinating little utility class called StringInquirer. In this post, Jonathan Miles dives into the rails codebase to show us exactly how StringInquirer works and how we can bring a little of its magic to our own apps.

Rails is a large framework, and it grows larger every year. This makes it easy for some helpful features to slip by unnoticed. In this series, we'll take a look at some lesser-known functionality built into Rails for specific tasks.

In the first article in this series, we're going to take a look at how calling Rails.env.test? actually works under-the-hood by using the little-known StringInquirer class from ActiveSupport. Going one step further, we will also walk through the StringInquirer source code to see how it works, which (spoiler alert) is a simple example of Ruby's metaprogramming via the special method_missing method.

The Rails.env helper

You've probably already seen code that checks the Rails environment:

if Rails.env.test?
  # return hard-coded value...
  # contact external API...

However, what does test? actually do, and where does this method come from?

If we check Rails.env in a console, it acts like a string:

=> Rails.env

The string here is whatever the RAILS*ENV environment variable is set to; it's not _just* a String, though. When reading the class in the console, we see:

=> Rails.env.class

Rails.env is actually a StringInquirer.


The code for StringInquirer is both brief and well documented. How it works, though, may not be obvious unless you are familiar with Ruby's metaprogramming capabilities. We'll walk through what's happening here.

class StringInquirer < String

First, we see that StringInquirer is a subclass of String. This is why Rails.env acts like a String when we call it. By inheriting from String, we automatically get all the String-like functionality, so we can treat it like a String. Rails.env.upcase works, as does ActiveRecordModel.find_by(string_column: Rails.env).

While Rails.env is a handy, built-in example of StringInquirer, we are also free to create our own:

def type
  result = "old"
  result = "new" if @new


Then, we get the question-mark methods on the returned value:

=> @new = false
=> type.old?
=> type.new?
=> type.testvalue?

=> @new = true
=> type.new?
=> type.old?


method_missing is the real secret sauce here. In Ruby, whenever we call a method on an object, Ruby begins by looking at all the ancestors of the object (i.e., base classes or included modules) for the method. If one is not found, Ruby will call method_missing, passing in the name of the method it is looking for, along with any arguments.

By default, this method just raises an exception. We're all probably familiar with the NoMethodError: undefined method 'test' for nil:NilClass type of error message. We can implement our own method_missing that does not raise an exception, which is exactly what StringInquirer does:

def method_missing(method_name, *arguments)
  if method_name.end_with?("?")
    self == method_name[0..-2]

For any method name that ends in ?, we check the value of self (i.e., the String) against the method name without the ?. Put another way, if we call StringInquirer.new("test").long_test_method_name?, the returned value is "test" == "long_test_method_name".

If the method name does not end with a question mark, we fall back to the original method_missing (the one that will raise an exception).


There's one more method in the file: respond_to_missing?. We might say that this is a companion method to method_missing. Although method_missing gives us the functionality, we also need a way to tell Ruby that we accept these question-mark-ending methods.

def respond_to_missing?(method_name, include_private = false)
  method_name.end_with?("?") || super

This comes into play if we call respond_to? on this object. Without this, if we called StringInquirer.new("test").respond_to?(:test?), the result would be false because we have no explicit method called test?. This is obviously misleading because I would expect it to return true if I call Rails.env.respond_to?(:test?).

respond_to_missing? is what allows us to say "yes, I can handle that method" to Ruby. If the method name does not end in a question mark, we fall back to the superclass method.

Practical use cases

Now that we know how StringInquirer works, let's take a look at instances where it could be useful:

1. Environment variables

Environment variables meet two criteria that make them great choices for StringInquirer. First, they often have a limited, known set of possible values (like an enum), and second, we often have conditional logic that depends on their value.

Let's say our app is hooked up to a payment API, and we have the credentials stored in environment variables. In our production system, this is obviously the real API, but in our staging or development version, we want to use their sandbox API:

# ENV["PAYMENT_API_MODE"] = sandbox/production

class PaymentGateway
  def api_mode
    # We use ENV.fetch because we want to raise if the value is missing
    @api_mode ||= ENV.fetch("PAYMENT_API_MODE").inquiry

  def api_url
    # Pro-tip: We *only* use production if MODE == 'production', and default
    # to sandbox if the value is anything else, this prevents us using production
    # values if the value is mistyped or incorrect
    if api_mode.production?

Note that in the above, we are using ActiveSupport's String#inquiry method, which handily converts a String into a StringInquirer for us.

2. API responses

Continuing our payment API example from above, the API will send us a response that includes some success/failure state. Again, this meets the two criteria that make it a candidate for StringInquirer: a limited set of possible values and conditional logic that will test these values.

class PaymentGateway
  def create_charge
    response = JSON.parse(api_call(...))

    result = response["result"].inquiry

    if result.success?

    # result still acts like a string
    Rails.logger.info("Payment result was: #{result}")


StringInquirer is an interesting tool to have in your back pocket, but personally, I wouldn't reach for it too often. It has some uses, but most of the time, an explicit method on an object gives you the same result. An explicitly named method also has a couple of benefits; if the value ever needs to change, you only have to update a single place, and it makes the codebase easier to search if a developer is trying to locate the method.

Although it focuses on StringInquirer, this article is intended to be more of a gentle introduction to some of Ruby's metaprogramming capabilities using method_missing. I would say that you probably don't want to use method_missing in your application. However, it is often used in frameworks, such as Rails or domain-specific-languages provided by gems, so it can be very helpful to know "how the sausage is made" when you encounter issues.

What to do next:
  1. Try Honeybadger for FREE
    Honeybadger helps you find and fix errors before your users can even report them. Get set up in minutes and check monitoring off your to-do list.
    Start free trial
    Easy 5-minute setup — No credit card required
  2. Get the Honeybadger newsletter
    Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo

    Jonathan Miles

    Jonathan began his career as a C/C++ developer but has since transitioned to web development with Ruby on Rails. 3D printing is his main hobby but lately all his spare time is taken up with being a first-time dad to a rambunctious toddler.

    More articles by Jonathan Miles
    Stop wasting time manually checking logs for errors!

    Try the only application health monitoring tool that allows you to track application errors, uptime, and cron jobs in one simple platform.

    • Know when critical errors occur, and which customers are affected.
    • Respond instantly when your systems go down.
    • Improve the health of your systems over time.
    • Fix problems before your customers can report them!

    As developers ourselves, we hated wasting time tracking down errors—so we built the system we always wanted.

    Honeybadger tracks everything you need and nothing you don't, creating one simple solution to keep your application running and error free so you can do what you do best—release new code. Try it free and see for yourself.

    Start free trial
    Simple 5-minute setup — No credit card required

    Learn more

    "We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release."
    — Michael Smith, Cofounder & CTO of YvesBlue

    Honeybadger is trusted by top companies like:

    “Everyone is in love with Honeybadger ... the UI is spot on.”
    Molly Struve, Sr. Site Reliability Engineer, Netflix
    Start free trial
    Are you using Sentry, Rollbar, Bugsnag, or Airbrake for your monitoring? Honeybadger includes error tracking with a whole suite of amazing monitoring tools — all for probably less than you're paying now. Discover why so many companies are switching to Honeybadger here.
    Start free trial