In the process of familiarizing myself with Hotwire, I am running into some challenges when firing actions to manipulate records.
Resources are nested as follows:
resources :batches do
resources :tasks do
end
Here is the tasks index view:
<div>
<h1><%= @batch.name %></h1>
<div>
<%= render "form", task: Task.new %>
</div>
<divid ="tasks_to_complete">
<% if @batch.no_tasks? %>
<p>No task in this batch.</p>
<% else %>
<%= render @tasks_to_complete %>
<% end %>
</div>
<divid ="tasks_already_completed">
<%= render "tasks_already_completed" %>
</div>
</div>
And the _task
partial:
<div id="<%= "#{dom_id(task)}_container" %>">
<%= turbo_frame_tag task do %>
<div>
<%= link_to task.title, [:edit, @batch, task] %>
<div>
<% if task.completed? %>
<%= button_to toggle_batch_task_path(@batch, task), method: :patch, params: { completed: false } do %>
<div>Mark as incomplete</div>
<% end %>
<% else %>
<%= button_to toggle_batch_task_path(@batch, task), method: :patch, params: { completed: true } do %>
<div>Mark as complete</div>
<% end %>
<% end %>
</div>
<%= button_to batch_task_path(@batch, task), method: :delete do %>
<div>Delete</div>
<% end %>
</div>
<% end %>
</div>
The tasks_controller
looks like this:
class TasksController < ApplicationController
before_action :set_batch
def index
@tasks = @batch.tasks
@tasks_to_complete = @batch.tasks.to_complete
@tasks_already_completed = @batch.tasks.already_completed
@task = Task.new
end
...
def edit
@task = @batch.tasks.find(params[:id])
end
def update
@task = @batch.tasks.find(params[:id])
respond_to do |format|
if @task.update(task_params)
format.turbo_stream
format.html { redirect_to batch_tasks_url(@batch), notice: "Task was successfully updated." }
format.json { render :show, status: :ok, location: @task }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @task.errors, status: :unprocessable_entity }
end
end
end
def toggle
@task = @batch.tasks.find(params[:id])
@task.update(completed: params[:completed], completed_at: DateTime.now)
respond_to do |format|
format.turbo_stream
format.html { redirect_to batch_tasks_url(@batch), notice: "Updated task status." }
end
end
def destroy
@task = @batch.tasks.find(params[:id])
@task.destroy
respond_to do |format|
format.turbo_stream
format.html { redirect_to batch_tasks_url(@batch), notice: "Task was successfully destroyed." }
format.json { head :no_content }
end
end
private
def task_params
params.require(:task).permit(:title, :content, :completed_at, :completed)
end
def set_batch
@batch = Batch.find(params[:batch_id])
end
end
Accordingly, I created a new route for the toggle action:
resources :batches do
resources :tasks do
patch :toggle, on: :member
end
end
Lastly, I have 3 Turbo Stream templates.
First, update.turbo_stream.erb
:
<%= turbo_stream.replace "#{dom_id(@task)}_container" do %>
<%= render "task", task: @task %>
<% end %>
The, toggle.turbo_stream.erb
:
<%= turbo_stream.replace "tasks_to_complete" do %>
<%= render "tasks_to_complete", tasks: @tasks_to_complete %>
<% end %>
<%= turbo_stream.replace "tasks_already_completed" do %>
<%= render "tasks_already_completed", tasks: @tasks_already_completed %>
<% end %>
Finally, destroy.turbo_stream.erb
:
<%= turbo_stream.remove "#{dom_id(@task)}_container" do %>
<%= render "task", task: @task %>
<% end %>
Instead of respectively deleting a task and marking it as compelete/incomplete, the buttons return a 500 Internal Server Error
.
I suspect this has to do with the buttons' paths, which route to nested resources.
Any idea how to make this code work?
UPDATE: I was able to make the delete flow work and I am halfway there regarding the mark as complete/incomplete (toggle) flow: I am able to actually mark tasks as complete or incomplete, persist the info in the database, and upon reload of the page, the task appears where it should be (either in the @tasks_already_completed
when marked as complete or @tasks_to_complete
when marked as complete). However, I am struggling to figure out how to make tasks move from one category to the other without a page reload.
I have updated the code above to reflect the changes I made since I published the question.
Here are the details of the 500 error I get when I mark a task as incomplete:
Completed 500 Internal Server Error in 12ms (ActiveRecord: 1.8ms | Allocations: 4996)
09:06:51 web.1 |
09:06:51 web.1 |
09:06:51 web.1 |
09:06:51 web.1 | ActionView::Template::Error ('nil' is not an ActiveModel-compatible object. It must implement :to_partial_path.):
09:06:51 web.1 | 1: <%= render @tasks %>
09:06:51 web.1 |
09:06:51 web.1 | app/views/tasks/_tasks_to_complete.html.erb:1
09:06:51 web.1 | app/views/tasks/toggle.turbo_stream.erb:6
09:06:51 web.1 | app/views/tasks/toggle.turbo_stream.erb:5
And here is the error I get when I mark a task as incomplete:
Completed 500 Internal Server Error in 94ms (ActiveRecord: 77.7ms | Allocations: 5008)
09:07:28 web.1 |
09:07:28 web.1 |
09:07:28 web.1 |
09:07:28 web.1 | ActionView::Template::Error ('nil' is not an ActiveModel-compatible object. It must implement :to_partial_path.):
09:07:28 web.1 | 1: <%= render @tasks %>
09:07:28 web.1 |
09:07:28 web.1 | app/views/tasks/_tasks_to_complete.html.erb:1
09:07:28 web.1 | app/views/tasks/toggle.turbo_stream.erb:6
09:07:28 web.1 | app/views/tasks/toggle.turbo_stream.erb:5
It looks like the issue is related to the _tasks_to_complete.html.erb
partial, which only includes one line:
<%= render @tasks %>
I am surprised by this error, given that before clicking on any button or link on the page to manipulate the task records, everything seems to be working fine.
I also do not understand why tasks move from the tasks_to_complete
to tasks_already_completed
divs (and vice versa) upon page reload, but not on the fly, which is the goal with Turbo Streams.
Any help is appreciated.