Mikhah Mikhah - 4 months ago 22
Ruby Question

Missing required keys rails

I'm trying to create a form for task in my app.App has two entities:


  • Project
    belongs_to
    User

  • Task
    belongs_to
    Project



But I get this error in my view when I'm trying to create this (pretty basic) form

No route matches {:action=>"index", :controller=>"tasks"} missing required keys: [:project_id]


Here is a part of my view with this form

<div class="glyphicon glyphicon-plus col-xs-1 left_plus" ></div>
<div class="col-xs-10" >
<%= form_for [@project, @task],url: project_tasks_path do |f| %>
<%= f.input :body,class: 'form-control' %>
<%= f.submit 'Add task', class: 'btn' %>
<% end %>


And here is the project controller:

class ProjectsController < ApplicationController
before_action :load_project, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!


def index
@projects = current_user.projects unless current_user.nil?
@task = Task.find_by(params[:project_id])
end

def show
@task = @project.tasks.build
end

def new
@project = current_user.projects.new
end

def edit
end

def create
@project = current_user.projects.create(project_params)

if @project.save
redirect_to root_path
else
render :new
end
end

def update
if @project.update(project_params)
redirect_to @project
else
render :edit
end
end

def destroy
@project.destroy
redirect_to projects_path
end

private

def load_project
@project = Project.find(params[:id])
end

def project_params
params.require(:project).permit(:name, :user_id)
end

end


And the tasks controller:

class TasksController < ApplicationController
before_action :set_task, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!

def index
@tasks = Task.where(project_id: project_id)
end

def show
project = Project.find_by(id: params[:project_id])
@task = Task.new(project: project)
end

def new
project = Project.find_by(id: params[:project_id])
@task = Task.new(project: project)
end


def edit
project = Project.find_by(id: params[:project_id])
@task = Task.new(project: project)
end

def references
respond_to do |format|
if @task.valid?
format.html { redirect_to root_url }
format.json { render :show, status: :created, location: @task }
else
format.html { render :home_url }
format.json { render json: @task.errors, status: :unprocessable_entity }
end
end
end

def create
@project = Project.find_by(id: params[:project_id])
@task = @project.tasks.create(task_params)

respond_to do |format|
if @task.valid?
format.html { redirect_to root_url }
format.json { render :show, status: :created, location: @task }
else
format.html { render :home_url }
format.json { render json: @task.errors, status: :unprocessable_entity }
end
end
end


def update
respond_to do |format|
if @task.update(task_params)
format.html { redirect_to root_url }
format.json { render :home_url, status: :ok, location: @task }
else
format.html { render :root_url }
format.json { render json: @task.errors, status: :unprocessable_entity }
end
end
end

def edit
end

def destroy
@task.destroy
respond_to do |format|
format.html { redirect_to root_url }
format.json { head :no_content }
end
end

private
def set_task
@task = Task.find(params[:id])
end

def task_params
params.require(:task).permit(:deadline, :body, :project_id)
end
end


The routes file:

devise_for :users, :controllers => { :omniauth_callbacks => "callbacks" }
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
root 'projects#index'

resources :projects do
resources :tasks
end


And my routes:

projects#index
project_tasks GET /projects/:project_id/tasks(.:format) tasks#index
POST /projects/:project_id/tasks(.:format) tasks#create
new_project_task GET /projects/:project_id/tasks/new(.:format) tasks#new
edit_project_task GET /projects/:project_id/tasks/:id/edit(.:format) tasks#edit
project_task GET /projects/:project_id/tasks/:id(.:format) tasks#show
PATCH /projects/:project_id/tasks/:id(.:format) tasks#update
PUT /projects/:project_id/tasks/:id(.:format) tasks#update
DELETE /projects/:project_id/tasks/:id(.:format) tasks#destroy
projects GET /projects(.:format) projects#index
POST /projects(.:format) projects#create
new_project GET /projects/new(.:format) projects#new
edit_project GET /projects/:id/edit(.:format) projects#edit
project GET /projects/:id(.:format) projects#show
PATCH /projects/:id(.:format) projects#update
PUT /projects/:id(.:format) projects#update
DELETE /projects/:id(.:format) projects#destroy


Can somebody please help me to clarify where is the problem and what is the problem?

Answer

The problem is with the url for submitting the form. If you check your rake routes you'd see that all your task routes would be nested under projects, therefore in passing the url option, you should have something like:

   <div class="glyphicon glyphicon-plus col-xs-1 left_plus" ></div>
    <div class="col-xs-10" >
    <%= form_for [@project, @task],url: project_tasks_path(@project) do |f| %>
      <%= f.input :body,class: 'form-control' %>
      <%= f.submit 'Add task', class: 'btn' %>
    <% end %>

or even better, I think you should be able to do that without passing the url option:

    <div class="glyphicon glyphicon-plus col-xs-1 left_plus" ></div>
     <div class="col-xs-10" >
     <%= form_for [@project, @task] do |f| %>
      <%= f.input :body,class: 'form-control' %>
      <%= f.submit 'Add task', class: 'btn' %>
    <% end %>

UPDATE

def index
  project = Project.find(params[:project_id])
  @projects = ProjectsViewPresenter.new(project)
end

# presenters/projects_view_presenter.rb
class ProjectsViewPresenter
  attr_reader :project
  def initialize(project)
    @project = project
  end

  def tasks
   @tasks ||= project.tasks
  end

  def task
   @task ||= tasks.new
  end
end

Your form_for would now be like this:

   <div class="glyphicon glyphicon-plus col-xs-1 left_plus" ></div>
    <div class="col-xs-10" >
    <%= form_for [@project.project, @project.task] do |f| %>
      <%= f.input :body,class: 'form-control' %>
      <%= f.submit 'Add task', class: 'btn' %>
    <% end %>