Rails: how to format and organize routes - ruby-on-rails

I am a rails newbie (building my first app) and right now my routes.rb is quite a mess. I was wondering what is the best way to organize/format all the content so it is easy to see what is going on and avoid silly routing errors.
Any general tips or simplified examples would be appreciated.
Routes.rb
Rails.application.routes.draw do
resources :posts
get 'users/index'
#devise_for :admins
namespace :super_admin do #superadmin stuff
resources :dashboard, only: [:index]
end
devise_for :super_admins, path: "super_admin", controllers: { registrations: "registrations", sessions: "super_admin/sessions" } #lets super admin sign in
get 'welcome/index'
root to: "welcome#index"
match '/teachers', to: 'teachers#index', via: 'get'
#route to delete users
match 'users/:id' => 'users#destroy', :via => :delete, :as => :admin_destroy_user
match '/users/:id', to: 'users#show', via: 'get'
#routes for registration
devise_for :users, controllers: { registrations: "registrations" }
devise_for :teachers, controllers: { registrations: "teacher/registrations" }
get 'users/:id/posts' => 'users#posts', :as => :user_posts
match '/users', to: 'users#index', via: 'get'
match '/about', to: 'about#index', via: 'get'
match '/teachers/:id', to: 'teachers#show', via: 'get'
match '/teachers/list', to: 'teachers#list', via: 'get'
get 'super_admin/dashboard/new_user', :as => :super_admin_new_user
resources :users, :only =>[:show]

Unfortunately it's simply part of rails that this file gets messy over time. Our app has hundreds of entries for various items that have been added over the years, so I know from experience it's good to think of from the beginning.
The number one thing you can do to keep the file organized is to add lots of comments, with some kind of consistency that helps you understand how they match your app, for example:
# ADMIN FUNCTIONALITY
# -- Allows super admin access and functionality
# your admin stuff here
And then keep your routes for certain functionality in the same section. In your example, you have a "teachers" route near the top, and then some more near the bottom. Keep those grouped together and commented and it'll be easier to manage in the long run.

Related

Does the order of the routes effect which controller is accessed?

In my app I have grants and I want the url to be root/grant_id instead of root/grants/grant_id. I have this in my routes
Rails.application.routes.draw do
...
root 'static_pages#welcome'
# get 'home' => 'static_pages#home'
get 'about' => 'static_pages#about'
get 'faq' => 'static_pages#faq'
get 'signup' => 'users#new'
get 'login' => 'sessions#new'
post 'login' => 'sessions#create'
delete 'logout' => 'sessions#destroy'
get 'dashboard' => 'dashboard#index'
resources :users do
resources :projects
member do
get 'access_granted'
put 'access_granted'
get 'remove_access'
put 'remove_access'
end
end
resources :profiles
resources :account_activations, only: [:edit]
resources :password_resets, only: [:new, :create, :edit, :update]
resource :request_access, only: [:show, :new, :create]
resources :grants, :path => '' do
resources :app_types do
resources :submissions
end
end
get 'grants' => 'grants#index'
resources :matches
end
When I put resources :matches below the resources :grants, :path => '' do line I get the error "Couldn't find Grant" and I see that request parameters are
{"controller"=>"grants", "action"=>"show", "id"=>"matches"}. When I put resources :matches above the the grant line everything works fine. Its almost like something in the grant route isn't closing and is forcing any lines below it to look for the grant controller. A simple solution is just keeping everything above that line but I'm trying to understand why this is happening.
I also noticed that even though I define the grant#index as grants, when I rake routes I see:
grants GET / grants#index
GET /grants(.:format) grants#index
So two questions
1. Is :path => '' the correct way to remove the grants/ part of the url.
2. Why is everything below the grants route getting sent to the grants controller?
From the documentation :
Rails routes are matched in the order they are specified, so if you have a resources :photos above a get 'photos/poll' the show action's route for the resources line will be matched before the get line. To fix this, move the get line above the resources line so that it is matched first.
So, the problem you're having is that matching grants to "" means your grants INDEX route is /, and your grants SHOW route is /:grant_id, which will match any route. If you want to have this kind of route (which I would advise against), it has got to be at the bottom of the routes file.
You can read more about routing here: http://guides.rubyonrails.org/routing.html

Reduce complexity of routes

