使用活动资源保存时具有"未定义数据的分配器"



我缺少什么?我正在尝试使用活动资源的休息服务,我有以下内容:

class User < ActiveResource::Base
  self.site = "http://localhost:3000/"
  self.element_name = "users"
  self.format = :json
end
user = User.new(
        :name => "Test",
        :email => "test.user@domain.com")
p user 
if user.save
  puts "success: #{user.uuid}"
else
  puts "error: #{user.errors.full_messages.to_sentence}"
end

并为用户提供以下输出:

#<User:0x1011a2d20 @prefix_options={}, @attributes={"name"=>"Test", "email"=>"test.user@domain.com"}>

这个错误:

/Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1233:in `new': allocator undefined for Data (TypeError)
    from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1233:in `load'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1219:in `each'
    from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1219:in `load'
    from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1322:in `load_attributes_from_response'
    from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1316:in `create_without_notifications'
    from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1314:in `tap'
    from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1314:in `create_without_notifications'
    from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/observing.rb:11:in `create'
    from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1117:in `save_without_validation'
    from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/validations.rb:87:in `save_without_notifications'
    from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/observing.rb:11:in `save'
    from import_rest.rb:22

如果我的休息服务用户curl,它会是这样的:

curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"test curl", "email":"test@gmail.com"}' http://localhost:3000/users

响应:

{"email":"test@gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}

有一个名为Data的内置类型,其用途相当神秘。你似乎碰到了它:

$ ruby -e 'Data.new'
-e:1:in `new': allocator undefined for Data (TypeError)
  from -e:1

问题是,它是如何到达那里的?最后一帧堆栈将我们放在这里。因此,Data似乎从对find_or_create_resource_for的调用中走了出来。这里的代码分支看起来很可能:

$ irb
>> class C
>>   end
=> nil
>> C.const_get('Data')
=> Data

这让我怀疑你有一个名为:data"data"的属性或类似属性,尽管你上面没有提到。你呢?特别是,我们似乎有一个JSON响应,其中包含一个子散列,其关键字是"data"。

以下是一个脚本,它可以触发精心制作的输入的错误,但不能从您发布的响应中触发:

$ cat ./activeresource-oddity.rb
#!/usr/bin/env ruby
require 'rubygems'
gem 'activeresource', '3.0.10'
require 'active_resource'
class User < ActiveResource::Base
  self.site = "http://localhost:3000/"
  self.element_name = "users"
  self.format = :json
end
USER = User.new :name => "Test", :email => "test.user@domain.com"
def simulate_load_attributes_from_response(response_body)
  puts "Loading #{response_body}.."
  USER.load User.format.decode(response_body)
end
OK = '{"email":"test@gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}'
BORKED = '{"data":{"email":"test@gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}}'
simulate_load_attributes_from_response OK
simulate_load_attributes_from_response BORKED

产生。。

