Domanda

Ho isolato un problema con Ruby on Rails in cui un modello con una colonna serializzata non sta caricando correttamente i dati che sono stati salvati su di esso.

Ciò che accade è un hash e ciò che viene fuori è una stringa YAML che non può essere analizzata a causa di problemi di formattazione. Mi aspetto che un serializzatore possa archiviare e recuperare correttamente tutto ciò che gli dai, quindi qualcosa sembra essere andato storto.

La problematica stringa in questione è formattata in questo modo:

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’

La combinazione di nuova riga, seconda riga rientrata e terza riga non rientrata causa l'errore del parser. L'omissione della riga vuota o del rientro sembra porre rimedio al problema, ma questo sembra essere un bug nel processo di serializzazione. Dato che richiede un insieme piuttosto singolare di circostanze, sono disposto a scommettere che questo è uno strano caso limite che non è gestito correttamente.

Il modulo YAML fornito con Ruby e utilizzato da Rails cerca di delegare gran parte dell'elaborazione a Syck, tuttavia fornisce a Syck alcuni suggerimenti su come codificare i dati che sta inviando.

In yaml / rubytypes.rb c'è la definizione 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

Sembra che ci sia un controllo lì per le stringhe che iniziano con ':' e potrebbero essere confuse come Simbolo durante la deserializzazione, e l'opzione: quote2 dovrebbe essere un'indicazione per quotarla durante il processo di codifica. La modifica di questa espressione regolare per cogliere le condizioni sopra descritte non sembra avere alcun effetto sull'output, quindi spero che qualcuno che abbia più familiarità con l'implementazione YAML possa consigliare.

È stato utile?

Soluzione

Sì, sembra un bug nella libreria C syck. L'ho verificato usando i collegamenti syck di PHP (v 0.9.3): http: //pecl.php. net / package / syck e lo stesso bug è presente, indicando che si tratta di un bug nella libreria rispetto alla libreria ruby ??yaml o ai collegamenti ruby-syck:

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

  X
X
";

syck_load(syck_dump($message_text));
?>

L'esecuzione di questo sul cli dà la stessa 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

Quindi, suppongo che potresti provare a riparare Syck stesso. Sembra che la libreria non sia stata aggiornata dalla v0.55 nel maggio 2005 ( http: // rubyforge. org / projects / syck / ), tuttavia.

In alternativa, esiste un parser yaml di puro rubino chiamato RbYAML ( http://rbyaml.rubyforge.org/ ) originato con JRuby che non sembra avere questo errore:

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

Infine, hai considerato del tutto un altro formato di serializzazione? La libreria del Maresciallo di Ruby non ha neanche questo errore ed è più veloce di Yaml (vedi 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"

Altri suggerimenti

Devi rinunciare al semplice metodo serialize ActiveRecord :: Base per farlo, ma altrimenti non è difficile usare il tuo schema di serializzazione. Ad esempio, per serializzare un campo chiamato "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"}

(Vedi questo thread del forum ruby ?? per maggiori informazioni)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top