I am attempting to setup my routes.rb so that /sessions/ is not required in the url for logging in and out of the site. Below are my samples to show what I am trying to achieve. Whilst the "second attempt" does in fact do what I want, I'd like to know if there is a more efficient way of doing this. I am very new to rails and I am sure that the routes.rb has some option that can do what I am doing in three large lines.
First attempt
routes.rb
namespace :account do
resources :users
resources :sessions
end
$ rake routes
Prefix Verb URI Pattern Controller#Action
account_users GET /account/users(.:format) account/users#index
...
account_sessions GET /account/sessions(.:format) account/sessions#index
POST /account/sessions(.:format) account/sessions#create
new_account_session GET /account/sessions/new(.:format) account/sessions#new
edit_account_session GET /account/sessions/:id/edit(.:format) account/sessions#edit
account_session GET /account/sessions/:id(.:format) account/sessions#show
PATCH /account/sessions/:id(.:format) account/sessions#update
PUT /account/sessions/:id(.:format) account/sessions#update
DELETE /account/sessions/:id(.:format) account/sessions#destroy
Second attempt
routes.rb
namespace :account do
resources :users
match '/login', :controller => 'sessions', :action => 'new', :via => [:get]
match '/login', :controller => 'sessions', :action => 'create', :via => [:post]
match '/logout', :controller => 'sessions', :action => 'destroy', :via => [:delete]
end
$ rake routes
Prefix Verb URI Pattern Controller#Action
account_users GET /account/users(.:format) account/users#index
...
account_login GET /account/login(.:format) account/sessions#new
POST /account/login(.:format) account/sessions#create
account_logout DELETE /account/logout(.:format) account/sessions#destroy
Can this be done without having to manually specific the match locations? All I want to do is remove /sessions/ as a requirement.
namespace :account do
resources :users #-> account/users
resources :sessions, path: "", path_names: { new: "login", create: "login", destroy: "logout" } #-> accounts/login, accounts/logout
end
I hope you realise you have /login twice in your second example. This simplifies it a bit but you will always have to match each route you want to specify outside any defaults.
namespace :account do
match '/login', to: 'sessions#new', via: [:get]
match '/logout', to: 'sessions#destroy', via: [:delete]
end
In rails3 we should use with_options in following way:
scope '/account' do
match '/login' => "sessions#new", :as => :login
post '/:login' => 'sessions#create', :as => :signup_create
delete '/:logout' => 'sessions#destroy', :as => :logout
end

Deploy static pages to domain root and rails application to subdomain

I'm following Michael Hartl's Rails Tutorial and deploying to Heroku.
I have static pages that are public to every web visitor and dynamic and "protected" pages that require the user to sign in in order to view them. Currently all pages are deployed to the website's root: example.com/static-page and example.com/users/1/
My objective:
deploy static pages to the root, like example.com/static-page
deploy rails' pages to a subdomain, like app.example.com/users/1
I assume the solution involves changing the routes file. Is there any tutorial or video explaining how to do so? I'm a newbie on Rails.
My routes file:
Dcid::Application.routes.draw do
resources :users
resources :sessions, only: [:new, :create, :destroy]
root 'static_pages#home'
match '/home', to: 'static_pages#home', via: 'get'
match '/about', to: 'static_pages#about', via: 'get'
match '/signup', to: 'users#new', via: 'get'
match '/signin', to: 'sessions#new', via: 'get'
match '/signout', to: 'sessions#destroy', via: 'delete'
You could either have a controller serving your pages or simply put your HTML files in public an treat them as assets.
In either case if they are really static, you might want to cache heavily or put a CDN in front of everything.
You'll want something like this:
#config/routes.rb
root 'static_pages#home'
#Subdomain
constraints subdomain: 'app' do
resources :users
end
#Pages
pages = %w(home about)
for page in pages do
get "/#{page}", to: "static_pages##{page}"
end
#Resources
resources :users do
get :new, as: :collection
end
resources :sessions, only: [:new, :create, :destroy] do
get :signin, action: :new, as: :collection
delete :signout, to: :destroy, as: :collection
end
This will create the routes you need. However, you won't be able to use a subdomain on Heroku, unless you use a custom domain

Abstracting rails route

I want to replace the normal /users/:id route that is created by the resources command, with a more abstract /profile route. It won't be possible to view other users profiles in my app, and therefor the current route is unnecessary specific.
I have tried to overwrite the route created by 'resources :users' with:
get '/profile', to: 'users#show'
and other variances and combinations, but can't seem to get it right. Either the controller can't find the user because of a missing id or it simply can't find the route.
Thanks for the help!
You can use this code in routes.rb file:
resources :users, :except => :show
collection do
get 'profile', :action => 'show'
end
end
It will generate url "/users/profile".
But, if u want to use only '/profile', then don't create route as collection inside users resources block.
resources :users, :except => :show
get 'profile' => "users#show", :as => :user_profile
It will redirect '/profile' to show action in users controller.
I suggest simply adding a users/me route pointing to the show action of your UsersController like so:
resources :users, only: [] do
collection do
get 'me', action: :show
end
end
You can also use the match keyword in routes.rb file.
match 'users/:id' => 'users#show', as: :user_profile, via: :get

