bploetz / versionist

A plugin for versioning Rails based RESTful APIs.
MIT License
972 stars 51 forks source link

rake routes is duplicating api_version routes #50

Closed sricc closed 11 years ago

sricc commented 11 years ago

I am getting duplicate routes within a rake routes when resources are within an api_version block.

My routes are setup as follows

MyApp::Application.routes.draw do
  root :to => redirect("/signin")

  # Devise
  devise_for :users, :skip => [:sessions]
  as :user do
    get 'signin'     => 'home#index',              :as => :new_user_session
    post 'signin'    => 'devise/sessions#create',  :as => :user_session
    delete 'signout' => 'devise/sessions#destroy', :as => :destroy_user_session
  end

  # API
  api_version(:module => 'api/v1', :header => {:name => 'Accept', :value => "application/vnd.domain.com; version=1"}, :default => true, :defaults => {:format => :json}) do

      get 'things' => 'thing#index'
      resources 'thing', except: :index

  end
end

When I run rake routes this is my output

 Prefix Verb   URI Pattern                    Controller#Action
                root GET    /                              redirect(301, /signin)
       user_password POST   /users/password(.:format)      devise/passwords#create
   new_user_password GET    /users/password/new(.:format)  devise/passwords#new
  edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
                     PATCH  /users/password(.:format)      devise/passwords#update
                     PUT    /users/password(.:format)      devise/passwords#update
    new_user_session GET    /signin(.:format)              home#index
        user_session POST   /signin(.:format)              devise/sessions#create
destroy_user_session DELETE /signout(.:format)             devise/sessions#destroy
              things GET    /things(.:format)              api/v1/thing#index {:format=>:json}
         thing_index POST   /thing(.:format)               api/v1/thing#create {:format=>:json}
           new_thing GET    /thing/new(.:format)           api/v1/thing#new {:format=>:json}
          edit_thing GET    /thing/:id/edit(.:format)      api/v1/thing#edit {:format=>:json}
               thing GET    /thing/:id(.:format)           api/v1/thing#show {:format=>:json}
                     PATCH  /thing/:id(.:format)           api/v1/thing#update {:format=>:json}
                     PUT    /thing/:id(.:format)           api/v1/thing#update {:format=>:json}
                     DELETE /thing/:id(.:format)           api/v1/thing#destroy {:format=>:json}
                     GET    /things(.:format)              api/v1/thing#index {:format=>:json}
                     POST   /thing(.:format)               api/v1/thing#create {:format=>:json}
                     GET    /thing/new(.:format)           api/v1/thing#new {:format=>:json}
                     GET    /thing/:id/edit(.:format)      api/v1/thing#edit {:format=>:json}
                     GET    /thing/:id(.:format)           api/v1/thing#show {:format=>:json}
                     PATCH  /thing/:id(.:format)           api/v1/thing#update {:format=>:json}
                     PUT    /thing/:id(.:format)           api/v1/thing#update {:format=>:json}
                     DELETE /thing/:id(.:format)           api/v1/thing#destroy {:format=>:json}

As you can see, the first set of routes are:

              things GET    /things(.:format)              api/v1/thing#index {:format=>:json}
         thing_index POST   /thing(.:format)               api/v1/thing#create {:format=>:json}
           new_thing GET    /thing/new(.:format)           api/v1/thing#new {:format=>:json}
          edit_thing GET    /thing/:id/edit(.:format)      api/v1/thing#edit {:format=>:json}
               thing GET    /thing/:id(.:format)           api/v1/thing#show {:format=>:json}
                     PATCH  /thing/:id(.:format)           api/v1/thing#update {:format=>:json}
                     PUT    /thing/:id(.:format)           api/v1/thing#update {:format=>:json}
                     DELETE /thing/:id(.:format)           api/v1/thing#destroy {:format=>:json}

and the second set of routes are:

                     GET    /things(.:format)              api/v1/thing#index {:format=>:json}
                     POST   /thing(.:format)               api/v1/thing#create {:format=>:json}
                     GET    /thing/new(.:format)           api/v1/thing#new {:format=>:json}
                     GET    /thing/:id/edit(.:format)      api/v1/thing#edit {:format=>:json}
                     GET    /thing/:id(.:format)           api/v1/thing#show {:format=>:json}
                     PATCH  /thing/:id(.:format)           api/v1/thing#update {:format=>:json}
                     PUT    /thing/:id(.:format)           api/v1/thing#update {:format=>:json}
                     DELETE /thing/:id(.:format)           api/v1/thing#destroy {:format=>:json}

This doesn't happen if I put the same resource in a namespace or just as a regular route only within api_version.

Am I doing something wrong or is this a bug?

Thanks.

bploetz commented 11 years ago

