Cocoon gem,编辑或错误时未建立深度关联

Posted

技术标签:

【中文标题】Cocoon gem,编辑或错误时未建立深度关联【英文标题】:Cocoon gem, deep association not built when edit or error raised 【发布时间】:2018-04-13 11:13:20 【问题描述】:

我正在使用Cocoon gem 构建一个嵌套表单,其中一个 field_for 包含另一个 field_for。层次结构如下所示: - 信函形式 -Card field_for -Button 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>&lsaquo;</span></a>
                        <a href="#" class="next next-stage"><span>&rsaquo;</span></a>
                    </div>
              <div class="navigation">
                  <a href="#" class="prev prev-navigation">&lsaquo;</a>
                  <a href="#" class="next next-navigation">&rsaquo;</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 的相同逻辑在渲染的卡片字段上调用插入后回调吗?

这是控制台测试,我的按钮在这里:

【问题讨论】:

Button_fields card_field_partial 是实际文件名还是您有正确的 Rails 名称而只是忘记包含它们? 另外,您是否删除了围绕初始 &lt;%= render 'card_fields', f: card_fields %&gt; 的 IF 语句?您是否设置了测试以确保在表单引发的故障期间 if 语句正确触发? 我用文件名编辑了问题。实际上我以为我需要渲染,但似乎不需要,因为我在其中动态添加了所有字段... 这可能是多余的......但你应该只需要一个.build......它建立了整个关系向后工作......@letter.cards.build.buttons.build,试试@letter.cards.buttons.build 是的,我就是这么做的,问题解决了! 【参考方案1】:

好的,所以通常其中一个问题中会隐藏一些问题。这个答案只是为了解决为什么Edit 视图总是无法加载。

我最初的猜测是在控制器中,有两个问题...... if params@letter.cards.build.buttons.build 是问题&而不是试图围绕它进行设计 - 你需要改变它,接受任何错误然后修复模型中的错误。第一个修复是从那里获取条件if params

我还注意到你的字母模型上没有accept_nested_attributes_for :buttons

最后,在我们开始解决问题之前 - 我忘了询问您的 strong_params 部分 - 请在控制器的私有部分中发布 create 的整个控制器操作和 strong_params 方法。


因为它根本没有加载。曾经。这可能不完全是一个 jquery 问题。我们应该看到一个由模型生成的所有卡片和按钮的集合。

我猜.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 中的新建/创建设置只是自动用于编辑。

型号

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

【讨论】:

我用控制台测试编辑了我的问题,一切正常,只是我在插入后使用的自定义 jquery。拜托,让我们继续讨论与 jquery 部分相关的新问题 ;) 我们可以弹出一个新问题来表明它是一个不同的问题,然后链接两者吗?它使人们在从互联网上关注时更容易关注或过滤掉有用的部分。

以上是关于Cocoon gem,编辑或错误时未建立深度关联的主要内容,如果未能解决你的问题,请参考以下文章

如何将复制按钮集成到使用 cocoon gem 创建的表单中

在 Ruby on Rails 4.2 中使用 Cocoon gem 嵌套表单

Rails 4 & cocoon gem:无法将 3 级子级添加到动态添加的 2 级子级

<ol> <li> 使用 rails partial 和 cocoon gem 实现的问题

一对多 添加表单 cocoon

Rails cocoon嵌套字段,在编辑表单上呈现每个字段旁边的现有图像