Tuesday, July 12, 2011

Make Rails Devise Routes Look Better

AKA remap the routes in Rails Devise Authentication

Anyone who explores Devise in their rails app will find that all the generated routes fall under a single path. Since most people choose the User model for saving user information, devise will put all these methods under /users/

rake routes
        new_user_session GET    /users/sign_in(.:format)       {:action=>"new", :controller=>"devise/sessions"}
            user_session POST   /users/sign_in(.:format)       {:action=>"create", :controller=>"devise/sessions"}
    destroy_user_session DELETE /users/sign_out(.:format)      {:action=>"destroy", :controller=>"devise/sessions"}
           user_password POST   /users/password(.:format)      {:action=>"create", :controller=>"devise/passwords"}
       new_user_password GET    /users/password/new(.:format)  {:action=>"new", :controller=>"devise/passwords"}
      edit_user_password GET    /users/password/edit(.:format) {:action=>"edit", :controller=>"devise/passwords"}
                         PUT    /users/password(.:format)      {:action=>"update", :controller=>"devise/passwords"}
cancel_user_registration GET    /users/cancel(.:format)        {:action=>"cancel", :controller=>"devise/registrations"}
       user_registration POST   /users(.:format)               {:action=>"create", :controller=>"devise/registrations"}
   new_user_registration GET    /users/sign_up(.:format)       {:action=>"new", :controller=>"devise/registrations"}
  edit_user_registration GET    /users/edit(.:format)          {:action=>"edit", :controller=>"devise/registrations"}
                         PUT    /users(.:format)               {:action=>"update", :controller=>"devise/registrations"}
                         DELETE /users(.:format)               {:action=>"destroy", :controller=>"devise/registrations"}
             user_unlock POST   /users/unlock(.:format)        {:action=>"create", :controller=>"devise/unlocks"}
         new_user_unlock GET    /users/unlock/new(.:format)    {:action=>"new", :controller=>"devise/unlocks"}
                         GET    /users/unlock(.:format)        {:action=>"show", :controller=>"devise/unlocks"}

Looking at this, I wanted to customize some of the routes so they are located elsewhere in my application. eg. Move the /users/sign_in to /login and /users/sign_up to /signup

Devise and rails routes offers a few methods of doing this which are mentioned on the Devise wiki pages, but the problem I ran into was how the registration controller was mapping over top of the users controller routes.

user_registration POST   /users(.:format)               {:action=>"create", :controller=>"devise/registrations"}
   new_user_registration GET    /users/sign_up(.:format)       {:action=>"new", :controller=>"devise/registrations"}
  edit_user_registration GET    /users/edit(.:format)          {:action=>"edit", :controller=>"devise/registrations"}
                         PUT    /users(.:format)               {:action=>"update", :controller=>"devise/registrations"}
                         DELETE /users(.:format)               {:action=>"destroy", :controller=>"devise/registrations"}
             user_unlock POST   /users/unlock(.:format)        {:action=>"create", :controller=>"devise/unlocks"}

I would like new_user_registration to point to /signup, and user_registration to /signup too. This is cause when the form is submitted, and an error occurs we want the use to remain on the /signup URL. After some help from this post on google groups:

http://groups.google.com/group/plataformatec-devise/browse_thread/thread/cfa98fd217d558e6

I ended up with these devise routes: /login, /logout, and /signup and it puts some of the registration routes under /register, thereby leaving the user actions for my users controller and not for devise. Pretty now.

devise_for :user, :path => '', :path_names => { :sign_in => 'login', :sign_out => 'logout'}, :skip => [:registration] do
    scope :controller => 'devise/registrations' do      
      get :cancel, :path => 'users/cancel', :as => :cancel_user_registration
      post :create,  :path => 'signup', :as => :user_registration
      get  :new,     :path => 'signup' , :as => :new_user_registration
      get :edit,    :path => 'users/edit', :as => :edit_user_registration
      put :update, :path => 'users/edit', :as => :update_user_registration
      delete :destroy, :path => 'users'
    end
  end 
 
 
rake routes
cancel_user_registration GET    /users/cancel(.:format)   {:action=>"cancel", :controller=>"devise/registrations"}
       user_registration POST   /signup(.:format)         {:action=>"create", :controller=>"devise/registrations"}
   new_user_registration GET    /signup(.:format)         {:action=>"new", :controller=>"devise/registrations"}
  edit_user_registration GET    /users/edit(.:format)     {:action=>"edit", :controller=>"devise/registrations"}
update_user_registration PUT    /users/edit(.:format)     {:action=>"update", :controller=>"devise/registrations"}
                 destroy DELETE /users(.:format)          {:action=>"destroy", :controller=>"devise/registrations"}
        new_user_session GET    /login(.:format)          {:action=>"new", :controller=>"devise/sessions"}
            user_session POST   /login(.:format)          {:action=>"create", :controller=>"devise/sessions"}
    destroy_user_session DELETE /logout(.:format)         {:action=>"destroy", :controller=>"devise/sessions"}
           user_password POST   /password(.:format)       {:action=>"create", :controller=>"devise/passwords"}
       new_user_password GET    /password/new(.:format)   {:action=>"new", :controller=>"devise/passwords"}
      edit_user_password GET    /password/edit(.:format)  {:action=>"edit", :controller=>"devise/passwords"}
                         PUT    /password(.:format)       {:action=>"update", :controller=>"devise/passwords"}
             user_unlock POST   /unlock(.:format)         {:action=>"create", :controller=>"devise/unlocks"}
         new_user_unlock GET    /unlock/new(.:format)     {:action=>"new", :controller=>"devise/unlocks"}
                         GET    /unlock(.:format)         {:action=>"show", :controller=>"devise/unlocks"}  


Edit: A quirk with devise is that the update_user_registration uses the same action="{URL}" as user_registration no matter what is defined in the routes. This causes the action for update_user_registration to send the put to /signup when we want it to go to /user/edit. The solution is to edit the registration/edit.html.erb and change
:url => registration_path(resource_name)
to
:url => :update_user_registration

.

No comments: