YAML – TypeError:无法转储匿名模块

在application_controller的一个动作中,如果我们尝试:

p request.env.to_yaml 

我会收到这个错误:

  TypeError: can't dump anonymous module: # from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:267:in `visit_Module' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:102:in `accept' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:447:in `block in dump_ivars' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:445:in `each' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:445:in `dump_ivars' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:124:in `visit_Object' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:102:in `accept' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:447:in `block in dump_ivars' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:445:in `each' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:445:in `dump_ivars' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:124:in `visit_Object' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:102:in `accept' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:292:in `block in visit_Hash' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:290:in `each' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:290:in `visit_Hash' from /Users/twer/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:102:in `accept' 

我的问题是:如何将request.env序列化为yaml?

实际上,我应该将request.env传递给delayed_job并发送电子邮件,我收到此错误,因为delayed_job需要将对象序列化为DB。

问题是,request.env哈希有很多嵌套对象(尤其是模块),无法转换为yaml。 诀窍是删除无法转换的哈希部分。

 tmp_env = request.env.clone tmp_env.delete "action_dispatch.routes" tmp_env.delete "action_controller.instance" tmp_env["action_dispatch.remote_ip"] = tmp_env["action_dispatch.remote_ip"].to_s p tmp_env.to_yaml # now it works 

我们首先克隆原始的env哈希值,以免意外修改它。 然后我们从我们的副本中删除这些密钥,这会导致错误。

tmp_env["action_dispatch.routes"]包含对ActionDispatch::Routing::RouteSet对象中未命名模块的引用,这是导致错误的原因。 我们最好删除它。

tmp_env["action_controller.instance"]包含对原始env -hash的引用(我们无法转换)。 删除它。

最后tmp_env["action_dispatch.remote_ip"]看起来像一个字符串(在检查它时),但它是一个ActionDispatch::RemoteIp::GetIp实例。 它包含对原始env哈希的另一个引用。 我们将其转换为字符串,因为我不知道您以后是否对该密钥感兴趣。

此外,您可以删除更多的键来减少您的yaml输出的大小。 但是,这应该可以工作而不会抛出您遇到的错误。 更精简的解决方案是从空Hash开始,只复制您在yaml输出中真正需要的键。

用ruby 1.9.3和rails 3.2.13测试

基于tessi的例子,这就是我想出的:

 module RequestSerializationHelper ::SerializableRequest = Struct.new( :env, :filtered_parameters, :fullpath, :headers, :request_method, :remote_ip ) ## From http://stackoverflow.com/questions/7604153/rails-2-3-14-how-to-serialise-an-actioncontrollerrequest-object ## with additional modifications # build a serializable Struct that out of the given request object, which looks like a real request def make_request_serializable(request) serializable_request = ::SerializableRequest.new serializable_request.env = request.env.clone serializable_request.filtered_parameters = request.filtered_parameters.clone if request.respond_to? :filtered_parameters serializable_request.fullpath = request.fullpath serializable_request.headers = request.respond_to?(:headers) ? request.headers.clone : {} serializable_request.request_method = request.request_method delete_identified_unserializable_values(serializable_request.env) delete_identified_unserializable_values(serializable_request.headers) # Some jobs want this, so set it after it's been converted to a string in the env serializable_request.remote_ip = serializable_request.env["action_dispatch.remote_ip"] # automatically delete anything left that's non-serializable. If we end up deleting # too much and breaking something, here's where to debug it based on info in warning delete_unidentified_unserializable_values :env, serializable_request.env delete_unidentified_unserializable_values :headers, serializable_request.headers serializable_request end def delete_identified_unserializable_values(hash) hash.delete "async.callback" hash.delete "action_dispatch.backtrace_cleaner" hash.delete "action_dispatch.cookies" hash.delete "action_dispatch.request.accepts" hash.delete "action_dispatch.routes" hash.delete "action_dispatch.logger" hash.delete "action_controller.instance" hash.delete "rack.input" hash.delete "rack.errors" hash.delete "rack.session" hash.delete "rack.session.options" hash["action_dispatch.remote_ip"] = hash["action_dispatch.remote_ip"].to_s hash.delete "warden" hash.delete_if { |key, _| key =~ /^rack-cache/ } end private def delete_unidentified_unserializable_values(hash_name, hash) hash.each do |key, value| begin serialized = value.to_yaml YAML.load(serialized) rescue => e warning = "RequestSerializationHelper: Automatically removing un(re)serializable entry in " + "'#{hash_name}' for key '#{key}' and value '#{value}'. Exception was: '#{e}'" Rails.logger.warn(warning) hash.delete key end end end end 

通过物理代码,可能听起来有点奇怪,但是

 request.env.instance_eval "def name; 'some_name'; end" 

可能有用。 很酷,哼哼?