Ruby on Rails:如何在特定条件下验证嵌套属性?

Tin*_*n81 9 ruby validation ruby-on-rails ruby-on-rails-3

我有这些模型:

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
Run Code Online (Sandbox Code Playgroud)

......以及这些观点:

_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 %>
Run Code Online (Sandbox Code Playgroud)

_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>
Run Code Online (Sandbox Code Playgroud)

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"=>"?"}
Run Code Online (Sandbox Code Playgroud)

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

我试过这样的事情:

validates :address, :presence => true, :if => "organisation_id.blank?"
Run Code Online (Sandbox Code Playgroud)

但它不起作用.

如何才能做到这一点?

谢谢你的帮助.

Mat*_*sel 27

首先,我想确定你的意思blank?而不是present?.通常,我看到这个:

validate :address, :presence_of => true, :if => 'organisation.present?'
Run Code Online (Sandbox Code Playgroud)

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

关于,:accepts_nested_attributes_for您是否通过传入嵌套表单属性或某些此类内容来使用此功能?我只想确保您绝对需要使用此功能.如果您实际上没有处理嵌套表单属性,则可以使用以下命令实现级联验证:

validates_associated :address
Run Code Online (Sandbox Code Playgroud)

如果确实需要使用: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
Run Code Online (Sandbox Code Playgroud)

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

它应该工作,:if/:unless采取符号,字符串和过程.您不需要指向foreign_key,但可以通过指向以下内容进行简化:

:if => "organisation.blank?"
Run Code Online (Sandbox Code Playgroud)

您在地址模型中有其他验证,对吗?地址是否在您不希望时进行验证?或者地址未经过验证?如果你能给我一些额外的细节,我可以帮你在控制台测试一下.


  1. 为了让自己更轻松:大规模分配,我更改了rails配置: config.active_record.whitelist_attributes = false
  2. 为你创造了一个要点
  3. 我也有一个示例项目.如果您有兴趣,请告诉我.

    基本要点:

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

    validates_presence_of :organisation, :unless => "address.present?" validates_associated :address, :unless => "organisation.present?"

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

    我能够满足您的要求.请查看我创建的完整控制台测试计划的要点.


在前一个要点中添加了一个控制器文件.

概述:

  1. 你需要创造的人是: @person = current_user.people.build(params[:person])
  2. :organisation_id总是会出现在:person param节点上,就像这样: params[:person][:organisation_id] 所以你永远不会是真的.

我更新了要点,对控制器,模型表格进行了必要的更改.

概述:

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

    这都是要点


应该是最后的要点.

概述:

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

    @ person.build_address if @ person.address.nil?这确保您具有地址输入,即使@ person.address不存在也是如此.它不存在,因为在accepts_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  
    
    Run Code Online (Sandbox Code Playgroud)

    一个.attrs- > params的结果[:person] [:address]
    b..except('id')- >返回除'id'c之外的所有键值
    ..values- >将散列中的所有值作为数组
    d返回..all?- >数组中的所有元素都满足以下检查
    e.&:blank - > ruby​​简写为一个块,像这样:all?{ |v| v.blank? }