erb ruby 65 lines · 4 tabs

Link that submits DELETE with Turbo (data-turbo-method)

Shared by codesnips Jan 2026
4 tabs
<div class="bookmarks">
  <h1>Saved Bookmarks</h1>

  <table>
    <tbody id="bookmarks">
      <%= render partial: "bookmark", collection: @bookmarks %>
    </tbody>
  </table>

  <%= link_to "Add bookmark", new_bookmark_path, class: "btn" %>
</div>
4 files · erb, ruby Explain with highlit

This example shows how a plain anchor tag can trigger a DELETE request in a Rails/Hotwire application without wrapping it in a <form>, and how the server responds with a Turbo Stream that surgically removes the record's row from the page. The data-turbo-method attribute is Turbo's replacement for the older Rails UJS data-method: when Turbo intercepts a click on a link carrying data-turbo-method="delete", it builds a synthetic form, injects the CSRF token, and submits it as a real HTTP DELETE.

In bookmarks/index.html.erb, each bookmark is rendered inside a partial and wrapped in a turbo_frame_tag-independent <tr> whose id comes from dom_id(bookmark). The delete link uses link_to with data: { turbo_method: :delete, turbo_confirm: "Remove this bookmark?" }. The turbo_confirm value drives a native confirmation dialog before the request fires, which matters because DELETE is destructive and easy to trigger accidentally. Note there is no method: :delete here — that is the legacy UJS API and is ignored once @rails/ujs is dropped in favor of Turbo.

The _bookmark.html.erb partial is deliberately tiny and keyed on dom_id, because the same DOM id is what the Turbo Stream targets on removal. Keeping the id generation in one place (dom_id) avoids the classic mismatch bug where the markup says bookmark_12 but the stream tries to remove bookmarks_12.

In BookmarksController, the destroy action calls @bookmark.destroy and then a respond_to block branches on format. The format.turbo_stream branch renders a turbo_stream.remove targeting the record, so only the affected row leaves the DOM — no full-page reload, no re-render of the list. The format.html fallback keeps the endpoint usable for non-Turbo clients and tests by redirecting with a flash. This dual response is what makes the feature degrade gracefully.

A key trade-off: because Turbo synthesizes the form client-side, the endpoint still relies on standard Rails CSRF protection, so the meta CSRF tag must be present in the layout. Also, data-turbo-method links are not crawlable actions and search bots won't follow them, which is desirable for destructive routes. Reach for this pattern whenever a list needs inline delete affordances with minimal JavaScript and precise DOM updates.


Related snips

ruby
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)

System test: asserting Turbo Stream responses

rails hotwire turbo
by codesnips 4 tabs
ruby
class Post < ApplicationRecord
  belongs_to :author, class_name: 'User'
  has_many :comments, dependent: :destroy

  scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
  scope :draft, -> { where(published_at: nil) }

ActiveRecord scopes for reusable query logic

rails activerecord patterns
by Alex Kumar 1 tab
ruby
module Api
  module V1
    class UsersController < BaseController
      def show
        user = User.includes(:profile).find(params[:id])

ETags for conditional requests and caching

rails caching http-caching
by Alex Kumar 1 tab
ruby
class PostsController < ApplicationController
  def index
    @posts = Post.includes(:author)
                 .order(created_at: :desc)
                 .page(params[:page])
                 .per(10)

Turbo Frames: infinite scroll with lazy-loading frame

rails turbo hotwire
by codesnips 4 tabs
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs
erb
<form data-controller="query-sync" data-action="change->query-sync#apply">
  <select name="status" class="rounded border p-2">
    <option value="">Any</option>
    <option value="open">Open</option>
    <option value="closed">Closed</option>
  </select>

Filter UI that syncs query params via Stimulus (no front-end router)

rails hotwire stimulus
by Henry Kim 2 tabs

Share this code

Here's the card — post it anywhere.

Link that submits DELETE with Turbo (data-turbo-method) — share card
Link copied