How to add param to single action route in Rails? - ruby-on-rails

I have a nested route denoting a single action, which works as expected in the following way:
resources :abilities, param: :slug do
delete '/company/:slug', controller: :ability_companies, action: :destroy
end
However, all my routes are defined as symbols, with the param name defined as an attribute, and I'd like to keep my route definitions coherent.
The following syntax seems to follow the logic I use elsewhere, but it doesn't work and I cannot figure out why.
delete :company, controller: :ability_companies, action: :destroy, param: :slug
With the above line, I get a 404 error. What am I overlooking here?

on config/routes.rb
resources :abilities, only: [ :destroy ], as: :destroy
on app/controllers/ability_contoller.rb
def destroy
#ability = Ability.find(params[:my_params])
#ability.destroy
redirect_to any_path
end
private
def my_params
params.require(:abilities).permit(:slug )
end

I ended up treating the nested route as a nested resource. The result is more semantic (plural "companies") and cleaner as well, given how 90% of my routes are handled on a resource-level.
The resulting lines of code:
resources :abilities, param: :slug do
resources :companies, controller: :ability_companies, param: :company_slug, only: [:destroy, :create]
end

Related

Rails routes: Order of nested scopes

so I have a route that is a bit more complex and I have an issue with the order of two nested scopes. Their order seems to be reversed. I want the most inner scope to be the last segment of the URL before the action. But it is the first.
routes.rb
namespace :customer do
namespace :api do
resources :products, only: [], param: :uid do
scope module: 'products' do
scope :buyer do
post :set_to_waiting_list, to: 'buyers#set_to_waiting_list'
end
end
end
end
end
Controller:
module Customer
module Api
module Products
class BuyersController < Customer::ApiController
def set_to_waiting_list
# do stuff
end
end
end
end
end
This gives me this route when running rake routes:
customer_api_product_set_to_waiting_list POST /customer/api/buyer/products/:product_uid/set_to_waiting_list(.:format) customer/api/products/buyers#set_to_waiting_list
But the URL I'm actually looking for is:
POST /customer/api/products/:product_uid/buyer/set_to_waiting_list
Reason is that this modifies the buyer and not the product. Also the buyer is fetched via the product uid (plus logged in user), so this URL format makes much more sense.
I don't really understand
I'm still interested in the logic behind this. But for anyone looking for an solution, I just solved it with:
namespace :customer do
namespace :api do
resources :products, only: [], param: :uid do
scope module: 'products' do
post :set_to_waiting_list, path: 'buyer/set_to_waiting_list' to: 'buyers#set_to_waiting_list'
end
end
end
end
You need to add a nested block so that the scope knows where to place the path prefix:
namespace :customer do
namespace :api do
scope module: 'products' do
resources :products, only: [], param: :uid do
nested do
scope :buyer do
post :set_to_waiting_list, to: 'buyers#set_to_waiting_list'
end
end
end
end
end
end
nested is not documented, so I'm not sure if you should rely on this. As an alternative, you can add a resource :buyers, only: [] {} wrapper:
namespace :customer do
namespace :api do
scope module: 'products' do
resources :products, only: [], param: :uid do
resource :buyer, only: [] do
collection do
post :set_to_waiting_list
end
end
end
end
end
end
The route and controller are the same in both cases, but the URL helper is different:
customer_api_product_set_to_waiting_list
POST /customer/api/products/:product_uid/buyer/set_to_waiting_list(.:format)
customer/api/products/buyers#set_to_waiting_list
vs
set_to_waiting_list_customer_api_product_buyer
POST /customer/api/products/:product_uid/buyer/set_to_waiting_list(.:format)
customer/api/products/buyers#set_to_waiting_list
Source: https://github.com/rails/rails/issues/12626

Named routes for nested resources

I'm actually having trouble finding the documentation for this so if you have a link handy that would be really appreciated too.
So I have:
resources :users do
resources :posts, only: [:index, :create, :show]
end
I wanted to access the index action of posts through a named route. I tried this: <%= link_to 'User Posts', user_posts_path %> but it said it was missing user_id. Any ideas?
When using the nested resource routes, you would need to provide the reference id of the parent resource. In your case resource user. You could do: user_posts_path(user). The route generated would be something like: /users/1/posts where 1 is the :user_id or if you would rather want a route like: /users/posts you should do:
resources :users do
collection do
resources :posts
end
end
Find full routing documentation here
it's asking for user_id because you're defining :users as a resource, change it for a namespace instead:
namespace :users do
resources :posts, only: [:index, :create, :show]
end

How do I write the routes for these resources?

