0

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.

  • i think you need only `<%= turbo_stream.remove "#{dom_id(@task)}_container" %>` without block, you could also remove the task itself `<%= turbo_stream.remove @task %>`. I still wonder why you need the `container`, though. – Lam Phan Jul 20 '22 at 03:28
  • @LamPhan thanks for your comment. The delete flow now works, so all good on the remove part. Any idea on how to fix the toggle flow (to move tasks from one category to the other)? Nevertheless, the reason why I have the `container` is because for each task there is both a `
    ` and a `` — unless this is exactly what you are pointing out as unnecessary?
    – newtoncolbert Jul 20 '22 at 13:28

0 Answers0