$ ./activeresource-oddity.rb 
Loading {"email":"test@gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}..
Loading {"data":{"email":"test@gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}}..
/opt/local/lib/ruby/gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1233:in `new': allocator undefined for Data (TypeError)
    from /opt/local/lib/ruby/gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1233:in `load'
    from /opt/local/lib/ruby/gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1219:in `each'
    from /opt/local/lib/ruby/gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1219:in `load'
    from ./activeresource-oddity.rb:17:in `simulate_load_attributes_from_response'
    from ./activeresource-oddity.rb:24

如果我是你,我会打开/Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb,在1320线上找到load_attributes_from_response,并临时更改

load(self.class.format.decode(response.body))

load(self.class.format.decode(response.body).tap { |decoded| puts "Decoded: #{decoded.inspect}" })

并再次重现错误,看看json解码器到底出了什么。

我刚刚在最新版本的ActiveResource中遇到了同样的错误,我找到了一个不需要monkey修补lib的解决方案:在与ActiveResource对象相同的命名空间中创建一个Data类。例如:

   class User < ActiveResource::Base
     self.site = "http://localhost:3000/"
     self.element_name = "users"
     self.format = :json
     class Data < ActiveResource::Base; end
   end

从根本上讲,问题与ActiveResource从API响应中为其实例化的对象选择类的方式有关。它将为响应中的每个哈希生成一个的实例。例如,它希望为以下JSON创建UserDataPet对象:

{
  "name": "Bob", 
  "email": "bob@example.com", 
  "data": {"favorite_color": "purple"}, 
  "pets": [{"name": "Puffball", "type": "cat"}] 
}

类查找机制可以在这里找到。基本上,它检查资源(User(及其祖先是否有一个常量与它想要实例化的子资源(即这里的Data(的名称相匹配。该异常是由以下事实引起的:此查找从Stdlib中查找顶级Data常量;因此,您可以通过在资源的命名空间(User::Data(中提供更具体的常量来避免它。使这个类从ActiveResource::Base继承会复制在根本找不到常量的情况下得到的行为(请参阅此处(。

感谢phs的分析,它让我朝着正确的方向前进。

我别无选择,只能破解ActiveResource来解决这个问题,因为我无法控制的一个外部服务发布了一个API,其中响应的所有属性都隐藏在一个顶级:data属性中。

以下是我在config/initializers/active_resource.rb中使用活动资源3.2.8:进行的破解

class ActiveResource::Base
  def load(attributes, remove_root = false)
    raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
    @prefix_options, attributes = split_options(attributes)
    if attributes.keys.size == 1
      remove_root = self.class.element_name == attributes.keys.first.to_s
    end
    # THIS IS THE PATCH
    attributes = ActiveResource::Formats.remove_root(attributes) if remove_root
    if data = attributes.delete(:data)
      attributes.merge!(data)
    end
    # END PATCH
    attributes.each do |key, value|
      @attributes[key.to_s] =
        case value
        when Array
          resource = nil
          value.map do |attrs|
          if attrs.is_a?(Hash)
            resource ||= find_or_create_resource_for_collection(key)
            resource.new(attrs)
          else
            attrs.duplicable? ? attrs.dup : attrs
          end
        end
        when Hash
          resource = find_or_create_resource_for(key)
          resource.new(value)
        else
          value.duplicable? ? value.dup : value
        end
    end
    self
  end
  class << self
    def find_every(options)
      begin
        case from = options[:from]
        when Symbol
          instantiate_collection(get(from, options[:params]))
        when String
          path = "#{from}#{query_string(options[:params])}"
          instantiate_collection(format.decode(connection.get(path, headers).body) || [])
        else
          prefix_options, query_options = split_options(options[:params])
          path = collection_path(prefix_options, query_options)
          # THIS IS THE PATCH
          body = (format.decode(connection.get(path, headers).body) || [])
          body = body['data'] if body['data']
          instantiate_collection( body, prefix_options )
          # END PATCH
        end
      rescue ActiveResource::ResourceNotFound
        # Swallowing ResourceNotFound exceptions and return nil - as per
        # ActiveRecord.
        nil
      end
    end
  end
end

我使用monkey-patch方法解决了这个问题,该方法在运行find_or_create_resource_for(有问题的方法(之前将"data"更改为"xdata"。这样,当find_or_create_resource_for方法运行时,它就不会搜索Data类(这会崩溃(。它转而搜索Xdata类,希望它不存在,并将由该方法动态创建。这将是一个从ActiveResource派生出来的适当类。

只需在config/initializers 中添加一个包含此内容的文件

module ActiveResource
  class Base
    alias_method :_find_or_create_resource_for, :find_or_create_resource_for
    def find_or_create_resource_for(name)
      name = "xdata" if name.to_s.downcase == "data"
      _find_or_create_resource_for(name)
    end
  end
end

相关内容

  • 没有找到相关文章

最新更新