Rails nesting scope block in resources - ruby-on-rails

I'm trying to create a path like product/:id/monthly/revenue/ and product/:id/monthly/items_sold and the equivalent named routes product_monthly_revenue and product_monthly_items_sold, and these routes would simply show the charts. I tried
resources :products do
scope 'monthly' do
match 'revenue', to: "charts#monthly_revenue", via: 'get'
match 'items_sold', to: "charts#monthly_items_sold", via: 'get'
end
end
But this gives me the routes:
product_revenue GET /monthly/products/:product_id/revenue(.:format) charts#monthly_revenue
product_items_sold GET /monthly/products/:product_id/items_sold(.:format) charts#monthly_items_sold
where monthly gets appended in front instead, and the route naming is off. I know I could just do:
resources :products do
match 'monthly/revenue', to: "charts#monthly_revenue", via: 'get', as: :monthly_revenue
match 'monthly/items_sold', to: "charts#monthly_items_sold", via: 'get', as: :monthly_items_sold
end
but that isn't DRY, and it gets crazy when I try to add more categories like yearly. Using a namespace would force me to create a new controller for each namespace, when I want to consolidate all the charts into a single controller.
So I guess the summarised question would be: is it possible to namespace routes without namspacing controllers? Or is it possible to consolidate the creation of categories of named routes?
Edit: Using
resources :products do
scope "monthly", as: :monthly, path: "monthly" do
match 'revenue', to: "charts#monthly_revenue", via: 'get'
match 'items_sold', to: "charts#monthly_items_sold", via: 'get'
end
end
would give me the routes
monthly_product_revenue GET /monthly/products/:product_id/revenue(.:format) charts#monthly_revenue
monthly_product_items_sold GET /monthly/products/:product_id/items_sold(.:format) charts#monthly_items_sold
which similar to the first block, is unexpected because I expect that if a scope is nested in a resources block, only the routes in the scope block would affected by the the scope, not the resources block.
Edit 2: Forgot to include this information earlier, but I'm on Rails 4.0.0, with Ruby 2.0.0-p247

The real solution is to use nested:
resources :products do
nested do
scope 'monthly', as: :monthly do
get 'revenue', to: 'charts#monthly_revenue'
get 'items_sold', to: 'charts#monthly_items_sold'
end
end
end
Ref: https://github.com/rails/rails/issues/12626

Here's how I might approach:
periods = %w(monthly yearly)
period_sections = %w(revenue items_sold)
resources :products do
periods.each do |period|
period_sections.each do |section|
get "#{period}/#{section}", to: "charts##{period}_#{section}", as: "#{period}_#{section}"
end
end
end
It is also possible to use named routes and pass the values to your controller method via params (be sure to properly validate before using):
resources :products do
get ":period/:section", to: "charts#generate_report", as: :report
end
# report_path(period: 'monthly', section: 'revenue')

Related

Rails multiple routes, same `as`

I have the following routes:
authenticated :user do
scope module: 'admin', path: ':publisher_id' do
get 'settings/general', to: 'publishers#index', as: 'publisher_settings'
post 'settings/general', to: 'publishers#create'
put 'settings/general', to: 'publishers#update'
end
end
However the first as: can only be used once. How can I make this apply to all these routes? I want all of them to work for the publisher_settings_path
You can also pass the as option to namespaces and scopes in Rails Routes.
# prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
scope as: "sekret" do
resources :posts
end
Source: https://api.rubyonrails.org/v5.1/classes/ActionDispatch/Routing/Mapper/Scoping.html
This should also work though i think
Rails.application.routes.draw do
resources :publishers, only: [:create, :update, :index], path: 'settings/general', as: 'publisher_settings'
end
Get's me
publisher_settings GET /settings/general(.:format) publishers#index
POST /settings/general(.:format) publishers#create
publisher_setting PATCH /settings/general/:id(.:format) publishers#update
PUT /settings/general/:id(.:format) publishers#update
publisher_settings_path already returns the path of of the three routes, e.g. /admin/settings/general. So you don't need to set as: multiple times.
And actually setting the same as: multiple times for multiple routes would not make sense, it has to return one value.

Rails routes: Nested, Member, Collection, namespace, scope and customizable

