10 May 2009

What is Inflector and how does it work

I am using Rails 2.3.2 and all the code mentioned in this article are tested with Rails 2.3.2 .

What is Inflector

Inflector is the backbone of the Rails code that converts a string from singular to plural and from plural to singular. This functionality is most commonly needed to find out the table name of a given model. It is Inflector that says that the table name for the model ‘Person’ will be ‘People’.

How do I use this feature

If you have a special case where you want to control the mapping from singular to plural then Rails provides you a hook. You will find a file called config/initializers/inflections.rb . This file looks something like this

# Add new inflection rules using the following format 
# (all these examples are active by default):
# ActiveSupport::Inflector.inflections do |inflect|
#   inflect.plural /^(ox)$/i, '\1en'
#   inflect.singular /^(ox)en/i, '\1'
#   inflect.irregular 'person', 'people'
#   inflect.uncountable %w( fish sheep )
# end

If I want the plural of ‘car’ to be ‘carz’ then this is the place to make that change.

ActiveSupport::Inflector.inflections do |inflect|
    inflect.irregular 'car', 'carz'
end

You can test it in script/console .

>> 'car'.pluralize
=> "carz"

How is Inflector implemented

In short inflector.rb has following code

module ActiveSupport
  module Inflector
    extend self

    class Inflections
      include Singleton

      def plural(rule, replacement)
      end

      def singular(rule, replacement)
      end
    end

    def pluralize(word)
    end

    def singularize(word)
    end

  end
end

A few things to notice here. First is the usage of extend self. Secondly module Singleton is being included.

What does ‘extend self’ do ?

A module can have instance methods and class methods.

module Foo
 def hi
  puts 'hi'
 end

 def self.hello
  puts 'hello'
 end
end

You already know that a module can not be instantiated.

f = Foo.new
unndefined method ‘new’ for Foo:Module

However you can invoke a class method directly on a module. Invoking an instance method will raise error.

module Foo
 def hi
  puts 'hi'
 end

 def self.hello
  puts 'hello'
 end
end


Foo.hello #=> 'hello'

Foo.hi #=> undefined method ‘hi’ for Foo:Module

You can see that invocation of instance method hi resulted in a error. What is the solution to this problem. Solution is extend self. Inside the module self is the module Foo itself and if that module is extended then the instance methods become directly available on Foo object.

module Foo
 def hi
  puts 'hi'
 end

 def self.hello
  puts 'hello'
 end
 extend self
end


Foo.hello #=> 'hello'

Foo.hi #=> 'hi'

In the above case extend self was added and both the methods ‘hi’ and ‘hello’ worked fine. Back to the main topic. The inflector code is this

module ActiveSupport
  module Inflector
    extend self

    class Inflections
      include Singleton

      def plural(rule, replacement)
      end

      def singular(rule, replacement)
      end
    end

    def pluralize(word)
    end

    def singularize(word)
    end

  end
end

In the script/console try this

>> ActiveSupport::Inflector.pluralize('cat')
=> "cats"

Notice that ‘Inflector’ is a module and method pluralize is an instance method. Invocation of instance method on a module results in error. However in this case extend self was done so that instance methods are directly available to the module.

What does ‘include Singleton’ do ?

The rule to find plural of a string or singular value of a string is defined in active_support/inflections.rb. Click here to look at the rules.

You will notice that the default rule to add ‘s’ to add any string is defined at the very top. The way it works is that the rules defined at the bottom override the rules defined at the top. So if all rules matching fails then the default rule to add ‘s’ to the string will kick in.

In this case inflections.rb is like a configuration file. The class that will read this file is a good candidate for singleton. You don’t want two instances of a class to read a configuration file. In this case methods plural, singular, irregular and uncountable are being called on Inflector.inflections.

def inflections
  if block_given?
    yield Inflections.instance
  else
    Inflections.instance
  end
end

As you can see method inflections returns Inflections.instance. Class Inflections has include Singleton and hence every single time Inflections.instance is called same instance of the class is returned.

Changing the singular/plural rule

Changing the rule of how singular or plural value is calculated is pretty easy.

ActiveSupport::Inflector.inflections do |inflect|
    inflect.irregular 'car', 'carz'
end

Add another rule for ‘car’.

ActiveSupport::Inflector.inflections do |inflect|
    inflect.irregular 'car', 'carz'
    inflect.irregular 'car', 'carzz'
end

In script/console

>> 'car'.pluralize
=> "carzz"

You can see that the rules that are defined at the top are overridden by the rules defined below them. Just keep that in mind if you are changing the rule.