Custom Inquirer Similar To ActiveSupport's StringInquirer
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
endIn 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
endIn 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
endIn 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: