Search code examples
ruby-on-rails-3routessti

STI routing error


Newbie working on his first rails application after studying Hartl's Rails Tutorial book & video cast.

I'm using a STI model where:

class User < ActiveRecord::Base
class Kid < User
class Parent < User

User has the basic elements: name, email, etc.

The problem I'm having is with routing. I continue to work in determining which model will ultimately work best in this situation (STI or polymorphic). I've started with STI and I think I can get this to work if I can nail down the routing issues.

My problem is my edit is looking for an "update" action in the users controller when I want it to route to kidupdate I've been reading many SO posts about STI routing but I can't seem to figure out why this won't route correctly.

Rspec test. It the error comes from the "click_button"

describe "with valid information" do
  let(:new_first)   { "New First" }
  let(:new_last)    { "New Last" }
  let(:new_email)   { "[email protected]" }
  before do
    fill_in "First Name",               with: new_first
    fill_in "Last Name",                with: new_last
    fill_in "Email",                    with: new_email
    select  "Kid",  from: "Are you a Kid or Parent"
    fill_in "Password",                 with: kid.password
    fill_in "Confirmation",             with: kid.password
    click_button "Save changes"
  end

Rspec error:

     KidPages edit with valid information 
     Failure/Error: click_button "Save changes"
     AbstractController::ActionNotFound:
       The action 'update' could not be found for UsersController
     # (eval):2:in `click_button'
     # ./spec/requests/kids_pages_spec.rb:32:in `block (4 levels) in <top (required)>'

Routes:

       root        /                         static_pages#home
       help        /help(.:format)           static_pages#help
    contact        /contact(.:format)        static_pages#contact
     signup        /signup(.:format)         users#new
     signin        /signin(.:format)         sessions#new
    signout DELETE /signout(.:format)        sessions#destroy
    kidshow        /kids/:id(.:format)       users#kidshow
  kidupate PUT    /kids/:id(.:format)       users#kidupdate
    kidedit        /kids/:id/edit(.:format)  users#kidedit
      users GET    /users(.:format)          users#index
            POST   /users(.:format)          users#create
   new_user GET    /users/new(.:format)      users#new
  edit_user GET    /users/:id/edit(.:format) users#edit
       user GET    /users/:id(.:format)      users#show
            PUT    /users/:id(.:format)      users#update
            DELETE /users/:id(.:format)      users#destroy
   sessions POST   /sessions(.:format)       sessions#create
new_session GET    /sessions/new(.:format)   sessions#new
    session DELETE /sessions/:id(.:format)   sessions#destroy

routes.rb

  root to: 'static_pages#home'
  match '/help',    to: 'static_pages#help'
  match '/contact', to: 'static_pages#contact'
  match '/signup',  to: 'users#new'
  match '/signin',  to: 'sessions#new'
  match '/signout',  to: 'sessions#destroy', via: :delete
  match 'kids/:id', to: 'users#kidshow',  :as => 'kidshow'
  match 'kids/:id', to: 'users#kidupdate', :via => 'put', :as => 'kidupdate'
  match 'kids/:id/edit', to: 'users#kidedit',  :as => 'kidedit'
  resources :users
  resources :sessions, only: [:new, :create, :destroy]

I've been struggling with these concepts and this problem for weeks and I appreciate the help.


Solution

  • You can organize your controller better to avoid this issue altogether. See below

    routes.rb

    resources :kids 
    resources :parents
    

    This would directly give you named path:

    edit_kid_path(kid_id) 
    edit_parent_path(parent_id)
    

    kids_controller.rb

    class KidsController < ApplicationController
      def update
      end
    end
    

    parents_controller.rb

    class ParentsController < ApplicationController
      def update
      end    
    end
    

    If you want to share controller behavior, You can potentially do

    class KidsController < UsersController
    end
    
    class ParentsController < UsersController
    end
    

    and put the common actions in users_controller.rb and override them in the child controllers.

    The decision on what model relationships to use should be independent of how you structure your controllers. Controllers and routing is one area of consideration. Modeling data is entirely separately consideration. And declaring resources are just shortcuts for some matching routes.

    If you are not going to use users#update etc, declaring resources :users is unnecessary as you are not using any of the routes that declaration provides.