ActiveRecord,故意将字符串截断为db列宽

时间:2014-09-30 14:08:57

标签: mysql ruby-on-rails activerecord

在Rails 4中,设置了ActiveRecord及其MySQL适配器,因此如果您尝试将AR模型中的属性保存到MySQL数据库,其中属性字符串长度对于MySQL列限制来说太宽 - - 你会引发异常。

大!这比Rails3好得多,它默默地截断了字符串。

但是,偶尔我有一个属性,我明确希望将其简单地截断为db允许的最大大小,没有例外。我无法找出使用AR执行此操作的最佳/支持方式。

  • 理想情况下应该在设置属性后立即发生,但我会在保存时将其发生。 (这不是一个'验证',因为我从不想提出,只是截断,但也许验证系统是最好的支持方式来做到这一点?)

  • 理想情况下,它会通过AR的内省自动计算db列宽,因此如果db列宽度发生更改(在以后的迁移中),截断限制将相应更改。但如果不可能,我会采用硬编码的截断限制。

  • 理想情况下,这将是适用于任何数据库的通用增强现实代码,但如果没有好办法,我会选择仅适用于MySQL的代码

    < / LI>

4 个答案:

答案 0 :(得分:4)

您可以在使用before_savebefore_validation插入数据库之前截断数据 请参阅Active Record Callbacks — Ruby on Rails GuidesActiveRecord::Callbacks

您可以使用MODEL.columnsMODEL.columns_hash检索表格中的信息。 见ActiveRecord::ModelSchema::ClassMethods

例如(未经测试):

class User < ActiveRecord::Base
  before_save :truncate_col
  ......

  def truncate_col
    col_size = User.columns_hash['your_column'].limit
    self.your_column = self.your_column.truncate(col_size)
  end
end

答案 1 :(得分:2)

我非常确定您可以通过ActiveRecord回调和ConnectionsAdapters的组合来实现这一目标。 ActiveRecord包含几个callbacks,您可以覆盖这些column以在保存流程中的不同点执行特定逻辑。由于在保存时抛出异常,我建议将您的逻辑添加到before_save方法。使用{{3}} ConnectionAdapter,您应该能够确定要插入的列的限制,尽管字符串与整数的逻辑很可能是不同的等等。在我的脑海中,你可以直接使用它。可能想要实现类似的东西:

class User < ActiveRecord::Base
    def before_save
        limit = User.columns_hash['attribute'].limit
        self.attribute = self.attribute[0..limit-1] if self.attribute.length > limit
    end
end

以上示例适用于字符串,但此解决方案应适用于所有连接适配器,假设它们支持limit属性。希望这会有所帮助。

答案 2 :(得分:1)

我想谈几点:

如果your_column的数据类型为text,则在Rails 4中,User.columns_hash['your_column'].limit将返回nil。如果是intvarchar,它将返回一个数字。

MySQL中的文本数据类型的存储限制为64k。如果内容包含ç之类的非ASCII字符(需要存储多个字节),则仅截断字符长度是不够的。

我最近遇到了这个问题,这是它的修复程序:

before_save :truncate_your_column_to_fit_into_max_storage_size

def truncate_your_column_to_fit_into_max_storage_size
  return if your_column.blank?

  max_field_size_in_bytes = 65_535

  self.your_column = your_column[0, max_field_size_in_bytes]

  while your_column.bytesize > max_field_size_in_bytes
    self.your_column = your_column[0..-2]
  end
end

答案 3 :(得分:0)

这是我自己的自我回答,它在属性集上截断(保存前的方式)。好奇,如果有人有任何反馈。它似乎工作!

# An ActiveRecord extension that will let you automatically truncate
# certain attributes to the maximum length allowed by the DB. 
#
#     require 'truncate_to_db_limit'
#     class Something < ActiveRecord::Base
#        extend TruncateToDbLimit
#        truncate_to_db_limit :short_attr, :short_attr2
#        #...
#
# Truncation is done whenever the attribute is set, NOT waiting
# until db save. 
#
# For a varchar(4), if you do:
#    model.short_attr = "123456789"
#    model.short_attr # => '1234'
#
#
# We define an override to the `attribute_name=` method, which ActiveRecord, I think,
# promises to call just about all the time when setting the attribute. We call super
# after truncating the value. 
module TruncateToDbLimit

  def truncate_to_db_limit(*attribute_names)
    attribute_names.each do |attribute_name|
      ar_attr = columns_hash[attribute_name.to_s]

      unless ar_attr
        raise ArgumentError.new("truncate_to_db_limit #{attribute_name}: No such attribute")
      end

      limit   = ar_attr.limit

      unless limit && limit.to_i != 0
        raise ArgumentError.new("truncate_to_db_limit #{attribute_name}: Limit not known")
      end

      define_method "#{attribute_name}=" do |val|
        normalized = val.slice(0, limit)
        super(normalized)
      end

    end
  end
end