Rails accepts_nested_attributes_for + HABM returns 一个包含空字符串的数组

Rails accepts_nested_attributes_for + HABM returns an array with empty string

查看我的问题:

class MedicalRecord < ActiveRecord::Base
  has_many :evaluations, dependent: :destroy

  accepts_nested_attributes_for :evaluations, allow_destroy: true, reject_if: :all_blank
end

class Evaluation < ActiveRecord::Base
  belongs_to :medical_record

  has_and_belongs_to_many :edemas

  validates :description, presence: true
end

我的表单显示 select 个具有多个属性的字段。

<%= form_for @medical_record do |f| %>
  <%= f.fields_for :evaluations do |e| %>
    <%= e.text_field :description %>

    <%= e.collection_select :edema_ids, Edema.all.order(:title), :id, :title,
      { }, multiple: true %>
  <% end %>      
<% end %>

当我不select任何'edema'时,表单发送一个包含一个空字符串的数组。因此,reject_if returns false 和我需要填写描述字段。在这种情况下,reject_if 应该 return true

我该怎么做才能让它也起作用?

非常感谢

您可能想要编写自定义 reject_if 方法。如果您提供符号,ActiveRecord 会在当前 class 中查找具有该名称的方法,并为 evaluation.

传递提交的属性散列
class MedicalRecord < ActiveRecord::Base
  accepts_nested_attributes_for :evaluations, allow_destroy: true, reject_if: :essentially_blank

  def essentially_blank(attributes)
    attributes[:description].blank? && attributes[:edema_ids][0].blank?
  end
end

请注意,虽然 [""].blank? 为假 "".blank? 为真,这就是我获取数组的第一个对象并检查其 blank-ness 的原因。

您可以查看 the Rails API 以查看有关如何在 reject_if 上完成自定义行为的更多示例。

如果您的目标只是让 :edema_ids 数组中没有空字符串,您可以将 include_hidden: false 传递给 collection_select

也就是说,如果您最终想要 "deselect" 现有评估的所有水肿,这将导致问题,因为如果 :edema_ids 数组的值确实为空,浏览器根本不会发送它(因为它是多个 select),出于大致相同的原因,它不会发送未选中的复选框值。在原本为空的数组中包含空字符串是表单助手处理此浏览器行为的方式。

相信允许HTML表单提交空字符串数组元素是明智的(这样浏览器和Rails都可以按预期运行在这种情况下),并在 :evaluations 关联上使用 ActiveRecord 的 reject_if: :all_blank(假设这确实是您正在寻找的行为)。

您可以通过在将 :edema_ids 传递到控制器之后但在对其求值之前的某个时刻从 :edema_ids 中剥离空字符串元素,从而在没有 运行 的情况下实现此目的通过 :all_blank.

类似于:

# Inside MedicalRecordsController

def create
  @medical_record = MedicalRecord.create(medical_record_params)
end

def update
  @medical_record = MedicalRecord.find(params[:id])
  @medical_record.update_attributes(medical_record_params)
end

private

  def medical_record_params
    # I assume you're using strong params to control what can be passed
    # through the controller.  If so, manipulate the params *after*     
    # calling .require() and .permit() on the params hash
    remove_empty_string_from_edema_ids(params)
  end

  def remove_empty_string_from_edema_ids(params_hash) # Use a better name than this
    params_hash[:evaluations].each do |evaluation|
      # Don't forget to use guard clause to prevent calling a
      # method on nil if :edema_ids is not present in evaluation
      evaluation[:edema_ids].reject!(&:empty) if evaluation[:edema_ids]
    end
  end

这个(或类似的东西,因为有很多方法可以达到相同的结果)除了正确地导致 :all_blank 到 return true 如果 :evaluation truly is all blank — 导致将一个真正的空数组传递给评估的 edema_ids=() 方法;由于一些 ActiveRecord 关联魔法,这将导致从该 Evaluation 实例中删除所有 :edemas