Ruby on Rails:如何在特定条件下验证嵌套属性?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12770018/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Ruby on Rails: How to validate nested attributes on certain condition?
提问by Tintin81
I have these models:
我有这些模型:
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
...and these views:
...以及这些观点:
_fields.html.erb:
_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:
_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:
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"=>"[email protected]", "first_name"=>"John", "address_attributes"=>{"city"=>"Chicago", "zip"=>"12345", "line2"=>"Apt 1", "line1"=>"1 Main Street"}}, "authenticity_token"=>"Jp3XVLbA3X1SOigPezYFfEol0FGjcMHRTy6jQeM1OuI=", "controller"=>"people", "utf8"=>"?"}
Inside my Personmodel I need to make sure that only if a person's organisation_idis blank, that person's address fields have to be present.
在我的Person模型中,我需要确保只有当一个organisation_id人的地址为空时,该人的地址字段才必须存在。
I tried something like this:
我试过这样的事情:
validates :address, :presence => true, :if => "organisation_id.blank?"
But it's not working.
但它不起作用。
How can this be done?
如何才能做到这一点?
Thanks for any help.
谢谢你的帮助。
回答by Matt Dressel
First of all, I want to be sure that you mean blank?rather than present?. Typically, I see this:
首先,我想确定您的意思是blank?而不是present?。通常,我看到这个:
validate :address, :presence_of => true, :if => 'organisation.present?'
Meaning, you only want to validate address if organisation is also present.
意思是,如果组织也存在,您只想验证地址。
Regarding, :accepts_nested_attributes_for, are you using this feature by passing in nested form attributes, or some such thing? I just want to make sure you absolutely need to use this functionality. If you are not actually dealing with nested form attributes, you can implement cascading validation using:
关于,:accepts_nested_attributes_for您是通过传递嵌套表单属性还是某些类似的东西来使用此功能?我只是想确保您绝对需要使用此功能。如果您实际上没有处理嵌套的表单属性,则可以使用以下方法实现级联验证:
validates_associated :address
If you do need to use :accepts_nested_attributes, be sure to check out the :reject_ifparameter. Basically, you can reject adding an attribute (and it's descendants) altogether if certain conditions apply:
如果确实需要使用: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
Now, if none of the above apply, let's take a look at your syntax:
现在,如果以上都不适用,让我们来看看你的语法:
It should work, :if/:unlesstake symbols, strings and procs. You don't need to point to the foreign_key, but can simplify by pointing to:
它应该可以工作,:if/:unless采用符号、字符串和 procs。您不需要指向foreign_key,但可以通过指向来简化:
:if => "organisation.blank?"
You have other validations in the Address model, correct? Is Address being validated when you don't want it to? Or is Address not being validated? I can help you test it out in the console if you can give me some additional details.
您在 Address 模型中有其他验证,对吗?当您不希望地址被验证时,它会被验证吗?或者地址没有被验证?如果你能给我一些额外的细节,我可以帮你在控制台中测试它。
- To make things easier for myself re: mass-assignment, I changed the rails config:
config.active_record.whitelist_attributes = false - I created a gist for you to follow along
I have a sample project as well. Let me know if you are interested.
Basic points:
Added the following to Person to ensure that either Org or Address are valid:
validates_presence_of :organisation, :unless => "address.present?"validates_associated :address, :unless => "organisation.present?"Added validation to Address to trigger errors when Org is not present:
validates_presence_of :line1, :line2, :city, :zipI was able to produce the requirements you are seeking. Please look at the gist I createdwhere I have a full console test plan.
- 为了让我自己更轻松:mas-assignment,我更改了 rails 配置:
config.active_record.whitelist_attributes = false - 我创建了一个要点供您遵循
我也有一个示例项目。如果您有兴趣,请告诉我。
基本要点:
将以下内容添加到 Person 以确保 Org 或 Address 有效:
validates_presence_of :organisation, :unless => "address.present?"validates_associated :address, :unless => "organisation.present?"添加了对 Address 的验证以在 Org 不存在时触发错误:
validates_presence_of :line1, :line2, :city, :zip我能够产生您正在寻求的要求。请查看我创建的要点,其中有完整的控制台测试计划。
I added a controller file to the previous gist.
Overview:
概述:
- All you should need to create the person is:
@person = current_user.people.build(params[:person]) - :organisation_id will always be found off of the :person param node, like so:
params[:person][:organisation_id]So you're if will never be true.
- 创建此人所需的全部内容是:
@person = current_user.people.build(params[:person]) - :organisation_id 总是会从 :person 参数节点中找到,就像这样:
params[:person][:organisation_id]所以你如果永远不会是真的。
I updated the gist with the necessary changes to the controller, the modeland the form.
Overview:
概述:
- You need to cleanup your controller. You are using
accepts_nested_attribute, so in the :create, you only care aboutparams[:person]. Additionally, in therender :new, you need to setup any instance variables that the partial will use. This does NOTgo back through the:newaction. The:newand:editactions also need to be simplified. - Your Person model needs to use the
:reject_ifargument because the Address fields are coming back to the :create action as:address_attributes => {:line1 => '', :line2 => '', etc}. you only want to create the association if any have values. Then yourvalidates_presence_offor:organisationwill work just fine. Your form needs to pass the organisation id to the controller, rather than the organisation names
It's all in the gist
- 您需要清理控制器。您正在使用
accepts_nested_attribute,因此在 :create 中,您只关心params[:person]. 此外,在 中render :new,您需要设置部分将使用的任何实例变量。但这不是通过返回:new的动作。该:new和:edit行动也必须简化。 - 您的 Person 模型需要使用该
:reject_if参数,因为 Address 字段将返回到 :create 操作 as:address_attributes => {:line1 => '', :line2 => '', etc}。如果有任何值,您只想创建关联。那么你的validates_presence_offor:organisation将工作得很好。 您的表单需要将组织 ID 传递给控制器,而不是组织名称
一切都在要点中
Should be the final gist.
应该是最后的要点。
Overview:
概述:
Add the following to your edit action right after building the @person:
@person.build_address if @person.address.nil? This ensure that you have the address inputs, even if the @person.address does not exist. It doesn't exist, because of the :reject_if condition on accepts_nested_attributes
I DRYed up the :reject_if as follows. It's a little hacky, but has some utility:
accepts_nested_attributes_for :address, :allow_destroy => true, :reject_if => :attributes_blank? def attributes_blank?(attrs) attrs.except('id').values.all?(&:blank?) enda.
attrs-> the result of params[:person][:address]
b..except('id')-> return all key-values except for 'id'
c..values-> return all values from a hash as an array
d..all?-> do all elements in the array satisfy the following check
e.&:blank-> ruby shorthand for a block, like this:all?{ |v| v.blank? }
在构建@person 后立即将以下内容添加到您的编辑操作中:
@person.build_address 如果@person.address.nil?这确保您有地址输入,即使 @person.address 不存在。它不存在,因为 accepts_nested_attributes 上的 :reject_if 条件
我干了 :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] 的结果
b..except('id')-> 返回除 'id' 之外的所有键值
c..values-> 将散列中的所有值作为数组返回
d..all?-> 数组中的所有元素是否满足以下检查
e.&:blank-> ruby 块的简写,如下所示:all?{ |v| v.blank? }
回答by varatis
Are you sure you didn't mean:
你确定你不是说:
validates :address, :presence => true, :if => organisation_id.nil?
回答by Matt
A more simple approach might be to add a custom validator. It's super easy, and you don't have to stumble on syntax or try to figure out why Rails' magic isn't working.
更简单的方法可能是添加自定义验证器。这非常容易,而且您不必偶然发现语法或试图弄清楚为什么 Rails 的魔法不起作用。
Inside my Person model I need to make sure that only if a person's organisation_id is blank, that person's address fields have to be present.
在我的 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
Adding to a model's errors will prevent it from saving. Note: you may wish to use address.blank?or address.empty?as discussed in other answers, but you can define this for the behavior you'd like.
添加到模型的错误将阻止它保存。注意:您可能希望使用address.blank?或address.empty?在其他答案中讨论,但您可以为您想要的行为定义它。

