From 28d112f5f94c46956f0e520803a18db50572292b Mon Sep 17 00:00:00 2001 From: Collin Jilbert Date: Thu, 3 Nov 2022 13:22:04 -0500 Subject: [PATCH 1/8] go full turbo --- app/controllers/projects_controller.rb | 1 - app/views/projects/_form.html.erb | 4 ++-- app/views/projects/task_field.turbo_stream.erb | 3 +++ 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 app/views/projects/task_field.turbo_stream.erb diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 6c72844..3166717 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -60,7 +60,6 @@ def destroy end def task_field - render layout: false end private diff --git a/app/views/projects/_form.html.erb b/app/views/projects/_form.html.erb index 0d5f23a..603ff4e 100644 --- a/app/views/projects/_form.html.erb +++ b/app/views/projects/_form.html.erb @@ -1,4 +1,4 @@ -<%= form_with(model: project, data: { controller: "render-response" }, class: "space-y-4") do |form| %> +<%= form_with(model: project, class: "space-y-4") do |form| %> <% if project.errors.any? %>

<%= pluralize(project.errors.count, "error") %> prohibited this project from being saved:

@@ -22,7 +22,7 @@
- <%= button_tag "Add a Task", type: "button", class: "px-4 py-2 rounded-md bg-blue-300 hover:bg-blue-400", data: { action: "render-response#getTaskPartial" } %> + <%= link_to "Add a Task", task_field_projects_path, data: { turbo_stream: true }, class: "p-2 rounded-md bg-blue-300 hover:bg-blue-400" %>
diff --git a/app/views/projects/task_field.turbo_stream.erb b/app/views/projects/task_field.turbo_stream.erb new file mode 100644 index 0000000..63c3424 --- /dev/null +++ b/app/views/projects/task_field.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.append "tasks" do %> + <%= render partial: "task", locals: { task: Task.new } %> +<% end %> From 5879038ac4cc4bd4c3beb793576ff9fbe23724e4 Mon Sep 17 00:00:00 2001 From: Collin Jilbert Date: Thu, 3 Nov 2022 13:28:44 -0500 Subject: [PATCH 2/8] goodbye stimulus controller, you served us well --- .../controllers/render_response_controller.js | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 app/javascript/controllers/render_response_controller.js diff --git a/app/javascript/controllers/render_response_controller.js b/app/javascript/controllers/render_response_controller.js deleted file mode 100644 index 8d4d32c..0000000 --- a/app/javascript/controllers/render_response_controller.js +++ /dev/null @@ -1,21 +0,0 @@ -import { Controller } from "@hotwired/stimulus" -import { get } from "@rails/request.js" - -// Connects to data-controller="render-response" -export default class extends Controller { - connect() { - } - - async getTaskPartial () { - const response = await get("/projects/task_field"); - - if (response.ok) { - const body = await response.html; - const tasksSection = document.querySelector("#tasks"); - const templateElement = document.createElement("template"); - templateElement.innerHTML = body; - - tasksSection.appendChild(templateElement.content.firstElementChild); - } - } -} From eabb108970429f27754bb1a5bffb816c54c72b07 Mon Sep 17 00:00:00 2001 From: Collin Jilbert Date: Fri, 4 Nov 2022 13:52:58 -0500 Subject: [PATCH 3/8] clean up --- README.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/README.md b/README.md index 57ef4af..85e8fc8 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,3 @@ # README ## Dynamic Nested Forms with Turbo GET request - -Screencast outline -- Introduce the app. Rails 7 app using esbuild and tailwind. Has a Project model that has_many :tasks and - a task belongs_to :project. For simplicity, each only has a name string attribute aside from the - project_id attibute on tasks. -- Walk through the code in `app/views/projects/new.html.erb` and `app/views/projects/edit.html.erb` and - then the code in the form located in `app/views/projects/_form.html.erb`. -- While walking through the form touch on `
` tag, using the `collection:` option - to render the "task" partial for each task associated to the project. -- Show the form in the browser and then talk about the `button_tag` that is being used to trigger - a function call in the related stimulus controller. -- Open up the stimulus controller code and walk through the initial setup that uses Request.js with - a GET request to the `ProjectsController#task_fields` endpoint. Talk about why `layout: false` - is there and then click the button with that commented out and in and look at the network - tab and inspect the response. From 8c9fefefd502459167060eaafadfc00d601a60a6 Mon Sep 17 00:00:00 2001 From: cjilbert504 <54157657+cjilbert504@users.noreply.github.com> Date: Fri, 4 Nov 2022 13:54:53 -0500 Subject: [PATCH 4/8] Delete README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 85e8fc8..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# README - -## Dynamic Nested Forms with Turbo GET request From 90544427f1d2bfccaca6ebf9f91ab9b22321e72f Mon Sep 17 00:00:00 2001 From: Collin Jilbert Date: Fri, 9 Jun 2023 19:54:41 -0500 Subject: [PATCH 5/8] Fix bug: When one of the validation fails, and the form is re-rendered to the user and they make another mistake, the 2nd time this nested form will completely disappear. --- app/views/projects/_task.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/_task.html.erb b/app/views/projects/_task.html.erb index af1a4be..40db1fd 100644 --- a/app/views/projects/_task.html.erb +++ b/app/views/projects/_task.html.erb @@ -1,4 +1,4 @@ -<%= fields_for "project[tasks_attributes][#{task.persisted? ? task.id : Time.now.to_i }]", task do |ff| %> +<%= fields_for "project[tasks_attributes][#{task.persisted? ? task.id : task.object_id }]", task do |ff| %>
Task Info <%= ff.label :name %>
From 41eedf2782774626bad6b2a297fdd0b25b6edcba Mon Sep 17 00:00:00 2001 From: Collin Jilbert Date: Mon, 3 Jul 2023 09:38:38 -0500 Subject: [PATCH 6/8] remove import of render response controller --- app/javascript/controllers/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index ac310f5..d0685d3 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -6,6 +6,3 @@ import { application } from "./application" import HelloController from "./hello_controller" application.register("hello", HelloController) - -import RenderResponseController from "./render_response_controller" -application.register("render-response", RenderResponseController) From 6a8564c50ad9b7e45e6c573037c585d1efccaa45 Mon Sep 17 00:00:00 2001 From: Collin Jilbert Date: Mon, 17 Jul 2023 09:24:15 -0500 Subject: [PATCH 7/8] add delete feature for nested attrs --- app/controllers/projects_controller.rb | 3 --- app/controllers/tasks_controller.rb | 14 ++++++++++++++ app/helpers/application_helper.rb | 9 +++++++++ app/helpers/tasks_helper.rb | 2 ++ app/models/project.rb | 8 ++------ app/models/task.rb | 2 ++ app/views/projects/_form.html.erb | 10 +++------- app/views/projects/_task.html.erb | 9 --------- app/views/projects/task_field.html.erb | 1 - app/views/projects/task_field.turbo_stream.erb | 3 --- app/views/tasks/_task.html.erb | 10 ++++++++++ app/views/tasks/destroy.html.erb | 2 ++ app/views/tasks/new.html.erb | 2 ++ config/routes.rb | 10 ++-------- test/controllers/tasks_controller_test.rb | 13 +++++++++++++ 15 files changed, 61 insertions(+), 37 deletions(-) create mode 100644 app/controllers/tasks_controller.rb create mode 100644 app/helpers/tasks_helper.rb delete mode 100644 app/views/projects/_task.html.erb delete mode 100644 app/views/projects/task_field.html.erb delete mode 100644 app/views/projects/task_field.turbo_stream.erb create mode 100644 app/views/tasks/_task.html.erb create mode 100644 app/views/tasks/destroy.html.erb create mode 100644 app/views/tasks/new.html.erb create mode 100644 test/controllers/tasks_controller_test.rb diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 3166717..c0de641 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -59,9 +59,6 @@ def destroy end end - def task_field - end - private # Use callbacks to share common setup or constraints between actions. def set_project diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb new file mode 100644 index 0000000..0d99646 --- /dev/null +++ b/app/controllers/tasks_controller.rb @@ -0,0 +1,14 @@ +class TasksController < ApplicationController + def new + render turbo_stream: turbo_stream.append(:tasks, partial: "tasks/task", locals: { task: Task.new }) + end + + def destroy + task = Task.find(params[:id]) + task.destroy + rescue ActiveRecord::RecordNotFound + task = params[:id] + ensure + render turbo_stream: turbo_stream.remove(task) + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be79..87787a5 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,11 @@ module ApplicationHelper + def turbo_id_for(obj, id_or_hash: false) + id = if id_or_hash + obj.id + elsif obj.persisted? + dom_id(obj) + end + + id || obj.hash + end end diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb new file mode 100644 index 0000000..ce894d0 --- /dev/null +++ b/app/helpers/tasks_helper.rb @@ -0,0 +1,2 @@ +module TasksHelper +end diff --git a/app/models/project.rb b/app/models/project.rb index 46c6c09..d0bfaa5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1,11 +1,7 @@ class Project < ApplicationRecord has_many :tasks, dependent: :destroy - accepts_nested_attributes_for :tasks + validates :name, presence: true - # def task_attributes=(task_attributes) - # task_attributes.each do |attr| - # tasks.build(attr) - # end - # end + accepts_nested_attributes_for :tasks end diff --git a/app/models/task.rb b/app/models/task.rb index e8f6d75..87483b6 100644 --- a/app/models/task.rb +++ b/app/models/task.rb @@ -1,3 +1,5 @@ class Task < ApplicationRecord belongs_to :project + + validates :name, presence: true end diff --git a/app/views/projects/_form.html.erb b/app/views/projects/_form.html.erb index 603ff4e..50eea10 100644 --- a/app/views/projects/_form.html.erb +++ b/app/views/projects/_form.html.erb @@ -18,18 +18,14 @@
- <%= render partial: "task", collection: @project.tasks %> + <%= render @project.tasks %>
- <%= link_to "Add a Task", task_field_projects_path, data: { turbo_stream: true }, class: "p-2 rounded-md bg-blue-300 hover:bg-blue-400" %> + <%= link_to "Add a Task", new_task_path, data: { turbo_stream: true }, class: "p-2 rounded-md bg-blue-300 hover:bg-blue-400" %>
- <% if form.object.persisted? %> - <%= form.submit "Update", class: "p-2 rounded-md bg-blue-300 hover:bg-blue-400 w-full" %> - <% else %> - <%= form.submit "Create", class: "p-2 rounded-md bg-blue-300 hover:bg-blue-400 w-full" %> - <% end %> + <%= form.submit nil, class: "p-2 rounded-md bg-blue-300 hover:bg-blue-400 w-full" %>
<% end %> diff --git a/app/views/projects/_task.html.erb b/app/views/projects/_task.html.erb deleted file mode 100644 index 40db1fd..0000000 --- a/app/views/projects/_task.html.erb +++ /dev/null @@ -1,9 +0,0 @@ -<%= fields_for "project[tasks_attributes][#{task.persisted? ? task.id : task.object_id }]", task do |ff| %> -
- Task Info - <%= ff.label :name %>
- <%= ff.text_field :name, class: "px-2 py-1 border w-full rounded-md" %> - <%= ff.hidden_field :id %> -
-<% end %> - diff --git a/app/views/projects/task_field.html.erb b/app/views/projects/task_field.html.erb deleted file mode 100644 index 3500e53..0000000 --- a/app/views/projects/task_field.html.erb +++ /dev/null @@ -1 +0,0 @@ -<%= render partial: "task", locals: { task: Task.new } %> diff --git a/app/views/projects/task_field.turbo_stream.erb b/app/views/projects/task_field.turbo_stream.erb deleted file mode 100644 index 63c3424..0000000 --- a/app/views/projects/task_field.turbo_stream.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= turbo_stream.append "tasks" do %> - <%= render partial: "task", locals: { task: Task.new } %> -<% end %> diff --git a/app/views/tasks/_task.html.erb b/app/views/tasks/_task.html.erb new file mode 100644 index 0000000..db6bf92 --- /dev/null +++ b/app/views/tasks/_task.html.erb @@ -0,0 +1,10 @@ +<%= fields_for "project[tasks_attributes][#{turbo_id_for(task)}]", task do |ff| %> +
+ Task Info + <%= ff.label :name %>
+ <%= ff.text_field :name, class: "px-2 py-1 border w-full rounded-md" %> + <%= ff.hidden_field :id %> + <%= button_tag "Delete Task", formaction: task_path(turbo_id_for(task, id_or_hash: true)), formmethod: :delete, class: "mt-4 float-right p-2 rounded-md bg-red-300 hover:bg-red-400" %> +
+<% end %> + diff --git a/app/views/tasks/destroy.html.erb b/app/views/tasks/destroy.html.erb new file mode 100644 index 0000000..a90559f --- /dev/null +++ b/app/views/tasks/destroy.html.erb @@ -0,0 +1,2 @@ +

