..

Singular and Plural Rails Routes for the Same Resource

Sometimes when building your API with Rails, following best practices may seem difficult. I recently came across one of these cases and was tempted to take a shortcut. However, I held strong and eventually came to a quality RESTful solution — but not without issue.

I wanted to allow users of my API to quickly access their own user profile. Our application had already implemented a route to allow this via GET /profiles/:id and simply implemented with resources :profiles, only: [:show] in our routes configuration.

Unfortunately, our API users wanted to be able to access their profile without providing their ID. My first pass at resolving this was passed in a “fake resource” to accomplish this.

But I had broken one of the RESTful best practices. /profiles/me is not an actual resource but we are pretending it is. So I looked to the Rails routes documentation for guidance and came across singular resources.

Sometimes, you have a resource that clients always look up without referencing an ID. For example, you would like /profile to always show the profile of the currently logged in user.

I should not have been surprised that my exact use case was cited!

Now we are back on track! I get to go back to my simple route declaration with resource :profile, only: :show and without changing my controller code at all.

But now I needed users to be able to access each other’s profiles. Again, the Rails documentation had me covered.

Because you might want to use the same controller for a singular route (/account) and a plural route (/accounts/45), singular resources map to plural controllers. So that, for example, resource :photo and resources :photos creates both singular and plural routes that map to the same controller (PhotosController).

And our implementation stays clean.

This was awesome until I needed to use path helpers. With this implementation, profile_path(:id) works as expected but profile_path does not. If the order is reversed in the routes configuration, profile_path will work and profile_path(:id) will not. This is the result of a bug in the Rails core that touches some pretty intricate code that is linked to other issues. One has even been open for five years!

And we can work around that one as well by overriding the named helpers. Passing as: to our resource definition creates the helpers with a new name.

Our final code is ready!

In our application, we can reference a generic profile with profile_path(:id) while still having current_profile_path to direct the user to their own profile.