Yaml::Load Raises Undefined Class/Module Error

YAML::load raises undefined class/module error

When you use YAML.dump to serialize an object in Ruby, the class name is use as part of the Yaml tag so that the correct class can be used when loading the object. For example:

require 'yaml'

class Foo; end

puts YAML.dump Foo.new

produces

--- !ruby/object:Foo {}

When you use YAML.load on that string, Psych knows what class to instantiate for the deserialized object.

If you try to call YAML.load on a Yaml string that specifies a class that hasn’t been defined, then you will get the error:

require 'yaml'

# No Bar class has been defined
YAML.load '--- !ruby/object:Bar {}'

produces:

/Users/matt/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/psych/visitors/to_ruby.rb:312:in `path2class': undefined class/module Bar (ArgumentError)
from /Users/matt/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/psych/visitors/to_ruby.rb:312:in `resolve_class'
...

This is because Psych needs to create an instance of class Bar, but doesn’t have the definition of the class available. This explains why adding require 'whatever' before loading the Yaml works – now Ruby has the definition of the class loaded and so can create an instance of it (note that there is no definitive link between class name and file name in Ruby, it’s just convention).

The solution therefore is to make sure that when you’re loading any Ruby objects from Yaml you have already required any files that may contain definitions of any classes potentially in that Yaml.

Rails doesn't load classes on deserializing YAML/Marshal objects

Well, after read @tadman and a bunch of answers I have received in the spanish ror mailing list [1] I have collected a few hot tips when you have to deal with Ruby deserializing and class loading in Rails:

Super fast solution

Use config.cache_classes = true in your development.rb but you will lost the class auto-refreshing.

Better solution

Require all the classes that are gonna be deserialized but not with require but with require_dependency[2] so in development environment the class auto-refreshing will remain working.

Elegant solution

Monkey-patch the YAML and the Marshal gem to tell them to call require_dependency when they find a non-defined class to deserialize.

And @Xavi has sent me a proposition of monkey-patch Marshal (he says he wrote it on the air and it is not tested so use it in your own risk) [3]

  • [1] http://lists.simplelogica.net/pipermail/ror-es/2011-January/024787.html
  • [2] http://apidock.com/rails/ActiveSupport/Dependencies/Loadable/require_dependency
  • [3] http://lists.simplelogica.net/pipermail/ror-es/2011-January/024796.html

DelayedJob: Job failed to load: uninitialized constant Syck::Syck

I wrote a post about it.

http://www.kensodev.com/2011/08/16/uninitialized-constant-sycksyck-nameerror/

NoMethodError: undefined method `each_value' when parsing YAML array

It's likely that the declaration at the top, "%YAML 1.1", is causing problems.

No error:

require "yaml"
YAML.load("---\n\n-\n - 'd7744c3878'\n - '80705686'").each
=> #<Enumerator: [["d7744c3878", "80705686"]]:each>

Error:

YAML.load("%YAML 1.1\n---\n\n-\n  - 'd7744c3878'\n  - '80705686'").each
NoMethodError: undefined method `each' for "%YAML 1.1 ---\n- - 'd7744c3878' - '80705686'":String
from (irb):4
from /Users/modify/.rvm/rubies/ruby-1.9.2-p180/bin/irb:17:in `<main>'

Here I'm using ruby 1.9.2p180 (2011-02-18 revision 30909) [i386-darwin9.8.0]. I also note that in an unmodified environment, #each_value is available for a Hash but not an Array (@oldergod).

It looks like Psych, which is the default YAML interepreter in later versions of Ruby, can handle the %YAML 1.1 directive:

require "psych"
Psych.load("%YAML 1.1\n---\n\n-\n - 'd7744c3878'\n - '80705686'").each
=> #<Enumerator: [["d7744c3878", "80705686"]]:each>

Possible alternatives to using Psych directly would be to switch to a later version of Ruby or to strip the %YAML 1.1 header from the file.

How to parse a yaml file into ruby hashs and/or arrays?

Use the YAML module:

http://ruby-doc.org/stdlib-1.9.3/libdoc/yaml/rdoc/YAML.html

node = YAML::parse( <<EOY )
one: 1
two: 2
EOY

puts node.type_id
# prints: 'map'

p node.value['one']
# prints key and value nodes:
# [ #<YAML::YamlNode:0x8220278 @type_id="str", @value="one", @kind="scalar">,
# #<YAML::YamlNode:0x821fcd8 @type_id="int", @value="1", @kind="scalar"> ]'

# Mappings can also be accessed for just the value by accessing as a Hash directly
p node['one']
# prints: #<YAML::YamlNode:0x821fcd8 @type_id="int", @value="1", @kind="scalar">

http://yaml4r.sourceforge.net/doc/page/parsing_yaml_documents.htm

Delayed::DeserializationError undefined method `has_key?'

Sometimes when we upgrade libs delayed jobs still keep old references.

Try to find the id of delayed_job in logs and play to parse its handler to ruby to find the wrong reference

j = DelayedJob.find(XXX)
data = YAML.load_dj(j.handler)
data.to_ruby

I made a pull request to help with this problem.

Meanwhile you can use this lines

# config/initializers/delayed_job.rb

# Monkey patch to use old class references
module Psych

class << self; attr_accessor :old_class_references end
@old_class_references = {}

class ClassLoader
private

def find klassname
klassname = ::Psych.old_class_references[klassname] || klassname
@cache[klassname] ||= resolve(klassname)
end
end

module Visitors
class ToRuby < Psych::Visitors::Visitor
def revive klass, node
if klass.is_a? String
klassname = ::Psych.old_class_references[klass] || klass
klass = Kernel.const_get(klassname) rescue klassname
end
s = register(node, klass.allocate)
init_with(s, revive_hash({}, node), node)
end
end
end
end

# Add all old dependencies (hash keys) pointing to new references (hash values)
Psych.old_class_references = {
'ActiveRecord::AttributeSet' => 'ActiveModel::AttributeSet'
# ...
}


Related Topics



Leave a reply



Submit