api resource rails routes - ruby-on-rails

So I have some API resources in my app, and some regular resources, and for regular resources I use :
resources :books
And then I could pass except: %i(destroy new edit) or only so works great! However for my resource I ll never have new/edit actions, sometimes I will need to pass except and only options too.
I was thinking to create something like:
api_resources :books
Which comes without new/edit actions by default, how would I do that?

Maybe something like this?
# config/routes.rb
Rails.application.routes.draw do
def api_resources(res)
resources res, only: [:new, :edit]
end
api_resources :a
api_resources :b
end
# output
Prefix Verb URI Pattern Controller#Action
new_a GET /a/new(.:format) a#new
edit_a GET /a/:id/edit(.:format) a#edit
new_b GET /b/new(.:format) b#new
edit_b GET /b/:id/edit(.:format) b#edit

#Amree's answer can not handle nested resources. An improvement would be:
# config/routes.rb
Rails.application.routes.draw do
def editable_resoustrong textrces(res, &block)
resources res, only: %i[new edit], &block
end
editable_resources :a
end
# output
Prefix Verb URI Pattern Controller#Action
new_a GET /a/new(.:format) a#new
edit_a GET /a/:id/edit(.:format) a#edit

Related

Modifying a rails route

I have the following route:
resources :trees, shallow: true, controller: 'base', param: :ed_node_id, only: %i[index show] do
resources :courses, shallow: true, controller: 'base', param: :ed_node_id, only: %i[index show]
end
Which gives me:
api_v1_tree_courses GET /api/v1/trees/:tree_ed_node_id/courses(.:format)
api_v1_course GET /api/v1/courses/:ed_node_id(.:format)
api_v1_trees GET /api/v1/trees(.:format)
api_v1_tree GET /api/v1/trees/:ed_node_id(.:format)
What i’m trying to avoid is on that first route, the param not being prefixed by :tree. Is there a way in nested routes to force the param not to have a parent route prefix or possibly using a regex to give the desired route?
Unfortunately Rails just generates the param key for nested resources by combining the singular form of the name with the param option:
module ActionDispatch
module Routing
module Mapper
class Resource
...
def nested_param
:"#{singular}_#{param}"
end
end
end
end
end
There is no option to actually alter the entire nested param - it would make a good feature request though.
The alternative is to use scope:
resources :trees, only: :index
scope '/trees/:ed_node_id' do
resources :courses,
only: :index,
as: :tree_courses
end
# shallow nesting won't work so we have to define this separately.
resources :courses, only: [:show]

Rails query parameter with same name as optional path parameter

Suppose, I have this code in routes.rb
get "/items/(:brand)", to: "items#index", as: :items
I can't change this route, because sometimes I need urls with brand in path (not in query).
Can I create path like this:
/items?brand=TestBrand
but not this:
/items/TestBrand
via route helpers?
items_path(brand: "TestBrand") give 2'nd variant.
This is not a very good solution as it violates the RESTful conventions.
In Rails flavor REST GET /resource_name/:id maps to the show route. In the case of get "/items/(:brand)", to: "items#index", as: :items this creates an ambiguity (is the segment an item id or a brand?) when the router matches the requests and the first declared route will win which is hardly desirable.
A better solution is to declare it as a nested resource:
resources :brands, only: [] do
resources :items, only: [:index], module: :brands
end
Prefix Verb URI Pattern Controller#Action
brand_items GET /brands/:brand_id/items(.:format) brands/items#index
# app/controllers/brands/items_controller.rb
module Brands
class ItemsController < ::ApplicationController
before_action :set_brand
# GET /brands/:brand_id/items
def index
#items = #brand.items
end
def set_brand
#brand = Brand.find(params[:brand_id])
end
end
end
Additional parameters in "get" are supported by default, so maybe you could use
get "/items", to: "items#index", as: :items
or
resources :items, only: [:index]
And use path helper which you provided:
items_path(brand: "TestBrand")
To answer your question - Yes, you can
get "/items", to: "items#index", as: :items
and the following route helper will create
items_path(brand: "TestBrand")
#=> items?brand=TestBrand
NOTE:
If you are using:
recourses :items
You must alredy have this

How to properly add single route to existing route resources

