Hotwire Discussion

How to delete turbo stream from another model's show page?

Hey there, I’ve been stuck on what I would imagine to be a very simple thing to do, however it is not working for me and it would be awesome to have a pointer in the right direction.

Basically I’m just following the general Hotwire Demo setup using default scaffolding changing the model names, and using Webpacker. I am just trying to delete an Item turbo_stream element with an associated dom_id of (item_id#) which is just being displayed on the Show page of a List view.

I am thinking that on the List show page when deleting from the Item stream, turbo_stream should be able to detect the item_id# and remove the corresponding item from the item controller…However it for some reason not linking correctly and I’m not sure what to do…

Basically - How do I grab the item_id from the -items turbo_stream- which is on the list#show page so that I can use that info on the button_to :delete method to tell the ItemsController which specific item needs to be deleted? I am hoping the TurboStream should be able to detect the item_id to pass to the item controller to be removed…but how do I do this??

. Here is the setup below

The setup:
Ruby 2.7.1
Rails: 6.1.4.1

Models

#../models/item.rb
class Item < ApplicationRecord
  belongs_to :list

  broadcasts_to ->(i) { "items" }, inserts_by: :prepend, target: "items"
  after_destroy_commit -> { broadcast_remove_to self }
end

#../models/list.rb
class List < ApplicationRecord
  has_many :items , dependent: :destroy
  
  #broadcasts
  broadcasts_to ->(l) { "lists" }, inserts_by: :prepend, target: "lists"

  after_destroy_commit do
    broadcast_remove_to self
  end
end

Controllers:

#this one works for me fine on the list Index page
#../controllers/lists_controller.rb 
  # DELETE  method
  def destroy
    @list.destroy
    respond_to do |format|
      format.turbo_stream {  }
      format.html { redirect_to lists_url, notice: "List was successfully destroyed." }
      format.json { head :ok } 
    end
  end

#../controllers/items_controller.rb
  # DELETE /items/1 or /items/1.json
  def destroy
    @item.destroy

    respond_to do |format|
      format.turbo_stream { " " }
      #format.turbo_stream { render turbo_stream: turbo_stream.remove(@list.item) }
      #format.html { redirect_to items_url, notice: "Item was successfully destroyed." }
      format.html { redirect_to @list, notice: "Item was successfully destroyed." }
      format.json { head :no_content }
    end
  end

Views

#../views/lists/show.html.erb
...
...
<%= turbo_stream_from "items" %>
<div id="items" class="box">
<%= render @list.items %>
</div>

#../views/items/_item.html.erb
<div id="<%= dom_id item %>" >
  <div class="is-size-6 has-text-primary columns">
    <div class="column"><%= item.created_at.to_s(:short) %> : <%= item.name %></div>
    <div class="column"><%= button_to "Delete",<????>, method: :delete, class: "button is-danger" %></div>
  </div>
</div>

Right here I want to delete an Item turbo_stream from the lists#show page; however I’m not sure how to set the params in the button_to :delete method. Any suggestions? Where am I to get the @list.item_id params from to pass to the button??

1 Like

OK - Lesson learned!
No response can be a good thing as it is great to be able to answer your own questions at times!

The offending sections were :

1.

# ../views/items/_item.html.erb
<%= turbo_frame_tag item do %>
# Having to wrap the elements with the **turbo_frame_tag** instead of using the dom_id
<% end %>

The above was strange as the list#index page used:

<%= tag.div id: dom_id(list) do %>
/views/lists/_list.html.erb
# This worked for the lists route and was able to delete lists from the index page fine
# However the one above like the turbo_frame_tag
<% end %>

I had imagined that I could use Stream independently w/o the need for a turbo_frame_tag
as the turbo_frame_tag was just used to interchange views. More testing to do on that.
Didn’t realize I may have needed a turbo_frame_tag here? I thought they were entirely separate structures.

2.
The next offender was:

# /views/items/_item.html.erb
<%= turbo_frame_tag item do %>
# ....
# ....
    <div class="column"><%= button_to "Delete", **[@list, item] **, method: :delete, class: "button is-danger" %></div>
<% end %>

Here I needed to pass some variables back to the controller and grab those variables in the controller, assign to the Item model in the List controller to make appropriate destroy call like:

#../controllers/items_controller.rb  
  def destroy

    pp "PARAMS : #{params}" #Show params being passed so I can make the link clearer
    
    @item = Item.find(params[:id]) # Key 
    @item.destroy
    respond_to do |format|
      format.turbo_stream { render turbo_stream: " " }
      format.html { redirect_to @list, notice: "Item removed" }
      format.json { head :no_content }
    end
  end

That’s about it. Now I can have a List that has many Items, where Items from the list#show can be deleted from the turbo stream appropriately using the button_to to pass Item model information through.

Oh Also: I changed the routes to expose the Item path…this feels wrong/hacky but it works for now.

#config/routes.rb

  resources :lists do
    resources :items
  end
  #resources :lists
  resources :items, only: [:destroy]
# Did the above to expose the /items/:id route to pass from the button_to POST action so the appropriate ITEM could be removed. 

Still slight bugs to work through…So while I didn’t find an answer to my question, I found a way.

Hi Cabrace,

I am using below way for my project

For Stream
<%= turbo_stream_from “items” %>
<%= turbo_frame_tag “items” do %>
<%= render @items %>
<% end %>

For Frame (file _item.html.erb)
<%= turbo_frame_tag dom_id(item) do %>

<% end %>

Thanks,

1 Like