Rails:将空字符串强制转换为数据库中的NULL

58

是否有一种简单的方法(比如配置)可以强制ActiveRecord将空字符串保存为数据库中的NULL(如果列允许)?

原因是,如果您在没有默认值的可空字符型列中使用新记录并且不设置该值,则该列将包含NULL。但是,如果设置该值为空字符串,则该列将不会为空,这将导致数据库中的不一致性,而我想避免这种情况。

目前,我在我的模型中做类似于以下的事情:

before_save :set_nil

def set_nil
  [:foo, :bar].each do |att|
    self[att] = nil if self[att].blank?
  end
end

这个代码可以工作,但是不够高效也不符合DRY原则。我可以将其分解成一个方法并将其混合到ActiveRecord中,但在采取这种方法之前,我想知道是否已经有现成的方法可以完成这个任务。

7个回答

58

是的,目前唯一的选择是使用回调函数。

before_save :normalize_blank_values

def normalize_blank_values
  attributes.each do |column, value|
    self[column].present? || self[column] = nil
  end
end

你可以将代码转换为一个mixin,以便在多个模型中轻松地包含它。

module NormalizeBlankValues
  extend ActiveSupport::Concern

  included do
    before_save :normalize_blank_values
  end

  def normalize_blank_values
    attributes.each do |column, value|
      self[column].present? || self[column] = nil
    end
  end

end

class User
  include NormalizeBlankValues
end

或者您可以在ActiveRecord :: Base中定义它,以便在所有模型中使用。

最后,您还可以将其包含在ActiveRecord :: Base中,但在需要时启用它。

module NormalizeBlankValues
  extend ActiveSupport::Concern

  def normalize_blank_values
    attributes.each do |column, value|
      self[column].present? || self[column] = nil
    end
  end

  module ClassMethods
    def normalize_blank_values
      before_save :normalize_blank_values
    end
  end

end

ActiveRecord::Base.send(:include, NormalizeBlankValues)

class User
end

class Post
  normalize_blank_values

  # ...
end

看起来不错,谢谢。在这个过程中,将其构建为一个 gem 是否有意义呢?我想我不是唯一遇到这个问题的人。 - Thilo
before_filter 是一个控制器方法。我不得不改为 before_save 来支持 ActiveRecords。此外,该方法不能很好地处理布尔值。如果列类型是字符串(或其他类型),请考虑在设置为 nil 之前添加测试用例。 - Totach
非常感谢,这对我很有用。 - Jigar Bhatt
3
@SimoneCarletti,有一件事情.. false.present? == falsefalse.to_s.present? == true。这对于模型字段非常重要,默认情况下应该是布尔值(例如false)。 - serghei
2
此外,你可以使用 .presence 方法来替代写成 self[column].present? || self[column] = nil 的方式,如下所示:self[column] = self[column].presence - Cruz Nunez
显示剩余3条评论

19

尝试使用这个宝石(gem):

https://github.com/rubiety/nilify_blanks

提供了一个框架,用于在您更愿意使用DB NULL而不仅仅是空字符串的情况下,将传入的空值保存为nil到数据库中...

在Rails中,当从表单保存模型并且用户未提供值时,将记录一个空字符串到数据库中,而不是像许多人希望的那样记录为NULL(混合使用空白和NULL可能会变得混乱)。此插件允许您指定一个属性列表(或所有属性的例外),如果它们在模型保存之前为空,则将其转换为nil。

只有具有true值的blank?响应属性才会被转换为nil。因此,对于具有0值的整数字段,例如,这种方法无效......


7
我最终使用了attribute_normalizer插件,它可以对值进行nil处理,并且还可以直接完成其他的操作,例如去除空格等。 - Thilo

18

另一种选择是提供自定义setter,而不是在钩子中处理。例如:

def foo=(val)
  super(val == "" ? nil : val)
end

这个解决方案要简单得多(假设您想针对特定字段执行此操作)。 - Fabiano Arruda
1
这个解决方案非常简单和惯用,以至于我为没有想到它而使用回调而自责。 - jajavoli

5

我的建议:

# app/models/contact_message.rb
class ContactMessage < ActiveRecord::Base
  include CommonValidations
  include Shared::Normalizer
end


# app/models/concerns/shared/normalizer.rb
module Shared::Normalizer
  extend ActiveSupport::Concern

  included do
    before_save :nilify_blanks
  end

  def nilify_blanks
    attributes.each do |column, value|
      # ugly but work
      # self[column] = nil if !self[column].present? && self[column] != false

      # best way
      #
      self[column] = nil if self[column].kind_of? String and self[column].empty?
    end
  end

end

1
轻微的样式更新:self[column] = nil if self[column].is_a?(String) && self[column].empty? - Chris Edwards

4

很抱歉打扰了,但是我在回答中没有找到确切的解决方案,如果您需要指定应该被置空的字段,请使用以下方法:

module EnforceNil
  extend ActiveSupport::Concern

  module ClassMethods
    def enforce_nil(*args)
      self.class_eval do
        define_method(:enforce_nil) do
          args.each do |argument|
            field=self.send(argument)
            self.send("#{argument}=", nil)  if field.blank?
          end
        end           
        before_save :enforce_nil
      end
    end
  end
end

ActiveRecord::Base.send(:include, EnforceNil)

这样做:
class User
  enforce_nil :phone #,:is_hobbit, etc  
end

当你有字段1和字段2时,强制执行某些字段是很方便的。在SQL中,字段1具有唯一索引,但可以为空,因此需要强制执行(将NULL视为唯一,“”不是)。但对于字段2,您实际上并不关心,并且已经有了许多回调或方法,这些回调或方法在字段2为“”时工作,但如果字段2为nil,则会深入到应用程序的错误层面。这是我遇到的情况。

可能对某些人有用。


3

Strip Attributes Gem

有一个方便的宝石可以在保存记录时自动执行此操作,无论是在用户表单中还是在控制台或rake任务中等。

它被称为strip_attributes,非常易于使用,并且具有合理的默认设置。

它默认执行两件主要事情,几乎总是应该这样做:

  1. Strip leading and trailing white space:

    " My Value " #=> "My Value"
    
  2. Turn empty Strings into NULL:

    ""  #=> NULL
    " " #=> NULL
    

安装

您可以通过以下方式将其添加到gem文件中:

gem strip_attributes

用法

将其添加到任何(或所有)您想要从中删除前导/尾随空格并将空字符串转换为NULL的模型中:

class DrunkPokerPlayer < ActiveRecord::Base
  strip_attributes
end

高级用法

您可以在每个模型上传递其他选项以处理异常,例如是否保留前导/尾随空格等。

您可以在GitHub存储库中查看所有选项:

https://github.com/rmm5t/strip_attributes#examples


0

我使用属性规范化器 gem 在将属性存入数据库之前对其进行规范化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接