Search code examples
ruby-on-railsruby-on-rails-4has-many-throughnested-resourcesnested-routes

Rails 4: ActionController::UrlGenerationError


I have three models:

class User < ActiveRecord::Base
  has_many :administrations
  has_many :calendars, through: :administrations
end

class Calendar < ActiveRecord::Base
  has_many :administrations
  has_many :users, through: :administrations
end

class Administration < ActiveRecord::Base
  belongs_to :user
  belongs_to :calendar
end

And here are my routes:

resources :users do
  resources :administrations
  resources :calendars
end

———————————

UPDATE:

Here is what I get when I run rake routes in Terminal:

Prefix Verb   URI Pattern                                        Controller#Action
    user_administrations GET    /users/:user_id/administrations(.:format)          administrations#index
                         POST   /users/:user_id/administrations(.:format)          administrations#create
 new_user_administration GET    /users/:user_id/administrations/new(.:format)      administrations#new
edit_user_administration GET    /users/:user_id/administrations/:id/edit(.:format) administrations#edit
     user_administration GET    /users/:user_id/administrations/:id(.:format)      administrations#show
                         PATCH  /users/:user_id/administrations/:id(.:format)      administrations#update
                         PUT    /users/:user_id/administrations/:id(.:format)      administrations#update
                         DELETE /users/:user_id/administrations/:id(.:format)      administrations#destroy
          user_calendars GET    /users/:user_id/calendars(.:format)                calendars#index
                         POST   /users/:user_id/calendars(.:format)                calendars#create
       new_user_calendar GET    /users/:user_id/calendars/new(.:format)            calendars#new
      edit_user_calendar GET    /users/:user_id/calendars/:id/edit(.:format)       calendars#edit
           user_calendar GET    /users/:user_id/calendars/:id(.:format)            calendars#show
                         PATCH  /users/:user_id/calendars/:id(.:format)            calendars#update
                         PUT    /users/:user_id/calendars/:id(.:format)            calendars#update
                         DELETE /users/:user_id/calendars/:id(.:format)            calendars#destroy
                   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
                         PATCH  /users/:id(.:format)                               users#update
                         PUT    /users/:id(.:format)                               users#update
                         DELETE /users/:id(.:format)                               users#destroy
                    root GET    /                                                  static_pages#home

———————————

UPDATE 2:

Here is my calendars_controller.rb file:

class CalendarsController < ApplicationController
  before_action :set_calendar, only: [:show, :edit, :update, :destroy]

  # GET /calendars
  # GET /calendars.json
  def index
    @calendars = Calendar.all
  end

  # GET /calendars/1
  # GET /calendars/1.json
  def show
  end

  # GET /calendars/new
  def new
    @user = User.find(params[:user_id])
    @calendar = @user.calendars.new
  end

  # GET /calendars/1/edit
  def edit
    @user = User.find(params[:user_id])
    @calendar = @user.calendar.find(params[:calendar_id])
  end

  # POST /calendars
  # POST /calendars.json
  def create
    @user = User.find(params[:user_id])
    @calendar = @user.calendars.new(calendar_params)

    respond_to do |format|
      if @calendar.save
        format.html { redirect_to user_calendar_path(@user,@calendar), notice: 'Calendar was successfully created.' }
        format.json { render :show, status: :created, location: @calendar }
      else
        format.html { render :new }
        format.json { render json: @calendar.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /calendars/1
  # PATCH/PUT /calendars/1.json
  def update
    respond_to do |format|
      if @calendar.update(calendar_params)
        format.html { redirect_to @calendar, notice: 'Calendar was successfully updated.' }
        format.json { render :show, status: :ok, location: @calendar }
      else
        format.html { render :edit }
        format.json { render json: @calendar.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /calendars/1
  # DELETE /calendars/1.json
  def destroy
    @calendar.destroy
    respond_to do |format|
      format.html { redirect_to calendars_url, notice: 'Calendar was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_calendar
      @calendar = Calendar.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def calendar_params
      params.require(:calendar).permit(:name)
    end
end

———————————

http://localhost:3000/users/1/calendars/new and http://localhost:3000/users/1/calendars/1 work fine.

But, whenever I try to access http://localhost:3000/users/1/calendars, I get the following error:

ActionController::UrlGenerationError in Calendars#index
Showing /Users/TXC/code/rails/calendy/app/views/calendars/index.html.erb where line #17 raised:

No route matches {:action=>"show", :controller=>"calendars", :id=>nil, :user_id=>nil} missing required keys: [:id, :user_id]
Extracted source (around line #17):

      <tr>
        <td><%= calendar.name %></td>
        <td><%= link_to 'Show', user_calendar_path(@user, @calendar) %></td>
        <td><%= link_to 'Edit', edit_user_calendar_path(@user, @calendar) %></td>
        <td><%= link_to 'Destroy', calendar, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>

In particular, I don't understand the missing required keys: [:id, :user_id] part, since I have both @user and @calendar in my user_calendar_path.

Obviously, I am missing something, but I cannot figure out what.

Any idea?


Solution

  • The problem here is, you are hitting http://localhost:3000/users/1/calendars url which maps to your calendars#index action and the correct path for this is: user_calendars_path and it needs only one param user_id as it will take you to the index page for the user and will show all the user calendars from the database.

    But, in your index.html.erb view, you have this:

    <td><%= link_to 'Edit', edit_user_calendar_path(@user, @calendar) %></td>
    

    which you can't have because for the index action in your controller, you did not set the @user and @calendar. You can use this in show.html.erb but not in index.html.erb. So, that's why you are getting this error.

    In short, you can't have this in your index.html.erb:

    <td><%= link_to 'Show', user_calendar_path(@user, @calendar) %></td>
    

    because this is mapped to calendars#show action and the corresponding view should be show.html.erb BUT currently you have it inside index.html.erb!

    To answer your last question:

    In particular, I don't understand the missing required keys: [:id, :user_id] part, since I have both @user and @calendar in my user_calendar_path.

    Yes, you have @user and @calendar defined in your controller BUT in the show method AND user_calendar_path maps to that calendars#show method. But hey! you are calling this from the index.html.erb which corresponds to calendars#index action. That's what you are missing. Hope it's clear now :-)

    Finally, the difference is subtle.

    user_calendars_path is for index view which shows you all the calendars for a particular user. In this case, you have to have a @user set in the index action in your controller.

    user_calendar_path is for show which will take you to a particular calendar of a particular user. In this case, you have to have @user and @calendar set in the show method of your controller.