Ask A Question

Notifications

You’re not receiving notifications from this thread.

Issue with Nested Forms in Rails 6.1.3.2

Casey McCormick asked in Rails

I’m working on a project that will utilize nested forms that incorporates Rails 6.1.3 and Bootstrap 5.1.2.
I’m having difficulty getting the nested form feature to work.
Project GitHub: cjmccormick88/testapp-nested

There are two models: client and shipping address.
Client accepts nested attributes for the shipping address model. A client can have many shipping addresses.

Authentication is being handled by Devise. Bootstrap is used for styling. Audited is used for audit trail.

Clients Controller

class ClientsController < ApplicationController

  before_action :set_client, only: %i[ show edit update destroy ]

  # GET /clients or /clients.json
  def index
    @clients = Client.all
  end

  # GET /clients/1 or /clients/1.json
  def show
  end

  # GET /clients/new
  def new
    @client = Client.new
    @client.shipping_addresses.build
  end

  # GET /clients/1/edit
  def edit
  end

  # POST /clients or /clients.json
  def create
    @client = Client.new(client_params)
    @client.shipping_addresses.build(client_params[:shipping_addresses_attributes])
    @client.save

    respond_to do |format|
      if @client.save
        format.html { redirect_to @client, notice: "Client was successfully created." }
        format.json { render :show, status: :created, location: @client }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @client.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /clients/1 or /clients/1.json
  def update
    respond_to do |format|
      if @client.update(client_params)
        format.html { redirect_to @client, notice: "Client was successfully updated." }
        format.json { render :show, status: :ok, location: @client }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @client.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /clients/1 or /clients/1.json
  def destroy
    @client.destroy
    respond_to do |format|
      format.html { redirect_to clients_url, notice: "Client was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_client
      @client = Client.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def client_params
      params.require(:client).permit(:client_name, shipping_addresses_attributes: [:id, :address_line1, :address_line2, :city, :state, :country])
    end

end

Client Model

 class Client < ApplicationRecord

   audited

   has_many :shipping_addresses, :inverse_of => :client, autosave: true
   accepts_nested_attributes_for  :shipping_addresses


 end

Shipping Address Model

class ShippingAddress < ApplicationRecord

   audited
   belongs_to :client
  validates :client, :presence => true

end

Client View Form Partial

<%= form_with(model: client) do |form| %>
   <% if client.errors.any? %>
     <div id="error_explanation">
       <h2><%= pluralize(client.errors.count, "error") %> prohibited this client from being saved:</h2>

       <ul>
         <% client.errors.each do |error| %>
           <li><%= error.full_message %></li>
         <% end %>
       </ul>
     </div>
   <% end %>

   <div class="field">
     <%= form.label :client_name %>
     <%= form.text_field :client_name %>
   </div>

   <%= form.fields_for @client.shipping_addresses.build do |s| %>

     <div class="field">
       <%= s.label :address_line1, 'Address Line 1' %>
       <%= s.text_field :address_line1 %>
     </div>

     <div class="field">
       <%= s.label :address_line2, 'Address Line 2' %>
       <%= s.text_field :address_line2 %>
     </div>

     <div class="field">
       <%= s.label :city, 'City' %>
       <%= s.text_field :city %>
     </div>

     <div class="field">
       <%= s.label :state, 'State' %>
       <%= s.text_field :state %>
     </div>

     <div class="field">
       <%= s.label :country, 'Country' %>
       <%= s.text_field :country %>
     </div>

   <% end %>

   <div class="actions">
     <%= form.submit %>
   </div>

 <% end %>

In addition, there is a controller for shipping addresses if someone chooses to view those pages on their own.

Shipping Addresses Controller

class ShippingAddressesController < ApplicationController
   before_action :set_shipping_address, only: %i[ show edit update destroy ]

   # GET /shipping_addresses or /shipping_addresses.json
   def index
     @shipping_addresses = ShippingAddress.all
   end

   # GET /shipping_addresses/1 or /shipping_addresses/1.json
   def show
   end

   # GET /shipping_addresses/new
   def new
     @shipping_address = ShippingAddress.new
   end

   # GET /shipping_addresses/1/edit
   def edit
   end

   # POST /shipping_addresses or /shipping_addresses.json
   def create
     @shipping_address = ShippingAddress.new(shipping_address_params)

     respond_to do |format|
       if @shipping_address.save
         format.html { redirect_to @shipping_address, notice: "Shipping address was successfully created." }
         format.json { render :show, status: :created, location: @shipping_address }
       else
         format.html { render :new, status: :unprocessable_entity }
         format.json { render json: @shipping_address.errors, status: :unprocessable_entity }
       end
     end
   end

   # PATCH/PUT /shipping_addresses/1 or /shipping_addresses/1.json
   def update
     respond_to do |format|
       if @shipping_address.update(shipping_address_params)
         format.html { redirect_to @shipping_address, notice: "Shipping address was successfully updated." }
         format.json { render :show, status: :ok, location: @shipping_address }
       else
         format.html { render :edit, status: :unprocessable_entity }
         format.json { render json: @shipping_address.errors, status: :unprocessable_entity }
       end
     end
   end

   # DELETE /shipping_addresses/1 or /shipping_addresses/1.json
   def destroy
     @shipping_address.destroy
     respond_to do |format|
       format.html { redirect_to shipping_addresses_url, notice: "Shipping address was successfully destroyed." }
       format.json { head :no_content }
     end
   end

   private
     # Use callbacks to share common setup or constraints between actions.
     def set_shipping_address
       @shipping_address = ShippingAddress.find(params[:id])
     end

     # Only allow a list of trusted parameters through.
     def shipping_address_params
       params.require(:shipping_address).permit(:address_line1, :address_line2, :city, :state, :country, :client_id)
     end
 end

Behavior of the Application

The application accepts the entry of the client form and it manipulates the model for the shipping address; however, the only entry in the table on each row is the client_id value for the client foreign key. It is not committing the other components of the hash into the table.

Things Tried

  1. I've tried the application posted on GitHub at stevepolitodesign/rails-nested-form-app.
  2. I've tried a suggestion made from a similar post on rails forum: difficulties-with-nested-form-implementation-rails-6-1-3/78776/3.
  3. I've gone through as much documentation as I can track down on the standard rails guides for nested forms.

Results from all of these did not yield good results. Item # 1 was a decent app in terms of the pathway it takes; however, when you are using bootstrap it does not seem to work. It could be that the code there has to be modified some to allow that functionality. So far, any posts made for a request regarding bootstrap with that design have not yielded fruit.

Scope

I'm looking to understand the problem that is happening and/or find a better way to accomplish this function that cooperates well with Bootstrap use.

Reply

Is this the link to your application? I might be able to take a look and submit a PR.

https://github.com/cjmccormick88/testapp-nested

Reply

I think the main problem was that you were calling @client.shipping_addresses.build in multiple places, so the values from the form were probably getting overridden.

  • Remove @client.shipping_addresses.build(client_params[:shipping_addresses_attributes]) and @client.save and from ClientControllers#create.
  • Remove autosave: true from has_many :shipping_addresses from the Client model. I'm not sure if that was also causing an issue or not.
  • Replace @client.shipping_addresses.build with :shipping_addresses in app/views/clients/_form.html.erb.

I made a PR against your repo that should solve the problem

https://github.com/cjmccormick88/testapp-nested/pull/1

Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 85,376+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.