Rails 4 nested link_to edit forms not working in a loop

I have nested routes / models / shapes in rails. In my index page, I list todo_lists with todo_items underneath. I want to be able to click on the title of the to-do list and then it takes me to the edit page. I am researching polymorphic routes and nested routes.

UPDATE

This was my fix to stop it from creating a numbered todo list.

<%  @current_todo_lists.each do |list| %>
    <% if list.id %>
        <div class="panel">
            <p><strong><%= link_to list.title ,edit_todo_list_path(list)%></strong></p>
            <% list.todo_items.each do |todo_item| %>
                <p><%= todo_item.description %></p>
            <% end %>
        </div>
    <% end %>
<% end %>

      

github link

Link to edit nested rail shapes

polyorphic_path doesn't create the right path I've done quite a bit of research on cocoon, polymorphic path guides and a few other stackoverflow channels.

I was unable to complete any of these works.

Here is the index page that lists all of my todo_lists with todo_items. It goes through a loop to list each todo list with the corresponding items created with it.

Update:

I've already tried <%= link_to list.title, edit_todo_list_path(list) %>

and <%= link_to list.title, edit_todo_list_path(@list) %>

. The error message I am getting:

ActionController::UrlGenerationError at /todo_lists No route matches {:action=>"edit", :controller=>"todo_lists", :id=>nil} missing required keys: [:id]

This same configuration with @todo_list gives the same error. Basically it cannot find the Todo List with an ID.

In the console, this gives me the output. So I am missing something.

>> t = TodoList.find(1)
=> #<TodoList id: 1, title: "First todo List with a modal", created_at: "2014-09-09 23:02:27", updated_at: "2014-09-09 23:02:27", user_id: 1>
>>

      

Update 2: This is where the error occurs in my to-do list controller. He cannot find no id.

def set_todo_list
@todo_list = TodoList.find(params[:id])
end


<% @todo_lists.each do |list| %> 
    <p><strong><%= link_to list.title, edit_polymorphic_path(@todo_list) %></strong></p>
    <% list.todo_items.each do |todo_item| %>
        <p><%= todo_item.description %></p>
    <% end %>
<% end %>

      

So far, the parameters <p><strong><%= link_to list.title, edit_polymorphic_path(@todo_list) %

have been @todo_list (s), @todo_lists (s), todo_items, etc.

Models:

class TodoList < ActiveRecord::Base
    has_many :todo_items, dependent: :destroy
    accepts_nested_attributes_for :todo_items, allow_destroy: true
    validates_presence_of :title
end

class TodoItem < ActiveRecord::Base
    belongs_to :todo_list
end

      

Controllers:

