Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Having 'allocator undefined for Data' when saving with ActiveResource

What I am missing? I am trying to use a rest service for with Active resource, I have the following:

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

user = User.new(
        :name => "Test",
        :email => "[email protected]")

p user 
if user.save
  puts "success: #{user.uuid}"
else
  puts "error: #{user.errors.full_messages.to_sentence}"
end

And the following output for the user:

#<User:0x1011a2d20 @prefix_options={}, @attributes={"name"=>"Test", "email"=>"[email protected]"}>

and this error:

/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

If I user curl for my rest service it would be like:

curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"test curl", "email":"[email protected]"}' http://localhost:3000/users

with the response:

{"email":"[email protected]","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}
like image 390
aletapool Avatar asked Sep 05 '11 09:09

aletapool


2 Answers

There is a built-in type named Data, whose purpose is rather mysterious. You appear to be bumping into it:

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

The question is, how did it get there? The last stack frame puts us here. So, it appears Data wandered out of a call to find_or_create_resource_for. The code branch here looks likely:

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

This leads me to suspect you have an attribute or similar floating around named :data or "data", even though you don't mention one above. Do you? Particularly, it seems we have a JSON response with a sub-hash whose key is "data".

Here's a script that can trigger the error for crafted input, but not from the response you posted:

$ 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 => "[email protected]"

def simulate_load_attributes_from_response(response_body)
  puts "Loading #{response_body}.."
  USER.load User.format.decode(response_body)
end

OK = '{"email":"[email protected]","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}'
BORKED = '{"data":{"email":"[email protected]","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

produces..

$ ./activeresource-oddity.rb 
Loading {"email":"[email protected]","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}..
Loading {"data":{"email":"[email protected]","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

If I were you, I would open /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb, find load_attributes_from_response on line 1320 and temporarily change

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

to

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

..and reproduce the error again to see what is really coming out of your json decoder.

like image 111
phs Avatar answered Oct 23 '22 23:10

phs


I just ran into the same error in the latest version of ActiveResource, and I found a solution that does not require monkey-patching the lib: create a Data class in the same namespace as the ActiveResource object. E.g.:

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

     class Data < ActiveResource::Base; end
   end

Fundamentally, the problem has to do with the way ActiveResource chooses the classes for the objects it instantiates from your API response. It will make an instance of something for every hash in your response. For example, it'll want to create User, Data and Pet objects for the following JSON:

{
  "name": "Bob", 
  "email": "[email protected]", 
  "data": {"favorite_color": "purple"}, 
  "pets": [{"name": "Puffball", "type": "cat"}] 
}

The class lookup mechanism can be found here. Basically, it checks the resource (User) and its ancestors for a constant matching the name of the sub-resource it wants to instantiate (i.e. Data here). The exception is caused by the fact that this lookup finds the top-level Data constant from the Stdlib; you can therefore avoid it by providing a more specific constant in the resource's namespace (User::Data). Making this class inherit from ActiveResource::Base replicates the behaviour you'd get if the constant was not found at all (see here).

like image 33
Anara Avatar answered Oct 23 '22 21:10

Anara