YAML-кодирование искаженной строки, проблемы с сериализацией модели - PullRequest
1 голос
/ 21 октября 2009

Я выделил проблему с Ruby on Rails, когда модель с сериализованным столбцом неправильно загружает данные, которые были сохранены в ней.

В результате получается Hash, а в результате получается строка YAML, которую невозможно проанализировать из-за проблем с форматированием. Я ожидаю, что сериализатор может правильно хранить и извлекать все, что вы ему даете, поэтому, похоже, что-то пошло не так.

Рассматриваемая проблемная строка отформатирована примерно так:

message_text = <<END

  X
X
END

yaml = message_text.to_yaml

puts yaml
# =>
# --- |
#
#   X
# X

puts YAML.load(yaml)
# => ArgumentError: syntax error on line 3, col 0: ‘X’

Сочетание новой строки, отступа второй строки и не отступа третьей строки приводит к сбою синтаксического анализатора. Пропуск пустой строки или отступа, по-видимому, исправляет проблему, но, похоже, это ошибка в процессе сериализации. Поскольку это требует довольно уникального набора обстоятельств, я готов поспорить, что это какой-то странный крайний случай, который не обрабатывается должным образом.

Модуль YAML, который поставляется с Ruby и используется Rails, по-видимому, делегирует большую часть обработки Syck, но предоставляет Syck некоторые подсказки о том, как кодировать данные, которые он отправляет.

В yaml / rubytypes.rb есть определение String # to_yaml:

class String
  def to_yaml( opts = {} )
    YAML::quick_emit( is_complex_yaml? ? self : nil, opts ) do |out|
      if is_binary_data?
        out.scalar( "tag:yaml.org,2002:binary", [self].pack("m"), :literal )
      elsif to_yaml_properties.empty?
        out.scalar( taguri, self, self =~ /^:/ ? :quote2 : to_yaml_style )
      else
        out.map( taguri, to_yaml_style ) do |map|
          map.add( 'str', "#{self}" )
          to_yaml_properties.each do |m|
            map.add( m, instance_variable_get( m ) )
          end
        end
      end
    end
  end
end

Похоже, там есть проверка строк, начинающихся с ':', и их можно спутать с символом при десериализации, а опция: quote2 должна указывать на кавычку в процессе кодирования. Настройка этого регулярного выражения для определения условий, описанных выше, похоже, не влияет на вывод, поэтому я надеюсь, что кто-то, более знакомый с реализацией YAML, может посоветовать.

Ответы [ 2 ]

4 голосов
/ 18 ноября 2009

Да, это похоже на ошибку в библиотеке Syck. Я проверил это, используя привязки PHP syck (v 0.9.3): http://pecl.php.net/package/syck, и такая же ошибка присутствует, указывая на то, что это ошибка в библиотеке, в отличие от библиотеки ruby ​​yaml или привязок ruby-syck:

// phptestsyck.php
<?php
$message_text = "

  X
X
";

syck_load(syck_dump($message_text));
?>

Выполнение этого в кли дает такое же исключение SyckException:

$ php phptestsyck.php 
PHP Fatal error:  Uncaught exception 'SyckException' with message 'syntax error on line 5, col 0: 'X'' in /.../phptestsyck.php:8
Stack trace:
#0 /.../phptestsyck.php(8): syck_load('--- %YAML:1.0 >...')
#1 {main}
  thrown in /.../phptestsyck.php on line 8

Итак, я полагаю, вы могли бы попытаться исправить сам Syck. Похоже, что библиотека не обновлялась с v0.55 в мае 2005 года (хотя http://rubyforge.org/projects/syck/),.

В качестве альтернативы, существует синтаксический анализатор yaml с чистым рубином, называемый RbYAML (http://rbyaml.rubyforge.org/)), созданный с помощью JRuby, у которого, похоже, нет этой ошибки:

>> require 'rbyaml'
=> true
>> message_text = <<END

  X
X
END
=> "\n  X\nX\n"
>> yaml = RbYAML.dump(message_text)
=> "--- "\\n  X\\nX\\n"\n"
>> RbYAML.load(yaml)
=> "\n  X\nX\n"
>> 

Наконец, вы вообще рассмотрели другой формат сериализации? Библиотека Ruby Marshal также не имеет этой ошибки и работает быстрее, чем Yaml (см. http://significantbits.wordpress.com/2008/01/29/yaml-vs-marshal-performance/):

>> message_text = <<END

  X
X
END
=> "\n  X\nX\n"
>> marshal = Marshal.dump(message_text)
=> "\004\b"\f\n  X\nX\n"
>> Marshal.load(marshal)
=> "\n  X\nX\n"
1 голос
/ 19 ноября 2009

Вы должны отказаться от простого serialize ActiveRecord :: Base метода, чтобы сделать это, но нетрудно иначе использовать вашу собственную схему сериализации. Например, для сериализации некоторого поля с именем person_data:

class Person < ActiveRecord::Base
 def person_data
    self[:person_data] ? Marshal.load(self[:person_data]) : nil
  end

  def person_data=(x)
    self[:person_data] = Marshal.dump(x)
  end
end

## User Person#person_data as normal and it is transparently marshalled
p = Person.find 1
p.person_data = {:color => "blue", :food => "vegetarian"}

(см. Эту ветку форума ruby ​​ для более подробной информации)

...