1

シリアル化された列を持つモデルが、保存されたデータを適切にロードしないという Ruby on Rails の問題を切り分けました。

入ってくるのはハッシュで、出てくるのはフォーマットの問題のために解析できない 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’

改行、インデントされた 2 行目、およびインデントされていない 3 行目の組み合わせにより、パーサーが失敗します。空白行またはインデントのいずれかを省略すると問題が解決するように見えますが、これはシリアライゼーション プロセスのバグのようです。かなり特殊な一連の状況が必要になるため、これは適切に処理されていない奇妙なエッジ ケースであると確信しています。

Ruby に同梱され、Rails で使用される YAML モジュールは、処理の大部分を 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

「:」で始まり、デシリアライズ時に Symbol と混同される可能性がある文字列のチェックがあるようです。:quote2 オプションは、エンコード プロセス中にそれを引用することを示す必要があります。上記の条件をキャッチするためにこの正規表現を調整しても、出力には何の影響もないように見えるので、YAML の実装に詳しい人がアドバイスしてくれることを願っています。

4

2 に答える 2

4

ええ、それは C 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));
?>

CLI でこれを実行すると、同じ 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 自体を修正してみてはいかがでしょうか。ただし、ライブラリは 2005 年 5 月の v0.55 ( http://rubyforge.org/projects/syck/ ) 以降更新されていないようです。

別の方法として、RbYAML ( http://rbyaml.rubyforge.org/ )と呼ばれる純粋な Ruby yaml パーサーがあります。これは、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"
于 2009-11-18T17:46:34.623 に答える
1

そのためには、簡単な ActiveRecord::Base メソッドをあきらめる必要がありserializeますが、独自のシリアル化スキームを使用することは難しくありません。たとえば、「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 フォーラムのスレッドを参照してください)

于 2009-11-19T18:24:56.653 に答える