Сохранение непечатаемых символов в базе данных в Rails - PullRequest
2 голосов
/ 25 февраля 2010

Я не могу сохранить непечатаемые символы (например, "\ x83") в базе данных с помощью Rails (v2.2).

Упрощенный пример (из консоли):

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

1 голос
/ 04 марта 2010

Решение, которое я выбрал, было вдохновлено ответом @ dsander и комментарием @ Glex и использует обратные вызовы .Сначала мне пришлось создать поле 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
1 голос
/ 25 февраля 2010

Я не могу сказать вам, где именно потеряно \x83, но я предполагаю, что это будет база данных (по крайней мере, PostgreSQL отклоняет эту строку из-за неправильной байтовой кодировки).

Для работы вокруг вы можете base64 кодировать вашу строку и сохранить ее:

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