Tasks#destroy

+

Find me in app/views/tasks/destroy.html.erb

diff --git a/app/views/tasks/new.html.erb b/app/views/tasks/new.html.erb new file mode 100644 index 0000000..2484008 --- /dev/null +++ b/app/views/tasks/new.html.erb @@ -0,0 +1,2 @@ +

Tasks#new

+

Find me in app/views/tasks/new.html.erb

diff --git a/config/routes.rb b/config/routes.rb index 7d74028..5f684c9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,12 +1,6 @@ Rails.application.routes.draw do root "projects#index" - resources :projects do - collection do - get "task_field" - end - end - # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html - # Defines the root path route ("/") - # root "articles#index" + resources :tasks, only: [:new, :destroy] + resources :projects end diff --git a/test/controllers/tasks_controller_test.rb b/test/controllers/tasks_controller_test.rb new file mode 100644 index 0000000..3135fce --- /dev/null +++ b/test/controllers/tasks_controller_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class TasksControllerTest < ActionDispatch::IntegrationTest + test "should get new" do + get tasks_new_url + assert_response :success + end + + test "should get destroy" do + get tasks_destroy_url + assert_response :success + end +end From adfe726005c89f1c71b81f1ddf63c797bddad80a Mon Sep 17 00:00:00 2001 From: Collin Jilbert Date: Fri, 11 Aug 2023 08:40:44 -0500 Subject: [PATCH 8/8] bug fix with updating. Might revisit to see if can be improved but seems to solve for now --- app/controllers/tasks_controller.rb | 4 ++-- app/helpers/application_helper.rb | 10 ++-------- app/views/tasks/_task.html.erb | 4 ++-- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb index 0d99646..67a7990 100644 --- a/app/controllers/tasks_controller.rb +++ b/app/controllers/tasks_controller.rb @@ -7,8 +7,8 @@ def destroy task = Task.find(params[:id]) task.destroy rescue ActiveRecord::RecordNotFound - task = params[:id] + task = Task.new(id: params[:id]) ensure - render turbo_stream: turbo_stream.remove(task) + render turbo_stream: turbo_stream.remove(task.id) end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 87787a5..9616485 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,11 +1,5 @@ module ApplicationHelper - def turbo_id_for(obj, id_or_hash: false) - id = if id_or_hash - obj.id - elsif obj.persisted? - dom_id(obj) - end - - id || obj.hash + def turbo_id_for(obj) + obj.persisted? ? obj.id : obj.hash end end diff --git a/app/views/tasks/_task.html.erb b/app/views/tasks/_task.html.erb index db6bf92..bab8b6b 100644 --- a/app/views/tasks/_task.html.erb +++ b/app/views/tasks/_task.html.erb @@ -1,10 +1,10 @@ <%= fields_for "project[tasks_attributes][#{turbo_id_for(task)}]", task do |ff| %> -
+
Task Info <%= ff.label :name %>
<%= ff.text_field :name, class: "px-2 py-1 border w-full rounded-md" %> <%= ff.hidden_field :id %> - <%= button_tag "Delete Task", formaction: task_path(turbo_id_for(task, id_or_hash: true)), formmethod: :delete, class: "mt-4 float-right p-2 rounded-md bg-red-300 hover:bg-red-400" %> + <%= button_tag "Delete Task", formaction: task_path(turbo_id_for(task)), formmethod: :delete, class: "mt-4 float-right p-2 rounded-md bg-red-300 hover:bg-red-400" %>
<% end %>