使空白参数[]为零

时间:2009-07-26 01:32:17

标签: ruby-on-rails ruby forms collections

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

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

感谢。

13 个答案:

答案 0 :(得分:27)

通过在控制器中使用过滤器来考虑模型在保存或更新时的行为方式,考虑您在此做什么。我认为一个更清晰的方法是在模型或观察者中回调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
=> #<Customer id: nil, first_name: nil, middle_name: nil, last_name: nil>
>> c.first_name = "Matt"
=> "Matt"
>> c.middle_name = "" # blank string here
=> ""
>> c.last_name = "Haley"
=> "Haley"
>> c.save
=> true
>> c.middle_name.nil?
=> true
>>

答案 1 :(得分:8)

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

答案 2 :(得分:5)

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

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

答案 3 :(得分:3)

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

答案 4 :(得分:2)

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

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

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

# 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

...

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

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])
...

答案 5 :(得分:2)

您可以使用 attribute_normalizer gem并使用 blank 规范化程序,它将转换为nil值的空字符串。

答案 6 :(得分:1)

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

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}

答案 7 :(得分:1)

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

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

答案 8 :(得分:1)

克里斯,

这是对具有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

答案 9 :(得分:1)

在ApplicationController中:

class ApplicationController < ActionController::Base

  def nilify(p)
    p.transform_values!{|v| v.present? ? v : nil }
  end

end

在您的控制器中,修改强参数过滤器方法以调用nilify:

class UserController < ApplicationController

  def user_params
    nilify params.require(:user).permit(:email, :name)
  end

end

答案 10 :(得分:0)

我概括了一个答案并制作了一个可用作初始化程序的钩子/扩展名。这允许它跨多个模型使用。我已将其添加为ActiveRecordHelpers repo on GitHub

的一部分

答案 11 :(得分:0)

我是这样做的。

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"] }

答案 12 :(得分:0)

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

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

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