Hi,
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 with the form helpers and controllers per the documentation on Rails Form Helpers.
The setup of the application can be viewed on my public github( https://github.com/cjmccormick88/testapp-nested ).
The setup of the application is as follows:
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.
Clients controller is as follows:
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: [:address_line1, :address_line2, :city, :state, :country])
end
end
Client Model is as follows:
class Client < ApplicationRecord
audited
has_many :shipping_addresses, :inverse_of => :client, autosave: true
accepts_nested_attributes_for :shipping_addresses
end
Shipping Address Model is as follows:
class ShippingAddress < ApplicationRecord
audited
belongs_to :client
validates :client, :presence => true
end
The form helper for clients is as follows:
<%= 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.
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
Currently, the application does accept the submission of a client and it makes a blank entry in the shipping addresses table and posts the id that corresponds to its foreign key. However, no other values are created in the entry.
Corrective Actions
I’ve gone through the rails documentation, read forum posts, and scoured the web for articles on nested forms from version to version. So far, I’ve had no luck in finding a way to allow the parameters to be saved into the other table that have been successful.
I also tried the dynamic nested form developed at stevepolitodesign for Rails 6. The app works, but with bootstrap it does not work. So, there is a problem with the more Javascript-based approach there.
Scope
I’m seeking help to understand what is wrong with the configurations if they are incorrect and/or if anyone is having similar issues and if they have been resolved. This project is an example derived from a larger project I’m working on that will need to use nested attributes to be beneficial to users.
Thanks!!