Why rails app is redirecting unexpectedly instead of matching the route?

I asked this question earlier and thought it was fixed, but it's not. Previous question here
My problem is I am trying to set my routes so that when I type in
localhost:3000/sites/admin
It should redirect to
localhost:3000/en/sites/admin
here is my routes.rb file
scope ":locale", locale: /#{I18n.available_locales.join("|")}/ do
get "log_out" => "sessions#destroy", as: "log_out"
get "log_in" => "sessions#new", as: "log_in"
resources :sites, except: [:new, :edit, :index, :show, :update, :destroy, :create] do
collection do
get :home
get :about_us
get :faq
get :discounts
get :services
get :contact_us
get :admin
get :posts
end
end
resources :users
resources :abouts
resources :sessions
resources :coupons
resources :monthly_posts
resources :reviews
resources :categories do
collection { post :sort }
resources :children, :controller => :categories, :only => [:index, :new, :create, :new_subcategory]
end
resources :products do
member do
put :move_up
put :move_down
end
end
resources :faqs do
collection { post :sort }
end
root :to => 'sites#home'
match "/savesort" => 'sites#savesort'
end
match '', to: redirect("/#{I18n.default_locale}")
match '*path', to: redirect("/#{I18n.default_locale}/%{path}")
But as of right now, it redirects to /en/en/en/en/en/en/en/en/en/en/sites/admin (adds en until browser complains).
Any thoughts why it keeps adding /en?
Edit:
The answer is great, thanks. Can you help me diagnose the root route?
root to: redirect("#{/#{I18n.default_locale}") # handles /
I know redirects is looking for something like
redirect("www.example.com")
So that leaves this part
#{/#{I18n.default_locale}
The #{ is using rubys string interpolation, right? i'm not sure what that { is doing though.
So then we have
/#{I18n.default_locale}
Which is also using string interpolation and to print out the value of I18n.default_locale?
Hopefully that makes sense, I really really appreciate the help, I am learning a lot.
Edit 2:
I changed the line from
root to: redirect("#{/#{I18n.default_locale}") # handles /
to
root to: redirect("/#{I18n.default_locale}") # handles /
But i'm not sure if thats right. Now i'm getting the error
uninitialized constant LocaleController
I know it's getting the error from the root to: "locale#root", but i thought the locale# would come from the scope.
I'll continue playing with it and let you know any progress.
Here is a new link to my routes file https://gist.github.com/2332198
We meet again, ruevaughn. :)
I created a test rails app and the following minimal example works for me:
scope ":locale", locale: /#{I18n.available_locales.join("|")}/ do
resources :sites do
collection do
get :admin
end
end
root to: "locale#root" # handles /en/
match "*path", to: "locale#not_found" # handles /en/fake/path/whatever
end
root to: redirect("/#{I18n.default_locale}") # handles /
match '*path', to: redirect("/#{I18n.default_locale}/%{path}") # handles /not-a-locale/anything
When using Rails 4.0.x that %{path} in the redirect will escape slashes in the path, so you will get an infinite loop redirecting to /en/en%2Fen%2Fen%2Fen...
Just in case someone, like me, is looking for a Rails-4-suitable solution, this is what I found to be working without problems, even with more complex paths to be redirected:
# Redirect root to /:locale if needed
root to: redirect("/#{I18n.locale}", status: 301)
# Match missing locale paths to /:locale/path
# handling additional formats and/or get params
match '*path', to: (redirect(status: 307) do |params,request|
sub_params = request.params.except :path
if sub_params.size > 0
format = sub_params[:format]
sub_params.except! :format
if format.present?
"/#{I18n.locale}/#{params[:path]}.#{format}?#{sub_params.to_query}"
else
"/#{I18n.locale}/#{params[:path]}?#{sub_params.to_query}"
end
else
"/#{I18n.locale}/#{params[:path]}"
end
end), via: :all
# Redirect to custom root if all previous matches fail
match '', to: redirect("/#{I18n.locale}", status: 301), via: :all

Resources