Cocoon gem,在编辑或出现错误时未建立深度关联
Cocoon gem, deep association not built when edit or error raised
我正在使用 Cocoon gem 构建一个嵌套表单,其中 field_for 包含另一个 field_for。层次结构如下所示:
-信件形式
-卡片field_for
-按钮field_for
我有一个 link_to_add_association
可以动态添加带有按钮的卡片。这部分看起来像这样:
<div class="connected-carousels">
<div class="stage">
<div class="carousel carousel-stage">
<ul id="carousel-stage-ul">
<%= f.fields_for :cards do |card_fields| %>
<% if @blabla %>
<%= render 'card_fields', f: card_fields %>
<% end %>
<% end %>
<% end %>
</ul>
</div>
<a href="#" class="prev prev-stage"><span>‹</span></a>
<a href="#" class="next next-stage"><span>›</span></a>
</div>
<div class="navigation">
<a href="#" class="prev prev-navigation">‹</a>
<a href="#" class="next next-navigation">›</a>
<div class="carousel carousel-navigation">
<ul id="carousel-navigation-ul"></ul>
</div>
</div>
<div>
<%= link_to_add_association '+ Add Card', f, :cards, id: 'add-card-button-bis', data: { association_insertion_node: '#carousel-stage-ul', association_insertion_method: :append } %>
</div>
</div>
_card_fields.html.erb 部分渲染按钮:
<% f.object.buttons.build %>
<%= f.fields_for :buttons do |button_card_fields| %>
<%= render 'button_fields', f: button_card_fields %>
<% end %>
_button_fields.html.erb部分:
<div class="add-button-card-modal">
<h4>Add New Button</h4>
<label>Button Text</label>
<%= f.text_field :button_text, :maxlength => 20, placeholder: "Enter the text to display on the button..." %>
<br><br>
<label>Button URL</label>
<%= f.text_field :button_url, placeholder: "Paste URL..." %>
<div class="nav-popups-buttons">
<button type="button" id="validate_new_card_button" class="small-cta2">Add Button</button>
<p class="remove-link" id="delete_new_card_button">Remove Button</p>
</div>
</div>
字母型号:
class Letter < ApplicationRecord
validates :campaign_name, :presence => true
belongs_to :core_bot
has_many :messages, dependent: :destroy
has_many :cards, dependent: :destroy
has_many :filters, dependent: :destroy
has_many :analytic_deliveries, dependent: :destroy
has_many :analytic_reads, dependent: :destroy
has_many :analytic_sends, dependent: :destroy
accepts_nested_attributes_for :filters, allow_destroy: true, :reject_if => :all_blank
accepts_nested_attributes_for :messages, allow_destroy: true, :reject_if => :all_blank
accepts_nested_attributes_for :cards, allow_destroy: true, :reject_if => :all_blank
end
卡片型号:
class Card < ApplicationRecord
validates :remote_image_url, :format => URI::regexp(%w(http https)), presence: { message: '%{value} : please enter valid url' }, :allow_blank => true
validates :title, :subtitle, :presence => true
belongs_to :letter, optional: true
has_many :buttons, dependent: :destroy
accepts_nested_attributes_for :buttons, :reject_if => Proc.new { |att| att[:button_text].blank? && att[:button_url].blank? }, allow_destroy: true
end
按键型号:
class Button < ApplicationRecord
validates :button_url, :format => URI::regexp(%w(http https)), presence: { message: '%{value} : please enter valid url' },
unless: Proc.new { |a| a.button_url.blank? }
validates :button_text, :presence => true, unless: Proc.new { |a| a.button_url.blank? }
belongs_to :message, optional: true
belongs_to :card, optional: true
has_one :short_url, dependent: :destroy
end
在出现错误的情况下在字母控制器中创建操作:
@letter.filters.build
if params[:letter]['cards_attributes'].present? == false
@letter.cards.build.buttons.build
end
format.html { render :new }
format.json { render json: @letter.errors, status: :unprocessable_entity }
问题是创建操作引发错误或编辑记录时未构建按钮。如果我将 @letter.cards.build.buttons.build
添加到控制器中,它会添加一张新卡,但按钮输入仍然不会出现。
更新
事实上按钮是内置的。唯一的问题是我的自定义 jquery 我用来显示显示按钮字段的弹出窗口不起作用,因为它们是在 cocoon:after-insert 回调中创建的。
当出现错误或在编辑视图中出现错误时,我创建的卡片将被呈现,而不是由 link_to_add_association 添加。这个 cocoon:after-insert 回调没有被调用...
知道如何使用与 link_to_add_association 在呈现的卡片字段上调用插入后回调的相同逻辑吗?
这是控制台测试,我的按钮在这里:
好的,通常在其中一个问题中隐藏着几个问题。这个答案只是为了解决为什么 Edit
视图总是加载失败的问题。
我最初的猜测是在控制器中,有两个问题...... if params
和 @letter.cards.build.buttons.build
是问题所在,而不是尝试围绕它进行设计 - 你需要改变它,接受无论错误是什么,然后修复模型中的错误。第一个修复是从那里获取条件 if params
。
我也注意到你的字母模型上没有 accept_nested_attributes_for :buttons
。
最后,在我们开始解决问题之前 - 我忘了询问你的 strong_params 部分 - 请 post create
和 strong_params 方法的整个控制器操作在控制器的私有部分。
因为它根本没有加载。曾经。这可能不完全是 jquery 问题。我们应该看到一个 rails 生成的所有属于该模型的卡片和按钮的集合。
我猜 .build
方法是第一个问题...
我想如果你检查 rails 控制台 ... rails c
...
首先,尝试调用 @test = Letter.first
,然后在它设置时探索它的值...您应该能够在其中看到其他 ID,然后能够使用 @test.{whatever}
调用它们..即:@test.cards
将通过模型中的关系给出所有存在的卡片的集合。
其次,如果第一个失败,@test.cards.create!(name: "test")
查看关系是否正确创建条目。如果控制台的 @letter.cards.buttons
中出现任何内容,我会很高兴,然后我们稍后会解决您层次结构的其他分支 (letter.filters
)。
第三,我们移动通过控制台生成测试数据,以确保这些值存在并通过 rails 控制器馈送到要显示的视图。
第四,我们检查 jquery 是否干扰了它们的显示。
演示代码
这是 3 个深度嵌套......
请注意,所有这些都对 EDIT
有效且不费吹灰之力,_form.html.erb
中 new/create 的设置只是自动用于编辑。
型号
country.rb
class Country < ApplicationRecord
has_many :states
has_many :counties, through: :states
accepts_nested_attributes_for :states, reject_if: proc { |attributes| attributes[:name].blank? }, allow_destroy: true
accepts_nested_attributes_for :counties, reject_if: proc { |attributes| attributes[:name].blank? }, allow_destroy: true
end
state.rb
class State < ApplicationRecord
belongs_to :country
has_many :counties
validates :name, presence: true
# can't recall if this is needed
accepts_nested_attributes_for :counties, reject_if: proc { |attributes| attributes[:name].blank? }, allow_destroy: true
end
county.rb
class County < ApplicationRecord
belongs_to :state
validates :name
end
countries_controller.rb ...控制器(你甚至不需要为别人生成控制器)
class CountriesController < ApplicationController
before_action :set_country, only: [:show, :edit, :update, :destroy]
def index
@countries = Country.paginate(page: params[:page], per_page: 10)
end
def show
end
def new
@country = Country.new
end
def edit
# @partial_choice = params[:partial_choice]
end
def create
@country = Country.new(country_params)
respond_to do |format|
if @country.save
format.html { redirect_to @country, notice: 'Country was successfully created.' }
format.json { render :show, status: :created, location: @country }
else
format.html { render :new }
format.json { render json: @country.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if @country.update!(country_params)
format.html { redirect_to @country, notice: 'Country was successfully updated.' }
format.json { render :show, status: :ok, location: @country }
else
format.html { render :edit }
format.json { render json: @country.errors, status: :unprocessable_entity }
end
end
end
def destroy
@country.destroy
respond_to do |format|
format.html { redirect_to countries_url, notice: 'Country was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_country
@country = Country.find(params[:id])
end
def country_params
params.require(:country).permit(:id, :name, :description, :size, :player_id,
countryneighbor_attritubtes: [:id, :bordercountry_id, :country_id, :_destroy],
states_attributes: [:id, :name, :description, :country_id, :_destroy,
counties_attributes: [:id, :name, :description, :state_id, :_destroy]])
end
end
视图 - 如果您已经在 rails 控制台中测试了数据和关系,strong_params
并在 log/development.log
中检查了服务器日志,那么也许可以比较 HAML 样式调用在此处的视图中...
/basicB/app/views/countries/_form.html.haml
.fieldset.form-inline
= simple_form_for @country do |f|
= f.error_notification
.countries
= f.input :name, input_html: {:placeholder => "...insert country name..."}
= f.input :description
.row-fuild
%a.btn.btn-primary{"aria-controls" => "neighborsDisplay", "aria-expanded" => "false", "data-toggle" => "collapse", :href => "#neighborsDisplay"} Show Neighbors
#neighborsDisplay.collapse
.list-group
- for neighbor in @country.neighbors
= render partial: 'country_neighbors', locals: {neighbor: neighbor}
.states
= f.simple_fields_for :states do |state|
= render 'state_fields', :f => state
.links.row
= link_to_add_association 'Add State', f, :states, render_options: { wrapper: 'inline_form' }, :class => "btn btn-default"
.form-actions.row
= f.button :submit
/app/views/countries/_state_fields.html.haml
.nested-fields.list-group-item
.well
%h4 State
.form-inline.text
= f.input :name, input_html: {:placeholder => "...insert State Name ..."}
= f.input :description
= link_to_remove_association f, class: 'btn btn-default btn-xs' do
.glyphicon.glyphicon-remove
.counties
= f.simple_fields_for :counties do |state|
= render 'county_fields', :f => state
.links.row
= link_to_add_association 'Add County', f, :counties, render_options: { wrapper: 'inline_form' }, :class => "btn btn-default"
/app/views/countries/_county_fields.html.haml
.nested-fields.list-group-item.my-well
.row.text
= f.input :name, input_html: {:placeholder => "... County Name ..."}
= f.input :description
= link_to_remove_association f, class: 'btn btn-default btn-xs' do
.glyphicon.glyphicon-remove
我正在使用 Cocoon gem 构建一个嵌套表单,其中 field_for 包含另一个 field_for。层次结构如下所示: -信件形式 -卡片field_for -按钮field_for
我有一个 link_to_add_association
可以动态添加带有按钮的卡片。这部分看起来像这样:
<div class="connected-carousels">
<div class="stage">
<div class="carousel carousel-stage">
<ul id="carousel-stage-ul">
<%= f.fields_for :cards do |card_fields| %>
<% if @blabla %>
<%= render 'card_fields', f: card_fields %>
<% end %>
<% end %>
<% end %>
</ul>
</div>
<a href="#" class="prev prev-stage"><span>‹</span></a>
<a href="#" class="next next-stage"><span>›</span></a>
</div>
<div class="navigation">
<a href="#" class="prev prev-navigation">‹</a>
<a href="#" class="next next-navigation">›</a>
<div class="carousel carousel-navigation">
<ul id="carousel-navigation-ul"></ul>
</div>
</div>
<div>
<%= link_to_add_association '+ Add Card', f, :cards, id: 'add-card-button-bis', data: { association_insertion_node: '#carousel-stage-ul', association_insertion_method: :append } %>
</div>
</div>
_card_fields.html.erb 部分渲染按钮:
<% f.object.buttons.build %>
<%= f.fields_for :buttons do |button_card_fields| %>
<%= render 'button_fields', f: button_card_fields %>
<% end %>
_button_fields.html.erb部分:
<div class="add-button-card-modal">
<h4>Add New Button</h4>
<label>Button Text</label>
<%= f.text_field :button_text, :maxlength => 20, placeholder: "Enter the text to display on the button..." %>
<br><br>
<label>Button URL</label>
<%= f.text_field :button_url, placeholder: "Paste URL..." %>
<div class="nav-popups-buttons">
<button type="button" id="validate_new_card_button" class="small-cta2">Add Button</button>
<p class="remove-link" id="delete_new_card_button">Remove Button</p>
</div>
</div>
字母型号:
class Letter < ApplicationRecord
validates :campaign_name, :presence => true
belongs_to :core_bot
has_many :messages, dependent: :destroy
has_many :cards, dependent: :destroy
has_many :filters, dependent: :destroy
has_many :analytic_deliveries, dependent: :destroy
has_many :analytic_reads, dependent: :destroy
has_many :analytic_sends, dependent: :destroy
accepts_nested_attributes_for :filters, allow_destroy: true, :reject_if => :all_blank
accepts_nested_attributes_for :messages, allow_destroy: true, :reject_if => :all_blank
accepts_nested_attributes_for :cards, allow_destroy: true, :reject_if => :all_blank
end
卡片型号:
class Card < ApplicationRecord
validates :remote_image_url, :format => URI::regexp(%w(http https)), presence: { message: '%{value} : please enter valid url' }, :allow_blank => true
validates :title, :subtitle, :presence => true
belongs_to :letter, optional: true
has_many :buttons, dependent: :destroy
accepts_nested_attributes_for :buttons, :reject_if => Proc.new { |att| att[:button_text].blank? && att[:button_url].blank? }, allow_destroy: true
end
按键型号:
class Button < ApplicationRecord
validates :button_url, :format => URI::regexp(%w(http https)), presence: { message: '%{value} : please enter valid url' },
unless: Proc.new { |a| a.button_url.blank? }
validates :button_text, :presence => true, unless: Proc.new { |a| a.button_url.blank? }
belongs_to :message, optional: true
belongs_to :card, optional: true
has_one :short_url, dependent: :destroy
end
在出现错误的情况下在字母控制器中创建操作:
@letter.filters.build
if params[:letter]['cards_attributes'].present? == false
@letter.cards.build.buttons.build
end
format.html { render :new }
format.json { render json: @letter.errors, status: :unprocessable_entity }
问题是创建操作引发错误或编辑记录时未构建按钮。如果我将 @letter.cards.build.buttons.build
添加到控制器中,它会添加一张新卡,但按钮输入仍然不会出现。
更新 事实上按钮是内置的。唯一的问题是我的自定义 jquery 我用来显示显示按钮字段的弹出窗口不起作用,因为它们是在 cocoon:after-insert 回调中创建的。
当出现错误或在编辑视图中出现错误时,我创建的卡片将被呈现,而不是由 link_to_add_association 添加。这个 cocoon:after-insert 回调没有被调用...
知道如何使用与 link_to_add_association 在呈现的卡片字段上调用插入后回调的相同逻辑吗?
这是控制台测试,我的按钮在这里:
好的,通常在其中一个问题中隐藏着几个问题。这个答案只是为了解决为什么 Edit
视图总是加载失败的问题。
我最初的猜测是在控制器中,有两个问题...... if params
和 @letter.cards.build.buttons.build
是问题所在,而不是尝试围绕它进行设计 - 你需要改变它,接受无论错误是什么,然后修复模型中的错误。第一个修复是从那里获取条件 if params
。
我也注意到你的字母模型上没有 accept_nested_attributes_for :buttons
。
最后,在我们开始解决问题之前 - 我忘了询问你的 strong_params 部分 - 请 post create
和 strong_params 方法的整个控制器操作在控制器的私有部分。
因为它根本没有加载。曾经。这可能不完全是 jquery 问题。我们应该看到一个 rails 生成的所有属于该模型的卡片和按钮的集合。
我猜 .build
方法是第一个问题...
我想如果你检查 rails 控制台 ... rails c
...
首先,尝试调用 @test = Letter.first
,然后在它设置时探索它的值...您应该能够在其中看到其他 ID,然后能够使用 @test.{whatever}
调用它们..即:@test.cards
将通过模型中的关系给出所有存在的卡片的集合。
其次,如果第一个失败,@test.cards.create!(name: "test")
查看关系是否正确创建条目。如果控制台的 @letter.cards.buttons
中出现任何内容,我会很高兴,然后我们稍后会解决您层次结构的其他分支 (letter.filters
)。
第三,我们移动通过控制台生成测试数据,以确保这些值存在并通过 rails 控制器馈送到要显示的视图。
第四,我们检查 jquery 是否干扰了它们的显示。
演示代码
这是 3 个深度嵌套......
请注意,所有这些都对 EDIT
有效且不费吹灰之力,_form.html.erb
中 new/create 的设置只是自动用于编辑。
型号
country.rb
class Country < ApplicationRecord
has_many :states
has_many :counties, through: :states
accepts_nested_attributes_for :states, reject_if: proc { |attributes| attributes[:name].blank? }, allow_destroy: true
accepts_nested_attributes_for :counties, reject_if: proc { |attributes| attributes[:name].blank? }, allow_destroy: true
end
state.rb
class State < ApplicationRecord
belongs_to :country
has_many :counties
validates :name, presence: true
# can't recall if this is needed
accepts_nested_attributes_for :counties, reject_if: proc { |attributes| attributes[:name].blank? }, allow_destroy: true
end
county.rb
class County < ApplicationRecord
belongs_to :state
validates :name
end
countries_controller.rb ...控制器(你甚至不需要为别人生成控制器)
class CountriesController < ApplicationController
before_action :set_country, only: [:show, :edit, :update, :destroy]
def index
@countries = Country.paginate(page: params[:page], per_page: 10)
end
def show
end
def new
@country = Country.new
end
def edit
# @partial_choice = params[:partial_choice]
end
def create
@country = Country.new(country_params)
respond_to do |format|
if @country.save
format.html { redirect_to @country, notice: 'Country was successfully created.' }
format.json { render :show, status: :created, location: @country }
else
format.html { render :new }
format.json { render json: @country.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if @country.update!(country_params)
format.html { redirect_to @country, notice: 'Country was successfully updated.' }
format.json { render :show, status: :ok, location: @country }
else
format.html { render :edit }
format.json { render json: @country.errors, status: :unprocessable_entity }
end
end
end
def destroy
@country.destroy
respond_to do |format|
format.html { redirect_to countries_url, notice: 'Country was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_country
@country = Country.find(params[:id])
end
def country_params
params.require(:country).permit(:id, :name, :description, :size, :player_id,
countryneighbor_attritubtes: [:id, :bordercountry_id, :country_id, :_destroy],
states_attributes: [:id, :name, :description, :country_id, :_destroy,
counties_attributes: [:id, :name, :description, :state_id, :_destroy]])
end
end
视图 - 如果您已经在 rails 控制台中测试了数据和关系,strong_params
并在 log/development.log
中检查了服务器日志,那么也许可以比较 HAML 样式调用在此处的视图中...
/basicB/app/views/countries/_form.html.haml
.fieldset.form-inline
= simple_form_for @country do |f|
= f.error_notification
.countries
= f.input :name, input_html: {:placeholder => "...insert country name..."}
= f.input :description
.row-fuild
%a.btn.btn-primary{"aria-controls" => "neighborsDisplay", "aria-expanded" => "false", "data-toggle" => "collapse", :href => "#neighborsDisplay"} Show Neighbors
#neighborsDisplay.collapse
.list-group
- for neighbor in @country.neighbors
= render partial: 'country_neighbors', locals: {neighbor: neighbor}
.states
= f.simple_fields_for :states do |state|
= render 'state_fields', :f => state
.links.row
= link_to_add_association 'Add State', f, :states, render_options: { wrapper: 'inline_form' }, :class => "btn btn-default"
.form-actions.row
= f.button :submit
/app/views/countries/_state_fields.html.haml
.nested-fields.list-group-item
.well
%h4 State
.form-inline.text
= f.input :name, input_html: {:placeholder => "...insert State Name ..."}
= f.input :description
= link_to_remove_association f, class: 'btn btn-default btn-xs' do
.glyphicon.glyphicon-remove
.counties
= f.simple_fields_for :counties do |state|
= render 'county_fields', :f => state
.links.row
= link_to_add_association 'Add County', f, :counties, render_options: { wrapper: 'inline_form' }, :class => "btn btn-default"
/app/views/countries/_county_fields.html.haml
.nested-fields.list-group-item.my-well
.row.text
= f.input :name, input_html: {:placeholder => "... County Name ..."}
= f.input :description
= link_to_remove_association f, class: 'btn btn-default btn-xs' do
.glyphicon.glyphicon-remove