72 lines
3.1 KiB
Markdown
72 lines
3.1 KiB
Markdown
---
|
||
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
|
||
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.
|
||
|
||
{% 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 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.
|
||
|
||
{% 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.
|