I want to properly add new route to already existing route resources in Spree.
Desired end url format: /orders/:order_id/order_returns(.:format)
I can achieve this by adding following snippet to routes.rb:
Rails.application.routes.draw do
resources :orders, only: [] do
resources :order_returns, controller: 'order_returns', only: [:create]
end
end
but resources :orders, only: [] do looks kind of ugly with empty hash and if I remove it Rails will generate routes for orders :(
Is there a better, Rails/Spree way to achieve that ?

Rails append common route to several routes

Here is my route:
resources :campaigns, only: [:index, :show]
get '/signs/:sign_id', to: 'signs#show', as: 'sign'
#...others like this...
I'm wanting to append a sub-route to the end of each of the routes above. The sub-route I want to append is:
'/location/:location_id'
This way, I would be able to access:
/campaigns/1
/campaigns/1/location/2
/signs/13
/signs/1/location/12
etc.
I looked into Routing Concerns, but I'm not sure if that would solve my problem. I tried something like this:
#routes.rb
concern :locationable do
member do
get '/location/:location_id'
end
end
resources :campaigns, only: [:show], concerns: :locationable
But obviously this is wrong and it does not work (does not add anything to rake routes). How can I achieve a dry routing solution?
Define the location route as a resource under the concern, like this:
concern :locationable do
resources :locations, only: :show
end
resources :campaigns, only: :show, concerns: :locationable
resources :signs, only: :show, concerns: :locationable
That will generate the following routes:
$ rake routes
Prefix Verb URI Pattern Controller#Action
campaign_location GET /campaigns/:campaign_id/locations/:id(.:format) locations#show
campaign GET /campaigns/:id(.:format) campaigns#show
sign_location GET /signs/:sign_id/locations/:id(.:format) locations#show
sign GET /signs/:id(.:format) signs#show
Source: http://guides.rubyonrails.org/routing.html#routing-concerns

Routing Error: uninitialized constant

I'm trying to set up routes for a mobile API, which should have a versioned api-path. I already could make the mobile Auth work, which is implemented in a separate Controller AuthController located in /controllers/api/v1/mobile/.
Usage example:
myapp.com/api/v1/mobile/auth
But now I want to register my existing ressources-Controllers to this path-pattern as additional api-routes. Concrete: this would be the TasksController located at /controllers/tracker/tasks_controller.rb. So I added a mobile route to the routes-definition:
# routes.rb
namespace :tracker, path: 'timetracking' do
resources :tasks, 'jobs'
end
namespace :api do
namespace :v1 do
namespace :mobile do
resources :auth, :only => [:create, :destroy]
namespace :tracker do #added mobile route
resource :tasks, controller: 'tracker/tasks', as: :mobile_tasks
end
end
end
end
But when I call myapp.com/api/v1/mobile/tracker/tasks it results in an error-message:
Routing Error
uninitialized constant Api::V1::Mobile::Tracker
I especially added the alias :mobile_tasks to this route, to avoid any conflicts with the original tasks-route above. Any ideas, how to set the controller properly for this route?
Update#1
Defining this route as a scope instead of a namespace, didn't work aswell.
scope "/api/v1/mobile/tracker" do
resources :tasks, controller: 'tracker/tasks', as: :mobile_tasks
end
But this time, it didn't even resolve the route-path itself.
Routing Error
No route matches [GET] "/api/v1/mobile/tracker/tasks"
I assume it might be a problem, that my additional mobile-api route tries to point to a completely different namespace tracker.
According to http://guides.rubyonrails.org/routing.html#controller-namespaces-and-routing you should use scope instead of namespace.
If you want to route /admin/posts to PostsController (without the Admin:: module prefix), you could use:
scope "/admin" do
resources :posts, :comments
end
Adding this answer to get clarity on namespace & scope.
When you use namespace, it will prefix the URL path for the specified resources, and try to locate the controller under a module named in the same manner as the namespace.
# config/routes.rb
namespace :admin do
resources :posts, only: [:index]
end
# rake routes
Prefix Verb URI Pattern Controller#Action
admin_posts GET /admin/posts(.:format) admin/posts#index
When we add scope, it will just map the controller action for the given scope patterns. No need to define controller under any module.
# config/routes.rb
scope :admin do
resources :posts, only: [:index]
end
# rake routes
Prefix Verb URI Pattern Controller#Action
admin_posts GET /admin/posts(.:format) posts#index
Note that, controller is just posts controller without any module namespace.
If we add a path option to scope it will map to the controller with the path option specified as follows
# config/routes.rb
scope module: 'admin', path: 'admin' do
resources :posts, only: [:index]
end
# rake routes
Prefix Verb URI Pattern Controller#Action
admin_posts GET /admin/posts(.:format) admin/posts#index
Note that the controller now is under admin module.
Now, if we want to change the name of path method to identify resource, we can add as option to scope.
# config/routes.rb
namespace module: 'admin', path: 'admin', as: 'root' do
resources :posts, only: [:index]
end
# rake routes
Prefix Verb URI Pattern Controller#Action
root_posts GET /admin/posts(.:format) admin/posts#index
You can see the change in the Prefix Verb.
Hope it helps others.
Late answer, but still might be helpful:
scope '/v1' do
resources :articles, module: 'v1'
end
controller
# app/controller/v1/articles_controller.rb
class V1::ArticlesController < ApplicationController
end
Now you should be able to access this url:
http://localhost:3000/v1/articles

Resources