【问题标题】:Ruby on Rails: How to validate nested attributes on certain condition?Ruby on Rails:如何在特定条件下验证嵌套属性?
【发布时间】:2023-03-31 21:49:01
【问题描述】:

我有这些模型:

class Organisation < ActiveRecord::Base

  has_many    :people
  has_one     :address, :as         => :addressable,
                        :dependent  => :destroy
  accepts_nested_attributes_for :address, :allow_destroy => true

end

class Person < ActiveRecord::Base

  attr_accessible :first_name, :last_name, :email, :organisation_id, :address_attributes

  belongs_to  :user
  belongs_to  :organisation
  has_one     :address, :as         => :addressable,
                        :dependent  => :destroy
  accepts_nested_attributes_for :address, :allow_destroy => true

  # These two methods seem to have no effect at all!
  validates_presence_of :organisation,  :unless => "address.present?"
  validates_associated  :address,       :unless => "organisation.present?"

end

class Address < ActiveRecord::Base

  belongs_to :addressable, :polymorphic => true

  validates_presence_of :line1, :line2, :city, :zip

end

...以及这些观点:

_fields.html.erb

<%= render 'shared/error_messages', :object => f.object %>
<fieldset>
<div class="left">
    <%= f.label :first_name %><br/>
    <%= f.text_field :first_name %>
</div>
<div>
    <%= f.label :last_name %><br/>
    <%= f.text_field :last_name %>
</div>
<div>
    <%= f.label :email %><br/>
    <%= f.text_field :email %>
</div>
<div>
    <%= f.label :organisation_id %><br/>
    <%= f.select(:organisation_id, current_user.organisation_names, {:include_blank => "--- None ---"}, :id => 'organisation_select') %>
</div>
</fieldset>

<%= f.fields_for :address do |address| %>
  <%= render 'shared/address', :f => address %>
<% end %>

_address.html.erb:

<fieldset id="address_fields">
<div>
    <%= f.label :line1 %>
    <%= f.text_field :line1 %>
</div>
<div>
    <%= f.label :line2 %>
    <%= f.text_field :line2 %>
</div>
<div>
    <%= f.label :zip %>
    <%= f.text_field :zip %>
</div>  
<div>
    <%= f.label :city %>
    <%= f.text_field :city %>
</div>  
</fieldset>

people_controller.rb

def new
  puts params.inspect
  @person = Person.new(:organisation_id => params[:organisation_id])
  @person.build_address
  @title = "New person"
end

{"action"=>"new", "controller"=>"people"}

def edit
  puts params.inspect
  @title = @person.name
end

{"action"=>"edit", "id"=>"69", "controller"=>"people"}

def create
  puts params.inspect
  if params[:organisation_id]
    @person = current_user.organisations.build_person(params[:person])
  else
    @person = current_user.people.build(params[:person])
  end
  if @person.save
    flash[:success] = "Person created."
    redirect_to people_path
  else
    render :action => "new"
  end
end

{"commit"=>"Create", "action"=>"create", "person"=>{"last_name"=>"Doe", "organisation_id"=>"9", "email"=>"john.doe@email.com", "first_name"=>"John", "address_attributes"=>{"city"=>"Chicago", "zip"=>"12345", "line2"=>"Apt 1", "line1"=>"1 Main Street"}}, "authenticity_token"=>"Jp3XVLbA3X1SOigPezYFfEol0FGjcMHRTy6jQeM1OuI=", "controller"=>"people", "utf8"=>"✓"}

在我的Person 模型中,我需要确保只有当某人的organisation_id 为空白时,该人的地址字段才必须存在。

我尝试过这样的事情:

validates :address, :presence => true, :if => "organisation_id.blank?"

但它不起作用。

如何做到这一点?

感谢您的帮助。

【问题讨论】:

标签: ruby-on-rails ruby ruby-on-rails-3 validation


【解决方案1】:

首先,我想确定您指的是blank?,而不是present?。通常,我会看到:

validate :address, :presence_of => true, :if => 'organisation.present?'

意思是,如果组织也存在,您只想验证地址。

关于:accepts_nested_attributes_for,您是否通过传递嵌套表单属性或类似的东西来使用此功能?我只是想确保您绝对需要使用此功能。如果您实际上并未处理嵌套表单属性,则可以使用以下方法实现级联验证:

validates_associated :address

如果您确实需要使用:accepts_nested_attributes,请务必查看:reject_if 参数。基本上,如果某些条件适用,您可以完全拒绝添加属性(及其后代):

accepts_nested_attributes_for :address, :allow_destroy => true, :reject_if => :no_organisation

def no_organisation(attributes)
  attributes[:organisation_id].blank?
end

现在,如果以上都不适用,让我们看看你的语法:

它应该工作,:if/:unless 采取 symbols, strings and procs。您不需要指向foreign_key,但可以通过指向来简化:

:if => "organisation.blank?"