I am trying to understand more about Rails routes.
Member and Collection
# Example resource route with options:
resources :products do
member do
get 'short'
post 'toggle'
end
collection do
get 'sold'
end
end
Namespace and Scope
# Example resource route within a namespace:
namespace :admin do
resources :products
end
scope :admin do
resources :products
end
Constraints, Redirect_to
# Example resource route with options:
get "/questions", to: redirect {|params, req|
begin
id = req.params[:category_id]
cat = Category.find(id)
"/abc/#{cat.slug}"
rescue
"/questions"
end
}
Customization:
resources :profiles
original url from resource profiles for edit.
http://localhost:3000/profiles/1/edit
I want to make it for users available only through click edit profile and see url like in below.
http://localhost:3000/profile/edit
Also, is there advanced routing, How most big companies design their routes in rails ? I would be really glad to see new kind of routes if there exist.
Thank You !
**Collection & Member routes**
A member route requires an ID, because it acts on a member.
A collection route doesn't require an ID because it acts on a
collection of objects
:member creates path with pattern /:controller/:id/:your_method
:collection creates path with the pattern /:controller/:your_method
For example :
map.resources :users, :collection => { :abc => :get } => /users/abc
map.resources :users, :member => { :abc => :get } => /users/1/abc
**Scopes & Namespaces routes**
namespace and scope in the Rails routes affect the controller
names, URIs, and named routes.
The scope method gives you fine-grained control:
scope 'url_path_prefix', module: 'module_prefix', as: 'named_route_prefix' do
resources :model_name
end
For Example :
scope 'foo', module: 'bar', as: 'baz' do
resources :posts
end
produces routes as :
Prefix Verb URI Pattern Controller#Action
baz_posts GET /foo/posts(.:format) bar/posts#index
POST /foo/posts(.:format) bar/posts#create
new_baz_post GET /foo/posts/new(.:format) bar/posts#new
edit_baz_post GET /foo/posts/:id/edit(.:format) bar/posts#edit
baz_post GET /foo/posts/:id(.:format) bar/posts#show
PATCH /foo/posts/:id(.:format) bar/posts#update
PUT /foo/posts/:id(.:format) bar/posts#update
DELETE /foo/posts/:id(.:format) bar/posts#destroy
The namespace method is the simple case — it prefixes everything.
namespace :foo do
resources :posts
end
produces routes as :
Prefix Verb URI Pattern Controller#Action
foo_posts GET /foo/posts(.:format) foo/posts#index
POST /foo/posts(.:format) foo/posts#create
new_foo_post GET /foo/posts/new(.:format) foo/posts#new
edit_foo_post GET /foo/posts/:id/edit(.:format) foo/posts#edit
foo_post GET /foo/posts/:id(.:format) foo/posts#show
PATCH /foo/posts/:id(.:format) foo/posts#update
PUT /foo/posts/:id(.:format) foo/posts#update
DELETE /foo/posts/:id(.:format) foo/posts#destroy
**Constraints & Redirect**
Rails routes are executed sequentially, you can mimic conditional
login in the following manner:
match '/route' => 'controller#action', :constraints => Model.new
match '/route' => 'user#action'
The first line checks whether the conditions of the constraint are met (i.e., if the request is emanating from a Model domain). If the constraint is satisfied, the request is routed to controller#action.
We can add constraints to routes for multiple uses like for ip-matching, params matching, restrict format parameter, request-based restrictions etc as :
- ip-matching
=> resources :model, constraints: { ip: /172\.124\.\d+\.\d+/ }
- filtering id params
=> match 'model/:id', to: 'model#show' ,constraints: { id: /\d+/}, via: :get
- restrict format params
=> match 'model/:id', to: 'model#show' ,constraints: { format: 'json' }, via: :get
- request-based constraints
=> get 'admin/', to: 'admin#show', constraints: { subdomain: 'admin' }
Use a singular resource for it:
resource :profile
and in controller manipulate the profile of current user.
As for complex routes - usually namespaces, nested resources with shallow routes and custom actions are all that is needed.
You can go through this answer which answers you first part of the question.
To answer second part of your question. You can treat "profile" as your singular resource (the singularity of the noun itself represents a singular resource). For a detailed description you can refer to this link.

Remove controller from friendly URL

I have written freindly URLs for the show action of the School Resource but now have
before i had ;
http://webaddress/schools/2
and now i have;
http://webaddress/schools/school_name
However, i want
http://webaddress/school_name
My config routes look like this for the resource;
resources :schools do
collection do
match 'search' => 'schools#search', via: [:get, :post], as: :search
end
end
How can i achieve that? thank you
Add this at the last your routes file:
match ':id' => 'schools#show', via: [:get]
A more conventional way would be to use the path: option in your route resources, like this:
#config/routes.rb
...
resources :schools, path: "", only: :show #-> has to go at end of file!
This will give you the ability to add different methods to this, as well as keeping with Rails conventions :)

How do I rename a resources routes?

How do I rename some of these routes...for example, below i want to use signup_path in my controllers instead of signup_sessions_path...
resources :sessions, only: [] do
collection do
post :signup, :as => :signup
post :login
delete :logout
end
end
Try not to nest the routes under resources :sessions but rather use the to: option like so:
post :signup, to: 'sessions#signup', as: :signup, on: :collection
Not too sure about your collection there but I'm sure you get the gist of it
Update
According to your comment, as of today, I don't know of any way to remove the nested route resource name from the path name of a nested resource route. In other words, whatever is nested is purposely to use the scope of the resource and therefore there are no options to revert that behaviour other than taking it out of the resource's block.

Routes with optional params in Rails

I'm trying to setup a route that looks like this: acme.com/posts/:category/:status. Both :category and :status are optional. I wrote many variations, but none worked:
resources :posts do
match '(/:category)(/:status)', to: 'posts#index', as: 'filter', on: :collection
end
# Category Links
link_to "Questions", filter_posts_path(:questions)
link_to "Suggestions", filter_posts_path(:suggestions)
# Status Links
link_to "Published", filter_posts_path(params[:category], :published)
link_to "Draft", filter_posts_path(params[:category], :draft)
The idea is to be able to 1) filter by category, 2) filter by status, and 3) filter by both category and status if both are available. The current setup has also broken my /posts/new path, always redirecting to posts#index.
I had this variation and it seems working fine:
namespace :admin do
resources :posts, :except => [:show] do
collection do
get "/(:category(/:status))", to: "posts#index", as: "list", :constraints => lambda{|req|
req.env["PATH_INFO"].to_s !~ /new|\d/i
}
end
end
end
= CONTROLLER=admin/posts rake route
list_admin_posts GET /admin/posts(/:category(/:status))(.:format) admin/posts#index
You can use the more RESTful resources :posts (in config/routes.rb) and send the params in the query string.
With that approach, all parameters are optional and you're not limited to using predefined parameters.
Do this works for you?
resources :posts do
collection do
match '/:category(/:status)', to: 'posts#index', as: 'filter'
match '/:status', to: 'posts#index', as: 'filter'
end
end
Hope at least it helps!
You could try something like this:
match '/filter/*category_or_status' => 'posts#index', as: 'filter'
With this you can build your own filter path. Then you could parse params[:category_or_status] in your controller and get the category or status if they are given.

Resources