ruby-on-railsrubynested-formshotwire-railshotwire

Rails 7, Hotwire dynamic forms, updating value doesn't work


I've recently started using Hotwire for a project I'm working on. Have been following this tutorial on GoRails. Currently having some issues with adding / updating records in a dynamic form. I have two models with a belongs_to and has_many relationship.

ProgramPart < ApplicationRecord
  has_many :rich_contents, dependent: :destroy
  accepts_nested_attributes_for :rich_contents, reject_if: :all_blank, allow_destroy: true
end

class RichContent < ApplicationRecord
  belongs_to :program_part
  has_rich_text :content
end

In a form I have a button where users dynamically can add RichContent fields:

enter image description here

My relevant parts of my form for ProgramPart looks like this:

<div id="block_elements" class="col-span-6 space-y-6">
  <% @program_part.rich_contents.each do |content| %>
    <%= render(RichContent::RichContentFormComponent.new(rich_content: content)) %>
  <% end %>
</div>
  
<div class="col-span-6">
  <span>
    <%= link_to "Text and media", new_company_program_program_part_rich_content_path(@company, @program, @program_part.persisted? ? @program_part.id : Time.now.to_i), data: { turbo_stream: true } %>
  </span>
</div>

<div class="bg-gray-50">      
  <%= part_form.submit class: "#{submit_classes}" %>
</div>

I have a .turbo_stream.erb file where I render the form for my RichContent:

<%= turbo_stream.append "block_elements" do %>
  <%= render(RichContent::RichContentFormComponent.new(rich_content: @rich_content)) %>
<% end %>

# @rich_content = RichContent.new in controller

My ViewComponent for that form looks like this:

<%= fields_for "program_part[rich_contents_attributes][#{@rich_content.persisted? ? @rich_content.id : Time.now.to_i }]", @rich_content do |rich_content_form| %>
  <%= render(Forms::InputLabelComponent.new(label: "Text and media", form_builder:  rich_content_form, model_attribute: :content)) do %>
    <%= rich_content_form.rich_text_area :content %>
  <% end %>
<% end %>

The problem I'm having is when editing ProgramParts, updating existing values doesn't work and it creates new instances. Let's say if I have content "1", goes to edit, updates value to "2" submits the form, then it insert content "1" and "2" as new records.

The form params looks like this when submitting.

Processing by ProgramPartsController#update as TURBO_STREAM
14:28:07 web.1  |   Parameters: {"authenticity_token"=>"[FILTERED]",   "program_part"=>{"title"=>"Test", "description"=>"", "rich_contents_attributes"=>. {"152"=>{"content"=>"<div>2</div>"}}}, "commit"=>"Update Program part",   "company_id"=>"1", "program_id"=>"9", "id"=>"27"}

Which gives the following result:

enter image description here

Any ideas on what I'm missing or why it doesn't update existing records and instead adds new records?


Solution

  • You need an id attribute submitted with nested fields:

    <%= fields_for "program_part[rich_contents_attributes][#{@rich_content.persisted? ? @rich_content.id : Time.now.to_i }]", @rich_content do |rich_content_form| %>
      <%= render(Forms::InputLabelComponent.new(label: "Text and media", form_builder:  rich_content_form, model_attribute: :content)) do %>
        <%= rich_content_form.rich_text_area :content %>
    
        <%= rich_content_form.hidden_field :id if rich_content_form.object.persisted? %>
      <% end %>
    <% end %>
    

    The key in nested attributes is not used, it's just there to group fields together, which is why Time.now.to_i can also be used:

    "rich_contents_attributes" => {"152"=>{"content"=>"<div>2</div>"}
    #                               ^ not used