使空白参数为零

当用户提交表单并将某些字段留空时,它们将在数据库中保存为空白。 我想迭代params [:user]集合(例如),如果字段为空,则在更新属性之前将其设置为nil。 我无法弄清楚如何做到这一点虽然我知道迭代的唯一方法创建新对象:

coll = params[:user].each do |c| if c == "" c = nil end end 

谢谢。

通过在控制器中使用filter来考虑模型在保存或更新时的行为方式,考虑您在此处所做的事情。 我认为一个更清晰的方法是在模型或观察者中调用before_save 。 这样,无论更改源自何处,无论是通过控制器,控制台还是运行批处理,您都可以获得相同的行为。

例:

 class Customer < ActiveRecord::Base NULL_ATTRS = %w( middle_name ) before_save :nil_if_blank protected def nil_if_blank NULL_ATTRS.each { |attr| self[attr] = nil if self[attr].blank? } end end 

这会产生预期的行为:

 >> c = Customer.new => # >> c.first_name = "Matt" => "Matt" >> c.middle_name = "" # blank string here => "" >> c.last_name = "Haley" => "Haley" >> c.save => true >> c.middle_name.nil? => true >> 

如果你只想杀死空白,你可以做params.delete_if {|k,v| v.blank?} params.delete_if {|k,v| v.blank?}

在模型中处理此问题的好gem: https : //github.com/rmm5t/strip_attributes

它定义了一个before_validation钩子,用于修剪空格并将空字符串设置为nil。

before_save对我来说似乎是错误的位置,如果你想在保存前使用该值,该怎么办? 所以我改为反对安装者:

 # include through module or define under active_record def self.nil_if_blank(*args) args.each do |att| define_method att.to_s + '=' do |val| val = nil if val.respond_to?(:empty?) && val.empty? super(val) end end end #inside model nil_if_blank :attr1, :attr2 

为了完成,我将以下内容放在lib / my_model_extensions.rb中

 module MyModelExtensions def self.included(base) base.class_eval do def self.nil_if_blank(*args) args.each do |att| define_method att.to_s + '=' do |val| val = nil if val.respond_to?(:empty?) && val.empty? super(val) end end end end end end 

并像这样使用它:

 class MyModel include MyModelExtensions nil_if_blank :attr1, :attr2 end 

通常我会鼓励将function移入模型中,如其他答案中所述,这意味着无论变化源自何处,您都将获得相同的行为。

但是,我不认为在这种情况下它是正确的。 注意到的影响纯粹是由于无法编码HTTP请求中空字符串和nil值之间的差异。 因此,应在控制器级别进行补救。 这也意味着在其他地方仍然可以在模型中存储一个空字符串(这可能是出于合法的原因,如果不是,它很容易用标准validation来覆盖)。

我用来克服这个问题的代码是:

 # application_controller.rb ... def clean_params @clean_params ||= HashWithIndifferentAccess.new.merge blank_to_nil( params ) end def blank_to_nil(hash) hash.inject({}){|h,(k,v)| h.merge( k => case v when Hash : blank_to_nil v when Array : v.map{|e| e.is_a?( Hash ) ? blank_to_nil(e) : e} else v == "" ? nil : v end ) } end ... 

虽然可读性有所下降,但我试图尽可能简洁地保持代码简洁,所以这里有一个测试用例来展示它的function:

 require "test/unit" class BlankToNilTest < Test::Unit::TestCase def blank_to_nil(hash) hash.inject({}){|h,(k,v)| h.merge( k => case v when Hash : blank_to_nil v when Array : v.map{|e| e.is_a?( Hash ) ? blank_to_nil(e) : e} else v == "" ? nil : v end ) } end def test_should_convert_blanks_to_nil hash = {:a => nil, :b => "b", :c => ""} assert_equal( {:a => nil, :b => "b", :c => nil}, blank_to_nil(hash) ) end def test_should_leave_empty_hashes_intact hash = {:a => nil, :b => "b", :c => {}} assert_equal( {:a => nil, :b => "b", :c => {}}, blank_to_nil(hash) ) end def test_should_leave_empty_arrays_intact hash = {:a => nil, :b => "b", :c => []} assert_equal( {:a => nil, :b => "b", :c => []}, blank_to_nil(hash) ) end def test_should_convert_nested_hashes hash = {:a => nil, :b => "b", :c => {:d => 2, :e => {:f => "", :g => "", :h => 5}, :i => "bar"}} assert_equal( {:a => nil, :b => "b", :c => {:d => 2, :e => {:f => nil, :g => nil, :h => 5}, :i => "bar"}}, blank_to_nil(hash) ) end def test_should_convert_nested_hashes_in_arrays hash = {:book_attributes => [{:name => "b", :isbn => "" },{:name => "c", :isbn => "" }], :shelf_id => 2} assert_equal( {:book_attributes => [{:name => "b", :isbn => nil},{:name => "c", :isbn => nil}], :shelf_id => 2}, blank_to_nil(hash)) end def test_should_leave_arrays_not_containing_hashes_intact hash = {:as => ["", nil, "foobar"]} assert_equal( {:as => ["", nil, "foobar"]}, blank_to_nil(hash)) end def test_should_work_with_mad_combination_of_arrays_and_hashes hash = {:as => ["", nil, "foobar", {:b => "b", :c => "", :d => nil, :e => [1,2,3,{:a => "" }]}]} assert_equal( {:as => ["", nil, "foobar", {:b => "b", :c => nil, :d => nil, :e => [1,2,3,{:a => nil}]}]}, blank_to_nil(hash)) end end 

然后可以在控制器中使用它,如下所示:

 ... @book.update_attributes(clean_params[:book]) ... 

您可以使用attribute_normalizer gem并使用将使用nil值转换空字符串的空白规范化器。

你可以使用inject执行此操作,这对于发生的事情很明显。

 params = params.inject({}){|new_params, kv| new_params[kv[0]] = kv[1].blank? ? nil : kv[1] new_params } 

还有一个hack你可以通过与自身合并进行合并,并传递一个块来处理新值(虽然这不是真正的用途,但它更简洁)

 params.merge(params){|k, v| v.blank? ? nil : v} 

使用“就地”收集方法(也称为地图!)

 params[:user].collect! {|c| c == "" ? nil : c} 

克里斯,

这是对具有blanc值的params的递归解析。

 before_filter :process_params ...... private def process_params .... set_blanc_values_to_nil(params) end # Maybe move method to ApplicationController # recursively sets all blanc values to nil def set_blanc_values_to_nil!(my_hash) my_hash.keys.each do |key| val = my_hash[key] next if val.nil? my_hash[key] = nil if val.is_a?(String) && val.empty? set_blanc_values_to_nil!(val) if val.is_a? Hash end end 

我概括了一个答案并制作了一个可用作初始化器的钩子/扩展。 这允许它在多个模型中使用。 我在GitHub上添加了它作为ActiveRecordHelpers repo的一部分

我是这样做的。

 def remove_empty_params(param, key) param[key] = param[key].reject { |c| c.empty? } end 

并称之为

 remove_empty_params(params[:shipments], :included_clients) 

不需要在模型中变得非常棘手。 通过这种方式,您可以控制哪些参数被清理干净。

 params = { "shipments"=>{ "included_clients" => ["", "4"] } } 

会变成

 >> params["shipments"] => {"included_clients" => ["4"] } 

如果您知道要将空白编码为nils的属性,则可以使用以下属性setter override:

 def colour=(colour) super(colour.blank? ? nil : colour) end 

如果你有很多属性需要覆盖,有点笨重。