It actually looks like the :default => true is causing that. Observe:

 api_version(:module => 'api/v1', :header => {:name => 'Accept', :value => "application/vnd.domain.com; version=1"}, :default => true, :defaults => {:format => :json}) do
    resources :foos
  end
> bundle exec rake routes
  Prefix Verb   URI Pattern              Controller#Action
    foos GET    /foos(.:format)          api/v1/foos#index {:format=>:json}
         POST   /foos(.:format)          api/v1/foos#create {:format=>:json}
 new_foo GET    /foos/new(.:format)      api/v1/foos#new {:format=>:json}
edit_foo GET    /foos/:id/edit(.:format) api/v1/foos#edit {:format=>:json}
     foo GET    /foos/:id(.:format)      api/v1/foos#show {:format=>:json}
         PATCH  /foos/:id(.:format)      api/v1/foos#update {:format=>:json}
         PUT    /foos/:id(.:format)      api/v1/foos#update {:format=>:json}
         DELETE /foos/:id(.:format)      api/v1/foos#destroy {:format=>:json}
         GET    /foos(.:format)          api/v1/foos#index {:format=>:json}
         POST   /foos(.:format)          api/v1/foos#create {:format=>:json}
         GET    /foos/new(.:format)      api/v1/foos#new {:format=>:json}
         GET    /foos/:id/edit(.:format) api/v1/foos#edit {:format=>:json}
         GET    /foos/:id(.:format)      api/v1/foos#show {:format=>:json}
         PATCH  /foos/:id(.:format)      api/v1/foos#update {:format=>:json}
         PUT    /foos/:id(.:format)      api/v1/foos#update {:format=>:json}
         DELETE /foos/:id(.:format)      api/v1/foos#destroy {:format=>:json}
 api_version(:module => 'api/v1', :header => {:name => 'Accept', :value => "application/vnd.domain.com; version=1"}, :defaults => {:format => :json}) do                                                                                                                                           
    resources :foos
  end
> bundle exec rake routes
  Prefix Verb   URI Pattern              Controller#Action
    foos GET    /foos(.:format)          api/v1/foos#index {:format=>:json}
         POST   /foos(.:format)          api/v1/foos#create {:format=>:json}
 new_foo GET    /foos/new(.:format)      api/v1/foos#new {:format=>:json}
edit_foo GET    /foos/:id/edit(.:format) api/v1/foos#edit {:format=>:json}
     foo GET    /foos/:id(.:format)      api/v1/foos#show {:format=>:json}
         PATCH  /foos/:id(.:format)      api/v1/foos#update {:format=>:json}
         PUT    /foos/:id(.:format)      api/v1/foos#update {:format=>:json}
         DELETE /foos/:id(.:format)      api/v1/foos#destroy {:format=>:json}

The way defaulting works is that it actually puts in another scope into your routes to handle the case when no version is requested:

https://github.com/bploetz/versionist/blob/master/lib/versionist/routing.rb#L64

Unfortunately rake routes doesn't show you the advanced constraints info (http://guides.rubyonrails.org/routing.html#advanced-constraints) which is where versionist hooks in to do it's thing. If it did, you'd see one is the actual version handler, the other is the default handler.

sricc commented 11 years ago

Ah, ok, I see. Maybe it would be a good idea to make a note of this in the README docs.

Thanks!

bploetz commented 11 years ago

Yeah I will note this in the README. Frankly I never noticed it until you pointed it out. :-) Thanks!

sricc commented 11 years ago

Glad to help!

thewatts commented 7 years ago

I seem to be having this same issue, but I'm not declaring the default: true configuration. Instead, I'm adding an Accept header.

Has Duplicates (in a different scope)


namespace :api do
api_version(module: 'V2', header: { name: 'Accept', value: 'application/vnd.api+json' }, path: { value: 'v2' }) do
resources :account_lists, only: [:index, :show, :update] do
```sh
$ bundle exec rake routes

/api/account_lists
/api/v2/account_lists

Doesn't have Duplicates

namespace :api do
api_version(module: 'V2', path: { value: 'v2' } do
resources :account_lists, only: [:index, :show, :update] do

$ bundle exec rake routes

/api/v2/account_lists



I see [here](https://github.com/bploetz/versionist#multiple-versioning-strategies-per-api-version) that you can specify multiple versions - but honestly I'm not sure where in that code example a second version is being declared. 

Thanks for your hard work on this gem!
bploetz commented 7 years ago

@thewatts Weird. Can you include your entire routes file and the entire output of rake routes? Also, what version of Rails and versionist are you using?

psahni commented 1 year ago

Hello All Is it possible to skip the header for a particular route in api_version V1

bploetz commented 1 year ago

@psahni what do you mean by "skip the header"?