Skip to content

Uncountable Nouns in Rails 3 Resource Routing

Yesterday I was puzzling over how to deal with the situation in Rails 3 where a resource name is an uncountable noun. It turns out Rails actually deals with this quite elegantly, but it seems not to be widely known - certainly I had an hour of headaches until I stumbled upon the answer. Hopefully this post will stop anyone else having that same headache.

Normally Rails 3 resource routing is very straightforward. The controller is plural and works like this:

# app/config/routes.rb
resources :apples

# app/controllers/apples_controller.rb
class ApplesController << ApplicationController
  # ...
end

# app/models/apple.rb
class Apple
  # ...
end

This is totally intuitive and gives (by default) these routes:

    apples GET    /apples(.:format)          {:action=>"index", :controller=>"apples"}
           POST   /apples(.:format)          {:action=>"create", :controller=>"tanks"}
 new_apple GET    /apples/new(.:format)      {:action=>"new", :controller=>"apples"}
edit_apple GET    /apples/:id/edit(.:format) {:action=>"edit", :controller=>"apples"}
     apple GET    /apples/:id(.:format)      {:action=>"show", :controller=>"apples"}
           PUT    /apples/:id(.:format)      {:action=>"update", :controller=>"apples"}
           DELETE /tanks/:id(.:format)       {:action=>"destroy", :controller=>"tanks"}

As you can see, this is perfect. However you can see the problem with an uncountable noun - the route for the index action (in this case, named apples) will be the same as the one for the show, update and destroy methods (in this case, named apple). As it turns out, the later ones supersede the earlier ones and essentially it'll make it impossible to link to the index page. However it turns out all is not lost, as the clever Rails authors already thought this through.

Opening up config/initializers/inflections.rb yields 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

This is where we can tell the Rails inflector about our uncountable words - just follow the examples in the file. As you can see, it already has the words 'fish' and 'sheep' set as uncountable by default.

After verifying that the inflector does actually know about our particular word, we can go ahead and create our controller and model.

# app/config/routes.rb
resources :fish

# app/controllers/fish_controller.rb
class FishController << ApplicationController
  # ...
end

# app/models/fish.rb
class Fish
  # ...
end

Looking again at the routes, we will see the following:

fish_index GET    /fish(.:format)           {:action=>"index", :controller=>"fish"}
           POST   /fish(.:format)           {:action=>"create", :controller=>"fish"}
  new_fish GET    /fish/new(.:format)       {:action=>"new", :controller=>"fish"}
 edit_fish GET    /fish/:id/edit(.:format)  {:action=>"edit", :controller=>"fish"}
      fish GET    /fish/:id(.:format)       {:action=>"show", :controller=>"fish"}
           PUT    /fish/:id(.:format)       {:action=>"update", :controller=>"fish"}
           DELETE /fish/:id(.:format)       {:action=>"destroy", :controller=>"fish"}

Rails has taken care of our conflicting route problem by naming the route to the index method as fish_index instead of fish. Now in the views, we can simply do link_to :fish_index and it works. And all the other (singular) routes are exactly the same as expected.

It's just a matter of knowing to use the alternative 'index' route name.