This is causing me such grief!
Let's say I have a story class.
A normal user can create a story, view their stories and delete a story belonging to them.
An admin user can create a story, view all stories and delete all stories.
I want an admin area in the url structure:
/control_panel/stories # list all stories in the site
/control_panel/stories/new # create a new story
/control_panel/stories/:id # show, edit, update, delete story (via different request methods)
However, I also want users to have these routes:
/stories
/stories/new
/stories/:id
I have no idea how to implement this. Trying to create the routes has been a nightmare. This configuration:
resources :stories
scope '/control_panel' do
resources :stories
end
Is almost there:
stories GET /stories(.:format) stories#index
POST /stories(.:format) stories#create
new_story GET /stories/new(.:format) stories#new
edit_story GET /stories/:id/edit(.:format) stories#edit
story GET /stories/:id(.:format) stories#show
PATCH /stories/:id(.:format) stories#update
PUT /stories/:id(.:format) stories#update
DELETE /stories/:id(.:format) stories#destroy
GET /control_panel/stories(.:format) stories#index
POST /control_panel/stories(.:format) stories#create
GET /control_panel/stories/new(.:format) stories#new
GET /control_panel/stories/:id/edit(.:format) stories#edit
GET /control_panel/stories/:id(.:format) stories#show
PATCH /control_panel/stories/:id(.:format) stories#update
PUT /control_panel/stories/:id(.:format) stories#update
DELETE /control_panel/stories/:id(.:format) stories#destroy
However, where are my named routes for the control_panel routes?! I expected:
GET /control_panel/stories to have the name control_panel_stories (like its corresponding non-namespaced routes /stories),
GET /control_panel/stories/new to have the name new_control_panel_story
GET /control_panel/stories/:id/edit to have the name edit_control_panel_story
and
GET /control_panel/stories/:id to have the name control_panel_story
Instead I have no named routes!
Questions:
I need guidance, so tell me what I should be doing. If any of the following are irrelevant just say!
How do I give my scoped routes names? If giving them names is impossible, how do I access them in the views?
If I use namespace :control_panel instead of scope 'control_panel', I get my named routes:
resources :stories
namespace :control_panel do
resources :stories
end
gives me:
control_panel_stories GET /control_panel/stories(.:format) control_panel/stories#index
POST /control_panel/stories(.:format) control_panel/stories#create
new_control_panel_story GET /control_panel/stories/new(.:format) control_panel/stories#new
edit_control_panel_story GET /control_panel/stories/:id/edit(.:format) control_panel/stories#edit
control_panel_story GET /control_panel/stories/:id(.:format) control_panel/stories#show
PATCH /control_panel/stories/:id(.:format) control_panel/stories#update
PUT /control_panel/stories/:id(.:format) control_panel/stories#update
DELETE /control_panel/stories/:id(.:format) control_panel/stories#destroy
...however, now they don't link to the same controller anymore! Gah! They now link to a stories controller that should be found at app/controllers/control_panel/stories_controller.rb
So does this mean I should be using two controllers to edit one resource? This appears to be where rails is pushing me. Every resource has an 'open' controller and a 'control_panel' controller. Is this the correct way to do it?
I just feel like it's a waste when the 'open' controller's actions are identical to the 'control_panel' controller's actions. However, is the DRY of having one controller offset by the sheer complexity of pushing admins and normal users through one controller? This is a simple example, but I can imagine it could get a lot lot trickier. I hope this is the case :)
So what is the point of scoped routes then? They seem kind of pointless.
Do you think a namespace should be created whenever you're creating an exclusive 'area' in your site? A 'forums' area, a 'users' area, and yes, an 'admin' area? This would help to keep controllers more organised.
about routes sope and namespace, you should have a look at this article
Try with:
scope :path => 'control_panel', :as => 'control_panel' do
For example you want the admin to be able to destroy, but not a regular user:
def destroy
return redirect_to(:back, :notice => 'You need to be admin to delete.') if current_user.regular?
...
end
Related
Is there a way to change the name of the routes that my scaffold created? I made a scaffold for Cars. Currently I have resources :cars in my routes. How can I change the routes such that my url shows http://localhost:3000/transportation instead of http://localhost:3000/cars? I do not need to change the name of the entity in my schema, all I want to change are the routes associated with it. How can I go about this?
Is there no other way to achieve this but to do a get for each? Ex:
get '/transportation', to: 'cars#index', as: 'cars_index'
You can define the new route after the resources created by your scaffold to respond to your cars controller and index action, or any other other, depending on what you want to achieve.
resources :cars
get 'transportation', to: 'cars#index'
If you want to apply it for all your routes on the car scaffold, then you can pass a path option:
resources :cars, path: 'transportations'
This way the routes pointing to car won't be available and will be replaced for transportations.
You can redefine resource routes with custom URLs by passing a string of your choice along with :path option along with its route definition in routes.rb
resources :cars, :path => "transportation"
With this route definition, access to cars resources in your app will be routed to these URLs
cars GET /transportation(.:format) cars#index
POST /transportation(.:format) cars#create
new_car GET /transportation/new(.:format) cars#new
edit_car GET /transportation/:id/edit(.:format) cars#edit
car GET /transportation/:id(.:format) cars#show
PATCH /transportation/:id(.:format) cars#update
PUT /transportation/:id(.:format) cars#update
DELETE /transportation/:id(.:format) cars#destroy
I'm developing rails application and encountered such problem.
I have movies_controller.rb, where I have these actions and routes defined:
Prefix Verb URI Pattern Controller#Action
movies GET /movies(.:format) movies#index
POST /movies(.:format) movies#create
new_movie GET /movies/new(.:format) movies#new
edit_movie GET /movies/:id/edit(.:format) movies#edit
movie GET /movies/:id(.:format) movies#show
PATCH /movies/:id(.:format) movies#update
PUT /movies/:id(.:format) movies#update
DELETE /movies/:id(.:format) movies#destroy
root GET / redirect(301, /movies)
movies_by_director GET /movies/by_director(.:format) movies#by_director
But when I try to go to /movies/by_director?director="something", rails think, that I'm navigating to movies#show action with parameter :id = by_director.
What am I doing wrong?
Routes are matched in the order they are specified so make sure the route for "by_director" is defined above the resource routes for movies.
Something like this should do the trick:
get '/movies/by_director' => 'movies#by_director'
resources :movies
There are two problems in one here:
The default pattern matching for :id is loose enough that by_director is interpreted as an :id.
Routes are matched in order and GET /movies/:id appears before GET
/movies/by_director.
You can manually define GET /movies/by_director before your resources :movie as infused suggests or you could add a constraint to narrow what :ids look like:
resources :movies, constraints: { id: /\d+/ } do
#...
end
Manually ordering the routes is fine if there's just one or two of them to deal with, constraining :id is (IMO) cleaner and less error prone.
I have a client controller and views which already functions for all the default actions. How do I add a new_account and create_account actions that also work with routing?
The idea is... there are new clients, but some clients can also function as an account that logs into the website. I want to prompt for different fields based on whether a client is being created or a login account is being created. I don't want two separate models with duplicate information.
client_controller.rb - index, new, create, edit, update, destroy, new_account, create_account
client views - I have views for each of the actions within the controller.
Routing - ../new_account should display the new_account view for the client model, not the new view.
Hopefully this makes sense. I'm guessing this isn't difficult but I'm just missing how.
Try:
match 'new_account', 'client#new_account', :via => :get
match 'create_account', 'client#create_account', :via => :post
View all your routes by running rake routes.
Refer to more info on routing here: http://guides.rubyonrails.org/routing.html
Put this in your routes.rb
resources :clients do
new do
scope type: 'account' do
get :account, to: 'clients#new'
post :account, to: 'clients#create'
end
end
end
and the actions you need will be accessible at GET|POST /clients/new/account.
In the controller you'll have params[:type] to indicate this specific case.
When dealing with a collection resource, I like to use the plural for the index (ie. list) page (viewing many objects), and singular for the other pages (create/update/delete one object).
In order to do so, I seem to have to create my routes like so:
map.objects 'objects.:format', :controller => :object, :action => :index, :conditions => { :method => :get }
map.resources :object, :controller => :object, :except => :index
This creates routes like so:
objects GET /objects(.:format) {:action=>"index", :controller=>"object"}
object_index POST /object(.:format) {:action=>"create", :controller=>"object"}
new_object GET /object/new(.:format) {:action=>"new", :controller=>"object"}
edit_object GET /object/:id/edit(.:format) {:action=>"edit", :controller=>"object"}
object GET /object/:id(.:format) {:action=>"show", :controller=>"object"}
PUT /object/:id(.:format) {:action=>"update", :controller=>"object"}
DELETE /object/:id(.:format) {:action=>"destroy", :controller=>"object"}
It works, but it seems like I'm using an extra line in my routes file (to explicitly specify the index route) when I shouldn't have to. Is there a way to do what I want in one route? Or, alternately, is there a reason not to route this way?
The only reason other than "normal REST says don't" to not have the "object" be a resource under "objects" is search engines.
Google will notice that you have "recipes" and then recipes under "recipes", and give you those cool sitelinks:
Google's Webmaster Guidelines say, in
the first item under design and
content guidelines, "Make a site with
a clear hierarchy and text links."
RESTful routing is designed in such a way that you're scoping down what it is you want to do. Say you go to http://example.com/objects. Here, you're telling the site you want a list objects.
Now when you go to http://example.com/objects/2 you're telling it you want to see the object with identifier of 2 in that list (or resource) of objects.
Finally, when you go to http://example.com/objects/2/edit you're saying you want to find the object again with identifier of 2 but this time you would like to edit it rather than view it.
By going against the grain like you have suggested in routing helpers you will be causing a tremendous amount of unnecessary pain for yourself and for anybody else reading your code.
However if you do choose to go this path (again, I advise against it) then yes, defining two routes is the only way.
I have a Rails app that has a controller called domain which has a nested controller called subdomain and stats. I have defined them in routes.rb:
resources :domains do
resources :subdomains, :stats
end
I have changed the to_param of the domain and subdomain models to use the name of the domain, e.g.: the routing I get is http://site/domains/foo/subdomains/bar.
I would like to tidy it up to so that instead of using http://site/domains/foo/subdomains/bar I could access it with just http://site/foo/subdomains/bar. I have tried the following in routes.rb:
match "/:id/" => "domains#show", :as => :domain
Which works fine, but it only gives me the ability to use the path http://site/foo but for example http://site/foo/subdomains/bar doesn't. I could create match lines for every respective model and nested model but that does nothing to other helpers besides domain_url - i.e. edit_domain_url points to /domains/foo/edit/ instead of /foo/edit.
Is there a way to change the routing so that the resources generates helpers that point to the root url without the 'domains' part?
The single match in your routes creates only one route. Resource helpers create many routes at once. Luckily there are a lot of options for customisation. If you want to omit /domains/ from your paths, it's as simple as:
resources :domains, :path => "/" do
resources :subdomains, :stats
end
With the above in config/routes.rb, running rake routes says the following:
domain_subdomains GET /:domain_id/subdomains(.:format)
domain_subdomains POST /:domain_id/subdomains(.:format)
new_domain_subdomain GET /:domain_id/subdomains/new(.:format)
edit_domain_subdomain GET /:domain_id/subdomains/:id/edit(.:format)
domain_subdomain GET /:domain_id/subdomains/:id(.:format)
domain_subdomain PUT /:domain_id/subdomains/:id(.:format)
domain_subdomain DELETE /:domain_id/subdomains/:id(.:format)
domain_stats GET /:domain_id/stats(.:format)
domain_stats POST /:domain_id/stats(.:format)
new_domain_stat GET /:domain_id/stats/new(.:format)
edit_domain_stat GET /:domain_id/stats/:id/edit(.:format)
domain_stat GET /:domain_id/stats/:id(.:format)
domain_stat PUT /:domain_id/stats/:id(.:format)
domain_stat DELETE /:domain_id/stats/:id(.:format)
domains GET /(.:format)
domains POST /(.:format)
new_domain GET /new(.:format)
edit_domain GET /:id/edit(.:format)
domain GET /:id(.:format)
domain PUT /:id(.:format)
domain DELETE /:id(.:format)
Looks like all the routes you need!