Bitten By Rails Pluralization/Inflection

If you’ve heard DHH or other Rails enthusiasts introducing Rails, they’ll often point to the neat inflection module, which maps back and forth between plurals and singular forms, among other things. Even if not the most killer thing about Rails, it’s an excellent introductory topic as it quickly conveys the humane nature of the Rails API as well as the principle of Convention Over Configuration.

It’s also the kind of thing where you think “These exception cases probably won’t happen to me…how likely am I to be coding Octopi, Sheep, or Oxen” (unless you work in the underwater farm sector). Actually, it turns out exceptions do happen more than I at first expected and are generally handled in a clean way. But how about exceptions that can’t be handled?

Where the inflector isn’t smart enough, I’ve seen the advice (e.g. in PragRails) to override ActiveRecord.set_table_name. e.g.

  1. class Sheep < ActiveRecord::Base
  2.    set_table_name "sheep"
  3. end
  4. [/ruby]
  5.  
  6. But today I had a problem. My class, "Fave", was leading to an error message about class "Fafe" not found, while loading the fixture. After some bemused grepping, I realised that it was due to this pluralisation issue. Seems that the fixture loader performs some singularization - here's an extract from fixtures.rb:
  7.  
  8. [ruby]
  9.     @class_name = class_name ||
  10.                   (ActiveRecord::Base.pluralize_table_names @table_name.singularize.camelize : @table_name.camelize)
  11. [/ruby]
  12.  
  13. **Overriding set_table_name doesn't solve this problem**. Which is surprising given the usual advice, so maybe I missed something!!! The solution that worked for me was to <a href="http://wiki.rubyonrails.com/rails/pages/Turn">configure the Inflection module</a>. I now have this in my config/environment.rb:
  14.  
  15. [ruby]
  16. Inflector.inflections do |inflection| inflection.irregular "fave", "faves"
  17. end

Now that Rails’ notion of grammar has been redefined, the set_table_name can be removed as it’s redundant.

Side note: Since people are often intrigued by the pluralization algorithm but probably not enough to hunt down the code, here it is – taken from active_support/inflections.rb (which confirms why “Faves” became “Fafe”):

  1. inflect.plural(/$/, 's')
  2.   inflect.plural(/s$/i, 's')
  3.   inflect.plural(/(ax|test)is$/i, '1es')
  4.   inflect.plural(/(octop|vir)us$/i, '1i')
  5.   inflect.plural(/(alias|status)$/i, '1es')
  6.   inflect.plural(/(bu)s$/i, '1ses')
  7.   inflect.plural(/(buffal|tomat)o$/i, '1oes')
  8.   inflect.plural(/([ti])um$/i, '1a')
  9.   inflect.plural(/sis$/i, 'ses')
  10.   inflect.plural(/(?:([^f])fe|([lr])f)$/i, '12ves')
  11.   inflect.plural(/(hive)$/i, '1s')
  12.   inflect.plural(/([^aeiouy]|qu)y$/i, '1ies')
  13.   inflect.plural(/([^aeiouy]|qu)ies$/i, '1y')
  14.   inflect.plural(/(x|ch|ss|sh)$/i, '1es')
  15.   inflect.plural(/(matr|vert|ind)ix|ex$/i, '1ices')
  16.   inflect.plural(/([m|l])ouse$/i, '1ice')
  17.   inflect.plural(/^(ox)$/i, '1en')
  18.   inflect.plural(/(quiz)$/i, '1zes')
  19.  
  20.   inflect.singular(/s$/i, '')
  21.   inflect.singular(/(n)ews$/i, '1ews')
  22.   inflect.singular(/([ti])a$/i, '1um')
  23.   inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '12sis')
  24.   inflect.singular(/(^analy)ses$/i, '1sis')
  25.   inflect.singular(/([^f])ves$/i, '1fe')
  26.   inflect.singular(/(hive)s$/i, '1')
  27.   inflect.singular(/(tive)s$/i, '1')
  28.   inflect.singular(/([lr])ves$/i, '1f')
  29.   inflect.singular(/([^aeiouy]|qu)ies$/i, '1y')
  30.   inflect.singular(/(s)eries$/i, '1eries')
  31.   inflect.singular(/(m)ovies$/i, '1ovie')
  32.   inflect.singular(/(x|ch|ss|sh)es$/i, '1')
  33.   inflect.singular(/([m|l])ice$/i, '1ouse')
  34.   inflect.singular(/(bus)es$/i, '1')
  35.   inflect.singular(/(o)es$/i, '1')
  36.   inflect.singular(/(shoe)s$/i, '1')
  37.   inflect.singular(/(cris|ax|test)es$/i, '1is')
  38.   inflect.singular(/([octop|vir])i$/i, '1us')
  39.   inflect.singular(/(alias|status)es$/i, '1')
  40.   inflect.singular(/^(ox)en/i, '1')
  41.   inflect.singular(/(vert|ind)ices$/i, '1ex')
  42.   inflect.singular(/(matr)ices$/i, '1ix')
  43.   inflect.singular(/(quiz)zes$/i, '1')
  44.  
  45.   inflect.irregular('person', 'people')
  46.   inflect.irregular('man', 'men')
  47.   inflect.irregular('child', 'children')
  48.   inflect.irregular('sex', 'sexes')
  49.   inflect.irregular('move', 'moves')
  50.  
  51.   inflect.uncountable(%w(equipment information rice money species series fish sheep))