52

I'm developing a REST API based on rails. To use this API, you MUST be logged in. Regarding that, I'd like to create a method me in my user controller that will return a JSON of the logged in user infos. So, I don't need an :id to be passed in the URL. I just want to call http://example.com/api/users/me

So I tried this:

namespace :api, defaults: { format: 'json' } do
  scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
    resources :tokens, :only => [:create, :destroy]
    resources :users, :only => [:index, :update] do

      # I tried this
      match 'me', :via => :get
      # => api_user_me GET    /api/users/:user_id/me(.:format)       api/v1/users#me {:format=>"json"}

      # Then I tried this
      member do
        get 'me'
      end
      # => me_api_user GET    /api/users/:id/me(.:format)            api/v1/users#me {:format=>"json"}

    end
  end
end

As you can see, my route waits for an id, but I'd like to get something like devise has. Something based on current_user id. Example below:

edit_user_password GET    /users/password/edit(.:format)         devise/passwords#edit

In this example you can edit the current user password without passing the id as a param.

I could use a collection instead of a member, but that's a dirty bypass.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Gozup
  • 1,005
  • 2
  • 10
  • 20

7 Answers7

125

The way to go is to use singular resources:

So, instead of resources use resource:

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. In this case, you can use a singular resource to map /profile (rather than /profile/:id) to the show action [...]

So, in your case:

resource :user do
  get :me, on: :member
end

# => me_api_user GET    /api/users/me(.:format)            api/v1/users#me {:format=>"json"}
weexpectedTHIS
  • 3,358
  • 1
  • 25
  • 30
Waiting for Dev...
  • 12,629
  • 5
  • 47
  • 57
  • 1
    Great, this just made my route file a lot simpler. – mc9 Jan 29 '15 at 09:56
  • 5
    This should be accepted. While `get 'users/me' => 'users#me'` works well enough, grouping them as a singular resource is a cleaner approach. – Orlando May 06 '16 at 05:37
  • Just make sure you pay attention to the red box mentioning a long standing bug regarding singular resources. https://github.com/rails/rails/issues/1769 – Kyle Heironimus Jul 27 '16 at 17:32
  • I think if you had defined the `resources` then adding to the `collection` would be a better (as some of the other answers have pointed out). Adding a singular resource will work but I think it would make more sense and be more readable to keep it all together – doz87 Mar 21 '18 at 11:39
  • 1
    CAUTION: one should either use the singular ```resource``` or the plural ```resources``` but not combine them, otherwise only the first occurrence will be routed to. – weshouman Mar 23 '20 at 04:48
24

Resource routes are designed to work this way. If you want something different, design it yourself, like this.

match 'users/me' => 'users#me', :via => :get

Put it outside of your resources :users block

Arjan
  • 6,264
  • 2
  • 26
  • 42
  • Brilliant Arjan. Exactly what I needed... except that I won't have path but I can do without it. Cheers. – Gozup Jun 12 '13 at 13:37
  • No problem, you could add a path by adding `:as => 'me'`. This will add `me_path` and `me_url`. – Arjan Jun 12 '13 at 13:40
  • 1
    Consider using singular resources as the [answer](http://stackoverflow.com/a/25642181/2057928) – RPinel Apr 30 '15 at 13:28
21

You can use

resources :users, only: [:index, :update] do
  get :me, on: :collection
end

or

resources :users, only: [:index, :update] do
  collection do
    get :me
  end
end

"A member route will require an ID, because it acts on a member. A collection route doesn't because it acts on a collection of objects. Preview is an example of a member route, because it acts on (and displays) a single object. Search is an example of a collection route, because it acts on (and displays) a collection of objects." (from here)

imtk
  • 1,510
  • 4
  • 18
  • 31
14

Maybe I am missing something, but why don't you use:

get 'me', on: :collection
jvperrin
  • 3,368
  • 1
  • 23
  • 33
EfratBlaier
  • 559
  • 2
  • 6
  • That's what I said in my conclusion. It's not a nice work around since a collection is an array of multiple object. I'm asking for the current_user, so 1 object, so a member ;-) – Gozup Jun 12 '13 at 13:34
7
  resources :users, only: [:index, :update] do
    collection do
      get :me, action: 'show' 
    end
  end

specifying the action is optional. you can skip action here and name your controller action as me.

manohar
  • 155
  • 1
  • 9
3

This gives same result as Arjan's in simpler way

get 'users/me', to: 'users#me'

Raf
  • 91
  • 1
  • 3
0

When you create a route nested within a resource, you can mention, whether it is member action or a collection action.

namespace :api, defaults: { format: 'json' } do
  scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
    resources :tokens, :only => [:create, :destroy]
    resources :users, :only => [:index, :update] do

      # I tried this
      match 'me', :via => :get, :collection => true
...
...
manoj
  • 1,655
  • 15
  • 19
  • That's what I said in my conclusion. It's not a nice work around since a collection is an array of multiple object. I'm asking for the current_user, so 1 object, so a member ;-) – Gozup Jun 12 '13 at 13:35