1

I am having terrible problems trying to get a new item to render back to the index of a page using turbo streams.

I want it to go back to the index after the create action and append the deal to the page, I need this to be a turbo stream. So far when I create it never leaves the new form..how do I make the create action go back to the index using the turbo stream and append the new deal. Thanks in advance

Here is my code

index.html.haml
%p{style: "color: green"}= notice
%h1 Deals
= turbo_frame_tag "deals" do
  - @deals.each do |deal|
    = render deal

= link_to "New deal", new_deal_path


_deal.html.haml
%div
  %p
    %strong Name:
    = deal.name

new.html.haml
%h1 New deal
= simple_form_for(@deal) do |f|
  %div
    = f.input :name
  %div
    = f.submit

deals_controller.rb
def create
    @deal = Deal.new(deal_params)
    respond_to do |format|
      if @deal.save
        format.turbo_stream
      end
    end
  end

create.turbo_stream.haml
= turbo_stream.prepend "deals", partial: "deals/deal", locals: { deal: @deal }


  • 2
    you're not on the index page, so you don't have `#deals` target. turbo stream is for updating your current page. for example, if you had your form on the index page it would work. if you need to go back to `index` from `new` page, you need to redirect to `deals_path`. – Alex Mar 16 '23 at 02:37

1 Answers1

1

The thing is here you don't want to be redirecting the user around. Instead they just stay on the index page and you just send the form submission with ajax and then append the new records to the existing document.

So lets start refactoring the form into a partial for resuse:

# deals/_form.html.haml
= simple_form_for(deal) do |f|
  %div
    = f.input :name
  %div
    = f.submit

And then lets put the form on the index view:

# deals/index.html.haml
%h1 Deals
= turbo_frame_tag "deal_form" do
  = render "form", deal: @deal

= turbo_frame_tag "deals" do
  = render @deals

We also need a turbo stream view for invalid form submissions:

# deals/new.turbo_stream.haml
= turbo_stream.replace "deal_form" do
  = render partial: "deals/form", locals: { deal: @deal } 

Here we are just using the progressive enhancement approach and both the form and deals are rendered on the initial page view. Using a turbo frame and not just div#deals isn't actually doing that much in this case but it will later if you add the src attribute for example to decompose the page.

Then lets setup the controller to stream either a new deal to to the list or "send the form back" with errors.

class DealsController < ApplicationController

  # GET /deals or /deals.json
  def index
    @deals = Deal.all
  end

  # POST /deals or /deals.json
  def create
    @deal = Deal.new(deal_params)
    respond_to do |format|
      if @deal.save
        format.html { redirect_to action: :index, notice: "Deal was successfully created." }
        format.turbo_stream
      else
        format.html do 
          @deals = Deal.all
          render :index, status: :unprocessable_entity
        end
        format.turbo_stream { render :new, status: :unprocessable_entity }
      end
    end
  end

  private
    # Only allow a list of trusted parameters through.
    def deal_params
      params.require(:deal).permit(:name)
    end
end

Turbo also provides methods to just render turbo streams straight for the the controller if what you're doing is simple enough that you don't need a view:

  def create
    @deal = Deal.new(deal_params)

    respond_to do |format|
      if @deal.save
        format.html { redirect_to action: :index, notice: "Deal was successfully created." }
        format.turbo_stream do
          render turbo_stream: turbo_stream.prepend(:deals, partial: "deals/deal", locals: { deal: @deal })
        end 
      else
        format.html { render :new, status: :unprocessable_entity }
        format.turbo_stream do
          render turbo_stream: turbo_stream.replace(:deal_form, partial: "deals/form", locals: { deal: @deal })
        end 
      end
    end
  end
max
  • 96,212
  • 14
  • 104
  • 165
  • Sorry for the messy editing history. I'm still kind of figuring turbo out and it took a few tries to find an approach that didn't just work but was actually a good clean solution. – max Mar 18 '23 at 15:04
  • because docs make a bit not obvious: `before` - is outside of the div, you want `prepend`. `replace` - like it sounds, will replace the target (outer html), you're losing the deal_form frame because it's not in the _form partial. you want `update` - to only switch the insides (inner html). also, this works: `turbo_stream.prepend(:deals, @deal)`. – Alex Apr 21 '23 at 12:14