libovness
libovness

Reputation: 398

Match multiple paths to single controller action

In my app, coins may or may not belong to networks. Hence I have the following in my routes.rb:

resources :coins

resources :networks do
    resources :coins
end

You can follow coins via coins#follow and unfollow them via coins#unfollow

This is working just fine for coins which belong to networks via:

match 'networks/:network_id/coins/:id/follow', to: 'coins#follow', via: 'get', :as => :follow_coin

match 'networks/:network_id/coins/:id/unfollow', to: 'coins#unfollow', via: 'get', :as => :unfollow_coin

I'd like to do this as well:

match 'coins/:id/follow', to: 'coins#follow', via: 'get', :as => :follow_coin

match '/coins/:id/unfollow', to: 'coins#unfollow', via: 'get', :as => :unfollow_coin

But I am getting this error when visiting coins/coinname, for example:

Invalid route name, already in use: 'follow_coin' You may have defined two routes with the same name using the `:as` option, or you may be overriding a route already defined by a resource with the same naming.

I am unclear on what the proper solution is. I'd like to map both networks/:network_id/coins/:id/follow and coins/:id/follow to the coins#follow action, which is already prepared to handle coins that belong to networks and coins that don't.

I suspect that even with:

match 'networks/:network_id/coins/:id/follow', to: 'coins#follow', via: 'get', :as => :follow_coin

I could be doing this better with networks resources

Upvotes: 0

Views: 2751

Answers (2)

jvillian
jvillian

Reputation: 20263

In addition to what seaify said (great advice!), you could just do:

resource :coins do
  member do
    put :follow
    put :unfollow
  end
end

Then, when you want to use the network_id, do something like (assuming you have a @coin variable defined):

follow_coin_path(network_id: network_id)

Which will give you something like:

/coins/2/follow?network_id=1

And your params will be something like:

{..., id: 2, network_id: 1, ...}

Since it sounds like you already have the logic to determine whether the network_id parameter is present.

If you're trying to use coinname as the identifying variable (instead of id), then do something like:

resources :coins, param: :coinname do 
  member do 
    put :follow
    put :unfollow
  end
end

When you do rake routes, you should see something like:

   follow_coin PUT    /coins/:coinname/follow(.:format)        coins#follow
 unfollow_coin PUT    /coins/:coinname/unfollow(.:format)      coins#unfollow
         coins GET    /coins(.:format)                         coins#index
               POST   /coins(.:format)                         coins#create
      new_coin GET    /coins/new(.:format)                     coins#new
     edit_coin GET    /coins/:coinname/edit(.:format)          coins#edit
          coin GET    /coins/:coinname(.:format)               coins#show
               PATCH  /coins/:coinname(.:format)               coins#update
               PUT    /coins/:coinname(.:format)               coins#update
               DELETE /coins/:coinname(.:format)               coins#destroy

And your params will contain something like:

{..., coinname: 'bitcoin', ...}

Also, when you're using link_to, remember to do:

link_to("Follow #{@coin.name}", follow_coin_path(@coin), method: :put)

If you don't use method: :put, then link_to generates a get. And you'll end up with the No route matches [GET] "/coins/coinname/follow" error.

Upvotes: 1

seaify - Freelancer
seaify - Freelancer

Reputation: 748

It's ok to set same controller#action for different url.

But they should have different route helper, so in rails view or controller, we can generate corresponding url for them.

Change code to this

match 'networks/:network_id/coins/:id/follow', to: 'coins#follow', via: 'get', :as => :network_follow_coin
match 'coins/:id/follow', to: 'coins#follow', via: 'get', :as => :follow_coin

So when use network_follow_coin(network_id, coin_id), it generate url /networks/1/coins/2/follow.

When use follow_coin_path(coin_id), it generate url /coins/2/follow.

As to simplify code, you should use resource for better wrap and understand, reduce the use of match(actually i think never). Change to this way

resource :networks do
   resource :coins do
     member do
       put :follow
       put :unfollow
     end
   end
end

And another thing is that if you change something in your database, you shouldn't set http method as get. If it's get method, it suppose to just fetch data, no modify. It's also dangerous, because some spiders keep visit every link in your websites, it might activate some bad things.

Upvotes: 1

Related Questions