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 %>
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 %>
source to share
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
.
source to share
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.
source to share