For my rails application the associations are as follows:
A user has many bookmarks and belongs to user.
A user has many friendships.
A user has many reminders.
A user has many comments.
A bookmark has many comments.
A comment belongs to a user and belongs to a bookmark.
A friendship belongs to a user.
A reminder belongs to a user
My routes.rb file:
Rails.application.routes.draw do
root 'welcome#index'
get 'home', :to => 'home#index'
get 'searchApis', :to => 'home#searchApis'
devise_for :users, :controllers => { registrations: 'registrations' }
resources :users, shallow: true do
resources :bookmarks, except: :new
resources :friendships, only: [:index, :show, :destroy]
resources :reminders
end
resources :bookmarks, shallow: true do
resources :comments
end
end
Am I writing these routes out correctly?
When I rake routes, I'm getting bookmarks#index twice so I am confused. The prefix for one of them is bookmark, and the other is bookmarks. Why is this happening?
From my understanding, the application does not need to see all of the bookmarks in an index because they are only visible to the user who made them. However, I want reminders to be visible to the user's friends.
I'm hoping to get suggestions to clean up my routes, if possible. I really doubt I am doing this correctly.
My interpretation of your spec:
#config/routes.rb
resources :users, only: [] do #-> show a user's collections (no edit)
resources :bookmarks, shallow: true, except: [:new, :edit, :update] #-> url.com/bookmarks/:id
resources :comments, :friendships, :reminders, shallow: true, only: [:index, :show] #-> url.com/comments/:id
end
resource :bookmarks, except: :index do #-> url.com/bookmarks/:id
resources :comments #-> url.com/bookmarks/:bookmark_id/comments/:id -- should be scoped around current_user
end
For the comments controller, do this:
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def new
#bookmark = Bookmark.find params[:bookmark_id]
#comment = #bookmark.comments.new
end
def create
#bookmark = Bookmark.find params[:bookmark_id]
#comment = #bookmark.comments.new bookmark_params
#comment.user = current_user
#comment.save
end
end
Don't make a welcome or home controller, you don't need them.
You can put off-hand actions in your application controller:
#config/routes.rb
root 'application#index'
get 'home', to: 'application#home'
get 'search_apis', to: 'application#search_apis'
Of course this is somewhat of an antipattern (you'll end up bloating your ApplicationController), but if you only have obscure "one-off" actions in other controllers, you'll be best suited using the above.
Also, only use snake_case with lowercase for URL's - HTTP's spec determines all URLs should be handled as lowercase:
Converting the scheme and host to lower case. The scheme and host
components of the URL are case-insensitive. Most normalizers will
convert them to lowercase. Example: →
http://www.example.com/
Although that's only for the domain/host, it's applicable in the URL itself, too.
Shallow provides the :index, :new and :create only. So you're getting index twice. Once from within users and the other bookmarks - comments.
On re-reading your associations at the start of your post, and that comments belongs_to both users AND bookmarks, it's probably a good idea to create a Polymorphic relationship.
A rough guide would be for your models,
class Comment < ActiveRecord::Base
belongs_to :messages, polymorphic: true
end
class User < ActiveRecord::Base
has_many :comments, as: :messages
end
class Bookmark < ActiveRecord::Base
has_many :comments, as: :messages
Then either rails generate migration Comments if you haven't already, and have it look like the following:
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :name
t.references :messages, polymorphic: true, index: true
t.timestamps null: false
end
end
end
otherwise run a migration to add columns to your Comment model. I.e rails g migration AddMessagesToComments messages:references
But be sure to open your new migration file named as above and add polymorphic: true before you rake db:migrate

Two routes with multiple parameters to single controller's method?

Let's assume I have models: Article, Post, and Comment. For these models I need to map CommentsController#show in articles and posts. Something like this:
resources :articles do
resources :comments, :only => [:show]
end
resources :posts do
resources :comments, :only => [:show]
end
And this works perfectly, i.e. it'll generate routes something like this:
/articles/:article_id/comments/:id
/posts/:post_id/comments/:id
Both pointing to the same controller's single method: CommentsController#show
What I need is to include more/multiple parameters for posts while retaining the same URL routes to CommentsController:
/articles/:article_id/comments/:id
/posts/:post_id/:post_title/:post_year/comments/:id
I tried match but first occurrence at resources :articles do... overrides it.
resources :articles do
resources :comments, :only => [:show]
end
match '/posts/:post_id/:post_title/:post_year/comments/:id', :to => 'comments#show', :as => :show_post_comments
It throws an error:
No route matches {:controller=>"comments", :action=>"show",
:post_id=>10, :post_title=>"some title", :post_year=>"1995", :id=>"1"}
So, is it possible to create such a route with multiple parameters to single controller's method in two resources using get or something?

rails routing nested resources duplicate

I want to support:
POST images/1/comments/2/like
and
POST comments/2/like
They both point to same resources with same action. How can I do that in rails route file?
resources :images do
resources :comments do
member do
post 'like'
end
end
end
This will work for
POST images/1/comments/2/like
but how can I also for when I don't specify the images/1 part?
You can make it more beautiful actually. According to http://ruby-journal.com/how-to-dry-your-rails-routes/, this also works:
comments = Proc.new do
member do
post 'like'
end
end
resources :comments, &comments
resources :images do
comments.call
end
and in Rails 4 you could use concerns
concern :comments_concern do
member do
post 'like'
end
end
resources :comments, concerns: :comments_concern
resources :images, concerns: :comments_concern do
#do more stuff here
end
I didn't test this, but it might help. Have a look at the website mentioned. Good luck
-frbl
I would consider reworking which RESTful routes for Comment you're nesting, and which you're not. I'm assuming your models look something like this:
# app/models/image.rb
class Image < ActiveRecord::Base
has_many :comments
end
# app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :image
end
Because your Image and Comment models possess a one-to-many relationship, I can see why you'd think to nest the comments resource route within the images one. However, of all the CRUD actions in comments_controller.rb, only create actually requires that a parent image ID be explicitly passed in. From a RESTful perspective, only the new and create actions require that a image_id is passed to the action. The edit, update, delete, and like actions can all take place independently of the parent image.
Consider the alternative routing schematic instead:
# config/routes.rb
resources :images do
resources :comments, :only => [:index, :new, :create]
end
resources :comments, :only => [:show, :edit, :update, :destroy] do
member do
post 'like'
end
end
Now, only the comment actions that are explicitly depended on a parent ID are actually nested within the images routes. The remainder of the comment actions are directly routed to the comments controller without passing in the parent ID. Your routes are no longer duplicated, and each action will have exactly one route declared for it.
simply add resources :images it may work
Not sure there is a much more beautiful way than repeating this below:
resources :comments do
member do
post 'like'
end
end
Like so:
resources :images do
resources :comments do
member do
post 'like'
end
end
end
resources :comments do
member do
post 'like'
end
end

Resources