Class TodoListsController < ApplicationController
  before_filter :authenticate_user!
  before_filter except: [:index]
  before_action :set_todo_list, only: [:show, :edit, :update, :destroy]

  # GET /todo_lists
  # GET /todo_lists.json
  def index
    #@todo_lists = TodoList.all
    #find current user todo lists/items
    @todo_lists = current_user.todo_lists
    @todo_items = current_user.todo_items
    #create a new user todo list
    @todo_list = current_user.todo_lists.new
    # builder for todo list _form
    3.times{ @todo_list.todo_items.build }
  end

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

  # # GET /todo_lists/new
  def new
    @todo_list = current_user.todo_lists.new
    3.times{ @todo_list.todo_items.build }
  end

  # GET /todo_lists/1/edit
  def edit
    #@todo_list = TodoList.find(todo_list_params)
    @todo_list = TodoList.find(params[:id])
  end

  # POST /todo_lists
  # POST /todo_lists.json
  def create
    #@todo_list = TodoList.new(todo_list_params)
    @todo_list = current_user.todo_lists.new(todo_list_params)
    respond_to do |format|
      if @todo_list.save
        format.html { redirect_to @todo_list, notice: 'Todo list was successfully created.' }
        format.json { render :show, status: :created, location: @todo_list }
      else
        format.html { render :new }
        format.json { render json: @todo_list.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /todo_lists/1
  # PATCH/PUT /todo_lists/1.json
  def update
    @todo_list = TodoList.find(params[:id])
    respond_to do |format|
      if @todo_list.update(todo_list_params)
        format.html { redirect_to @todo_list, notice: 'Todo list was successfully updated.' }
        format.json { render :show, status: :ok, location: @todo_list }
      else
        format.html { render :edit }
        format.json { render json: @todo_list.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /todo_lists/1
  # DELETE /todo_lists/1.json
  def destroy
    #@todo_list.TodoList.find(params[:id])
    @todo_list.destroy
    respond_to do |format|
      format.html { redirect_to todo_lists_url, notice: 'Todo list was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    def owns_todolist
      if current_user != TodoList.find(params[:id]).user
        redirect_to todo_lists_path, error: "You can't do that!"
      end
    end
    # Use callbacks to share common setup or constraints between actions.
    def set_todo_list
      @todo_list = TodoList.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def todo_list_params
      params.require(:todo_list).permit(:title, todo_items_attributes: [:description, :_destroy])
    end
end



class TodoItemsController < ApplicationController
  before_action :set_todo_item, only: [:show, :edit, :update, :destroy]
  before_action :set_todo_list

  # GET /todo_items
  # GET /todo_items.json
  def index
    @todo_items = TodoItem.all
  end

  # GET /todo_items/1
  # GET /todo_items/1.json
  def show
    @todo_item = TodoItem.find(params[:id])
  end

  # GET /todo_items/new
  def new
    @todo_item = @todo_list.todo_items.build
  end

  # GET /todo_items/1/edit
  def edit
    @todo_item = TodoItem.find(params[:id])
  end

  # POST /todo_items
  # POST /todo_items.json
  def create
    @todo_item = @todo_list.todo_items.build(todo_item_params)

    respond_to do |format|
      if @todo_item.save
        format.html { redirect_to [@todo_list,@todo_item], notice: 'Todo item was successfully created.' }
        format.json { render :show, status: :created, location: @todo_item }
      else
        format.html { render :new }
        format.json { render json: @todo_item.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /todo_items/1
  # PATCH/PUT /todo_items/1.json
  def update
    @todo_item = TodoItem.find(params[:id])
    respond_to do |format|
      if @todo_item.update(todo_item_params)
        format.html { redirect_to @todo_item, notice: 'Todo item was successfully updated.' }
        format.json { render :show, status: :ok, location: @todo_item }
      else
        format.html { render :edit }
        format.json { render json: @todo_item.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /todo_items/1
  # DELETE /todo_items/1.json
  def destroy
    @todo_item = TodoItem.find(params[:id])
    @todo_item.destroy
    respond_to do |format|
      format.html { redirect_to todo_list_todo_items_url, notice: 'Todo item was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

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

    def set_todo_list
      @todo_list = TodoList.find(params[:todo_list_id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def todo_item_params
      params.require(:todo_item).permit(:description, :text, :todo_list_id)
    end
end

      

and finally the form. It now allows you to add todo_list and multiple todo_items just like a practice. I am planning on using some Ajax to enable dynamic creation afterwards. And have a different form for editing.

<%= form_for(@todo_list) do |f| %>
  <% if @todo_list.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@todo_list.errors.count, "error") %> prohibited this todo_list from being saved:</h2>

      <ul>
      <% @todo_list.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
  <div class="field">
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </div>
  <div>
    <%= f.fields_for :todo_items do |builder| %>
      <%= builder.label :description, "Items" %>
      <%= builder.text_field :description %>
      <%= builder.check_box '_destroy' %>
    <% end %>

  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

      

+3


source to share


2 answers


I think the problem is actually a little more complicated because it edit_todo_list_path(list)

seems to be throwing the same error.

What happens when a variable @todo_lists

(first an array of saved lists) changes at startup @todo_list = current_user.todo_lists.new

. This command actually adds a new (unsafe) list to the end of the array @todo_lists

(seems like wrong behavior to me, but this has happened to me before) so that when your view iterates over them, the latter has no id and no path can be created for it.



The solution (I think) is to make this variable after you have used the variable @todo_lists

.

Take out @todo_list

from the controller, and where you use it in the view, instead current_user.todo_lists.build

. This should create a new list without changing the variable @todo_lists

.

+2


source


You are trying to link to a resource that has not yet been saved:

#create a new user todo list
@todo_list = current_user.todo_lists.new

      

Since you never call @todo_list.save

, the entry has no ID and cannot be redirected.

I think what you are trying to do is:

<% @todo_lists.each do |list| %> 
  <p><strong><%= link_to list.title, edit_polymorphic_path(id: list.to_param) #not @todo_list! %></strong></p>
  <% list.todo_items.each do |todo_item| %>
    <p><%= todo_item.description %></p>
  <% end %>
<% end %>

      

And I would seriously consider renaming @todo_list

β†’ @new_todo_list

, as it is quite confusing as it is.




Update

"I can even click a button to open a modal file that will create a new to-do list from one page."

Now you need to create a new resource by submitting a form with an AJAX POST request to /todo_list

, and then refresh the page with the new list (and edit link).

You are now creating a new todo_list that only exists in servers memory before rails finish processing the request.

+2


source







All Articles