在Rails中将不可打印的字符保存到数据库中

时间:2010-02-25 02:47:04

标签: ruby-on-rails database activerecord special-characters

我无法使用Rails(v2.2)将不可打印的字符(例如“\ x83”)保存到数据库中。

简化示例(来自控制台):

>> f = FileSpace.create( { :name => "/tmp/\x83" } )
=> #<FileSpace id: 7, name: "/tmp/\203">
>> f.name
=> "/tmp/\203"
>> FileSpace.last
=> #<FileSpace id: 7, name: "/tmp/">

所以你可以看到,Rails正在默默地丢弃字符串中的“\ x83”(或“\ 203”)字符。

“\ 83”字符存储在数据库中:

mysql> SELECT hex(name) FROM file_spaces WHERE id=7;
+------------------------------------+
| hex(name)                          |
+------------------------------------+
| 2F746D702F                         | 
+------------------------------------+
1 rows in set (0.03 sec)

mysql> select x'2F746D702F';
+---------------+
| x'2F746D702F' |
+---------------+
| /tmp/         | 
+---------------+
1 row in set (0.00 sec)

那么,我怎样才能让Rails保存不可打印的字符?

2 个答案:

答案 0 :(得分:1)

我无法告诉你确切地遗失了\x83,但我的猜测是数据库(至少PostgreSQL因为无效的字节编码而拒绝该字符串)。

要使用arround,你可以对你的字符串进行base64编码并存储它:

require 'base64'
str = "/tmp/\x83"
encoded = Base64.encode64(str) => "L3RtcC+D\n"
Base64.decode64(encoded) => "/tmp/\x83"

答案 1 :(得分:1)

我最终得到的解决方案受@ dsander的回答和@Glex的评论的启发,并使用callbacks。我必须首先为数据库中的name_encoded表创建一个false字段(默认值:file_spaces),因为已经保存的文件空间不会被编码。

然后,我创建了一个用于回调的模型(遗憾的是,不是最干净的代码):

class EncodingWrapper
  require 'base64'

  def initialize(attribute)
    @attribute = attribute.to_s
    @get_method = @attribute
    @set_method = @attribute + "="
    @get_encoded_method = @attribute + "_encoded"
    @set_encoded_method = @attribute + "_encoded="
  end

  def before_save(record)
    set_encoded(record)
    encode(record)
  end

  def after_save(record)
    decode(record)
  end

  # Rails dislikes the after_find callback because it has a potentially
  # severe performance penalty.  So it ignores it unless it is created a
  # particular way in the model.  So I have to change the way this method
  # works.  So long, DRY code. :-/
  def self.after_find(record, attribute)
    # Ugly...but hopefully relatively fast.
    a = attribute.to_s
    if record.send(a + '_encoded')
      record.send(a + '=', Base64.decode64(record.send(a)))
    end
  end

  private

  def is_encoded?(record)
    record.send(@get_encoded_method)
  end

  def set_encoded(record)
    record.send(@set_encoded_method, true)
  end

  def encode(record)
    record.send(@set_method, Base64.encode64(record.send(@get_method)))
  end

  def decode(record)
    record.send(@set_method, Base64.decode64(record.send(@get_method)))
  end

end

最后,将回调挂钩到FileSpace模型中:

class FileSpace < ActiveRecord::Base
  ...
  before_save EncodingWrapper.new(:name)
  after_save  EncodingWrapper.new(:name)
  # Have to do the after_find callback special, to tell Rails I'm willing to
  # pay the performance penalty for this feature.
  def after_find
    EncodingWrapper.after_find(self, :name)
  end
  ...
end