This is in continuation with the ArrayInquirer post. Lets see how to implement a Custom Inquirer for a Rails model similar to ActiveSupport StringInquirer and ArrayInquirer.

Lets create a Rails model called State which stores details about states in India.

class State < ActiveRecord::Base
  attr_accessible :id, :name
end

In India we have 29 states and if we want to find out which state a model instance belongs to

State.create('karnataka')
State.create('tamilnadu')
State.create('kerala')
State.create('andhra pradesh')

state = State.first
state.karnataka?

we have to create as many methods as states like below

class State < ActiveRecord::Base
  attr_accessible :id, :name

  def karnataka?
    name == 'karnataka'
  end

  def tamilnadu?
    name == 'tamilnadu'
  end

  def kerala?
    name == 'kerala'
  end

  def andhra_pradesh?
    name == 'andhra pradesh'
  end
end

In this way we have to create 29 methods for each state and the class is going to be big and redundant methods has to be created. How do we avoid this, is there any other way? Yes there is. We can override ruby classes method_missing method. Lets see how

module States
  STATES = ['karnataka', 'tamilnadu', 'kerala', 'andhra pradesh']
end

class State < ActiveRecord::Base
  attr_accessible :id, :name

  def respond_to_missing?(method_name, include_private = false)
    state_name = method_name[0..-2].gsub('_', ' ')
    (method_name[-1] == '?' && STATES.include?(state_name)) || super
  end

  def method_missing(method_name, *args, &block)
    state_name = method_name[0..-2].gsub('_', ' ')
    if method_name[-1] != '?' || STATES.exclude?(state_name)
      super
    else
      true
    end
  end
end

In the above model we have overriden 2 methods method_missing and respond_to_missing?. In the method_missing implementation we extract the state_name from the method_name method parameter and replacing all the _ with spaces and then we check the STATES array contains the state_name, if its so it will return true, otherwise it will delegate the request to super. In this way we have reduced 29 methods to just 2 and the model looks leaner and cleaner.

But why does the respond_to_missing? has to be overriden? Sometimes before you call a method you want to first ask your object if it will be able to respond to this method. To do this you can use the method respond_to? passing the method name to the method.

In our case

state.respond_to?(:karnataka?) # => true
state.method(:karnataka?) #=> <State>

For more details about method_missing, respond_to? and respond_to_missing?, follow the below references:

  1. Understanding method missing

  2. Using method_missing and respond_to to create dynamic methods