使用ActiveResource保存时,“为数据分配器未定义”

时间:2011-09-05 09:11:23

标签: ruby activeresource

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

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

4 个答案:

答案 0 :(得分:13)

有一个名为Data的内置类型,其目的是rather mysterious。你似乎碰到了它:

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

问题是,它是如何实现的?最后一个堆栈帧给我们here。因此,Data看似find_or_create_resource_for出现了$ irb >> class C >> end => nil >> C.const_get('Data') => Data 。代码分支here看起来很可能:

:data

这导致我怀疑你有一个名为"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 的属性或类似物,即使你没有提到上面的一个。你呢?特别是,似乎我们有一个带有子哈希的JSON响应,其键是“data”。

这是一个可以触发精心设计的输入错误的脚本,但不是来自您发布的响应:

$ ./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

如果我是你,我会打开load_attributes_from_response,在第1320行找到load(self.class.format.decode(response.body)) 并暂时更改

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

{{1}}

..并再次重现错误,看看你的json解码器真正发生了什么。

答案 1 :(得分:1)

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

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

     class Data < ActiveResource::Base; end
   end

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

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

可以找到类查找机制here。基本上,它检查资源(User)及其祖先的常量匹配它想要实例化的子资源的名称(即Data)。这个例外是由于这个查找从Stdlib找到顶级Data常量引起的;因此,您可以通过在资源的命名空间(User::Data)中提供更具体的常量来避免它。如果根本找不到常量(see here),则使此类继承自ActiveResource::Base复制您将获得的行为。

答案 2 :(得分:0)

感谢phs的分析 - 它让我指出了正确的方向。

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

这是hack我最终放入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

答案 3 :(得分:0)

我使用猴子补丁方法解决了这个问题,在运行find_or_create_resource_for(违规方法)之前将“数据”更改为“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