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))

13 thoughts on Bitten By Rails Pluralization/Inflection

  1. Isn’t [^f]ves$ very much more assumptive than most of the others? All I come up with off the top of my head are knives, scarves, wolves and loaves, but there ought to be lots of faves, shaves, and, amusingly, even Daves out there to get bitten by it. :-)

  2. I ran your question by some friends and one of them ( Courtenay @ http://habtm.com/) pointed out that there’s a commented-out block in your environment.rb file for just this kind of occasion:

    Add new inflection rules using the following format

    (all these examples are active by default):

    Inflector.inflections do |inflect|

  3. Johan, Agree. I noticed later that “moves” is listed as irregular, but I guess they could add some of your suggestions. (Although maybe someone only wants there to be only one Dave :-)

    Amy, crikey! Yep, it’s there alright (I guess I should occasionally read comments). I’m now more surprised about the standard advice to override table name, seems redundant in situations where the only reason to do so is because of inflection problems. (Whereas it’s still pointful in a legacy db situation.) (BTW Looking fwd to your book.)

  4. I got bit by this one with my table ‘harddrives’ which rails singularized to ‘harddrife’ :) . Unfortunately I thought the problem was from my own misspelling somewhere along the line. Drove me nuts grepping through everything and not finding a single instance anywhere of ‘harddrife’. I finally deduced it had to be an on-the-fly rails singularization operation and your post was just the thing to lead me to where to make the necessary changes to get me back up and running…thanks!

  5. BTW, its easy to discover how rails will pluralize or singularize a word:

    % script/console

    “datum”.pluralize => “data” “data”.singularize => “datum” “chassis”.pluralize => “chasses” “chassis”.singularize

    => “chassi”

    … car builders beware!

  6. Pingback: Things I Wish I’d Know About Ruby & Rails | Bulletproof Technology

  7. Pingback: Rails and singular / plural model names « Code Motes

Leave a Reply