您在地址模型中还有其他验证,对吗?当您不希望地址被验证时,它是否正在被验证?或者地址没有被验证?如果你能给我一些额外的细节,我可以帮助你在控制台中测试它。


  1. 为了让我自己更轻松 re: mass-assignment,我更改了 rails 配置:config.active_record.whitelist_attributes = false
  2. 我创建了a gist for you to follow along
  3. 我也有一个示例项目。如果您有兴趣,请告诉我。

    基本点:

  4. 将以下内容添加到 Person 以确保 Org 或 Address 有效:

    validates_presence_of :organisation, :unless =&gt; "address.present?" validates_associated :address, :unless =&gt; "organisation.present?"

  5. 添加了对地址的验证以在 Org 不存在时触发错误: validates_presence_of :line1, :line2, :city, :zip

    我能够满足您的需求。请查看at the gist I created,我有完整的控制台测试计划。


我添加了a controller file to the previous gist

概述:

  1. 创建人员只需要: @person = current_user.people.build(params[:person])
  2. :organisation_id 将始终从 :person 参数节点中找到,如下所示: params[:person][:organisation_id] 所以你的假设永远不会是真的。

我更新了要点,对the controllerthe modelthe form 进行了必要的更改。

概述:

  1. 您需要清理控制器。您正在使用accepts_nested_attribute,所以在:create 中,您只关心params[:person]。此外,在render :new 中,您需要设置部分将使用的任何实例变量。这确实通过:new 操作返回。 :new:edit 操作也需要简化。
  2. 您的 Person 模型需要使用 :reject_if 参数,因为 Address 字段将作为 :address_attributes =&gt; {:line1 =&gt; '', :line2 =&gt; '', etc} 返回到 :create 操作。如果有任何值,您只想创建关联。然后你的validates_presence_of:organisation 就可以正常工作了。
  3. 您的表单需要将组织 ID 传递给控制器​​,而不是组织名称

    一切尽在the gist


应该是the final gist

概述:

  1. 在构建@person 之后立即将以下内容添加到您的编辑操作中:

    @person.build_address 如果@person.address.nil? 这确保您有地址输入,即使 @person.address 不存在。它不存在,因为 accept_nested_attributes 上的 :reject_if 条件

  2. 我将 :reject_if 干燥如下。这有点hacky,但有一些实用性:

    accepts_nested_attributes_for :address, :allow_destroy => true, :reject_if => :attributes_blank?
    
    def attributes_blank?(attrs)  
      attrs.except('id').values.all?(&:blank?)  
    end  
    

    一个。 attrs -> params[:person][:address]的结果
    湾。 .except('id') -> 返回除“id”之外的所有键值
    C。 .values -> 将哈希中的所有值作为数组返回
    d。 .all? -> 数组中的所有元素是否满足以下检查
    e. &amp;:blank -> 块的 ruby​​ 简写,像这样:all?{ |v| v.blank? }

【讨论】:

  • 顺便说一句,我一直在输入“组织”作为“组织”,所以可能有一些拼写错误。
  • 您好,马特,感谢您的帮助。我将最初的答案扩展了一点,并发布了视图代码。事实上,我在 Person 模型中使用了:accepts_nested_attributes_for,因此它也接受了相关人员的地址数据。这不是个好主意吗?老实说,我没能让你的代码正常工作,尽管它肯定比我的好很多。
  • 基本上,当创建一个新人时,该人必须属于一个组织。仅当 not 时(即,如果在组织选择框中未选择任何选项),则必须由用户填写该人的嵌套地址属性。不幸的是,我发现这真的很难实现,所以我希望你能帮助我。
  • 你好,马特。非常感谢!!我能够让你的代码在我的控制台上运行,并且通过这样做学到了很多东西。因为我的 Person 和 Organization 模型都需要user_id,所以我只对其做了一些细微的改动。您创建的 5 个测试都按预期通过。 p、p2、p3 返回 true,而 p4 和 p5 返回 false。我猜这就是你所期望的结果?
  • 现在的问题是除了第一个之外,当我在浏览器中创建一个新人时,没有一个场景像这样工作。每当我将地址字段留空时,我都会收到一条错误消息,无论是否在下拉菜单中选择了组织。那么这是否意味着我的控制器有故障?我想知道@person.build_address 行是否会导致验证每次都运行?但我也有点需要它,因为否则我的表单中根本不会显示地址字段。
【解决方案2】:

你确定你不是故意的:

validates :address, :presence => true, :if => organisation_id.nil?

【讨论】:

  • 这也不起作用。我收到 undefined method key? for nil:NilClass 错误。
  • 是的,对不起,这将是否定的 (!organisation_id.nil)。无论如何我都会接受马特的回答
【解决方案3】:

更简单的方法可能是添加自定义验证器。这非常简单,您不必偶然发现语法或试图找出 Rails 的魔法不起作用的原因。

在我的 Person 模型中,我需要确保只有当一个人的 organization_id 为空白时,该人的地址字段才必须存在。

class Person < ActiveRecord::Base
  ...
  validate :address_if_organisation_id_is_present

  private

  def address_if_organisation_id_is_present
    return true unless organisation_id
    errors.add(:address, "cannot be blank") unless address
  end
end

添加到模型的错误将阻止它保存。注意:您可能希望使用address.blank?address.empty?,如其他答案中所讨论的那样,但您可以为您想要的行为定义它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-25
    相关资源
    最近更新 更多