I'm working on a rails CMS project and I am trying to create from the admin dashboard, a quotes section where users can add a section_quote with title, image and five quotes with their authors(5 inputs for quotes and 5 for authors).
The issue I have is when I submit the form, all the data are saved and they persist in the form section except for data in the nested form. I found many topics here almost related to this case but none resolved my issue.
Quote model and its migration:
class Quote < ApplicationRecord
#RELATIONS
belongs_to :section, inverse_of: :quotes, :class_name => 'SectionQuote', foreign_key: :section_id
#VALIDATIONS
validates :quote_text, :author, presence: true
end
class CreateQuotes < ActiveRecord::Migration[6.1]
def change
create_table :quotes, id: :uuid do |t|
t.string :author
t.text :quote_text
t.timestamps
end
add_column :quotes, :section_id, :uuid
end
end
My section_quote model and section migration:
class SectionQuote < Section
has_many :quotes, :class_name => 'Quote', inverse_of: :section, foreign_key: :section_id
accepts_nested_attributes_for :quotes, allow_destroy: true, reject_if: :all_blank
validates :title, presence: true
validates :image, presence: true
end
class CreateSections < ActiveRecord::Migration[6.1]
def change
create_table :sections, id: :uuid do |t|
t.string :type
t.uuid :page_id
t.timestamps
end
end
end
Section_controller:
module Admin
# administrative controller for Sections
class SectionsController < Admin::AdminController
before_action :load_section, only: [:update, :destroy, :show]
def create
@section = Section.new(build_params)
authorize! :create, @section
if @section.save
flash.now[:success] = 'Sezione creata con successo'
else
flash.now[:error] = 'Ci sono degli errori nei dati inseriti. Si prega di
controllare'
end
respond_to do |format|
format.js{ }
end
end
def update
authorize! :update, @section
if @section.update(update_params)
flash.now[:success] = 'Sezione aggiornata con successo'
else
flash.now[:error] = 'Ci sono degli errori nei dati inseriti. Si prega di controllare'
end
respond_to do |format|
format.js{ }
end
end
def destroy
authorize! :destroy, @section
@section.destroy
flash.now[:success] = 'Sezione eliminata correttamente'
respond_to do |format|
format.js{ }
end
end
def section
@section_type = params[:section_type]
section_klass = @section_type.camelize.constantize
@page = Page.find(params[:page_id])
@section = section_klass.new(page_id: params[:page_id])
end
private
def load_section
@section = Section.find(params[:id])
end
# Build params
def build_params
params.require(:section).permit(params_list)
end
def update_params
params.require(:section).permit(params_list)
end
def params_list
[
:page_id,
:html_identifier,
:title,
:subtitle,
:description,
:image,
:image_alt,
:image_position,
:article_list_type,
quotes_attributes: [:id, :author, :quote_text, :_destroy],
]
end
end
end
Partial Section_quote form in haml using simple_form.
- section ||= @section
= simple_form_for [:admin, section], as: :section, url: section.persisted? ?
admin_section_path(section) : admin_sections_path, remote: true, format: :js,
multipart: true do |f|
= f.input :page_id, as: :hidden
= f.input :type, as: :hidden
= f.input :html_identifier
= f.input :title
=f.simple_fields_for :quotes, Quote.new do |quote_form|
= quote_form.input :author
= quote_form.input :quote_text
= f.input :image, hint: 'Dimensioni: 690x920px'
.row
.col-xs-12.col-md-6
= webp_image_tag section, {width: 400, height: 210, class: 'mb-1 img-fluid'}
.col-xs-12.col-md-6
= f.input :image_alt
= f.submit class: 'btn btn btn-primary'
What I expect is to also preserve the quotes data in the section form so it can render to the view layout.
The problem is:
= f.simple_fields_for :quotes, Quote.new do |quote_form|
You have a one to many relation but you're passing a single instance of Quote to the helper. Creating model instances in your view is actually an anti-pattern. You'll lose any user input when re-rendering the form after an invalid form submission. The view should take its data from the controller and turn it into markup in the simplest way possible - it should not be concerned with instanciating models.
Instead you should "seed" the assocation in your controller:
module Admin
class SectionsController < Admin::AdminController
# ...
def new
@section = Section.new
3.times do
@section.quotes.new
end
end
end
end
And just call the assocation in your view:
= f.simple_fields_for :quotes do |quote_form|