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

73 lines
3.1 KiB
Markdown
Raw Permalink Normal View History

2023-02-05 21:07:24 -05:00
---
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.