atomaka.com/_posts/2016-11-18-singular-and-plural-rails-routes-for-the-same-resource.md

72 lines
3.1 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
layout: post
title: Singular and Plural Rails Routes for the Same Resource
tag:
- api
- ruby-on-rails
- restful
---
Sometimes when building your API with Rails,
[following best practices](http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api)
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
solutionbut 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.
{% gist 1a056f8dca8931d8872c6cfefebb2d1a %}
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](http://guides.rubyonrails.org/routing.html) for
guidance and came across
[singular resources](http://guides.rubyonrails.org/routing.html#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 others 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.
{% gist e8d6641349e4d0ea7e68d22dd3755e9d %}
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](https://github.com/rails/rails/issues/1769)!
And we can work around that one as well by
[overriding the named helpers](http://guides.rubyonrails.org/routing.html#overriding-the-named-helpers).
Passing as: to our resource definition creates the helpers with a new name.
Our final code is ready!
{% gist e9dcc4cd4bad89554fb01be6627c7b63 %}
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.