我无法使用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保存不可打印的字符?
答案 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