Having a "undefined data dispenser" when saving with ActiveResource

What am I missing? I am trying to use the recreation service for an 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 => " test.user@domain.com ") 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"=>" test.user@domain.com "}> 

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 the user freezes for my rest service, it will look like this:

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

with the answer:

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

There is a built-in type called Data whose purpose is rather cryptic . You seem to stumble upon it:

 $ ruby -e 'Data.new' -e:1:in `new': allocator undefined for Data (TypeError) from -e:1 
Question: how did he get there? The last stack of frames places here . Thus, it appears Data , going from the call find_or_create_resource_for . Perhaps the code branch looks like this:
 $ irb >> class C >> end => nil >> C.const_get('Data') => Data 

This makes me suspect that you have an attribute or similar floating object named :data or "data" , although you have not mentioned above. You? In particular, it looks like we have a JSON response with a sub-hash whose key is "data".

Here is a script that can trigger an error for the generated input, but not from the response you sent:

 $ 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 

produces ..

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

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 really comes out of your json decoder.

+12
source

I just encountered the same error in the latest version of ActiveResource, and found a solution that does not require rendering lib patches harmless: create the Data class in the same namespace as the ActiveResource object. For instance:.

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

The main problem is how ActiveResource selects classes for the objects that it creates from your API response. This will make an instance of something for each hash in your answer. For example, he wants to create User , Data and Pet objects for the following JSON:

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

The class search engine can be found here . Basically, it checks the resource ( User ) and its ancestors for a constant corresponding to the name of the subresource that it wants to create (i.e. Data here). The exception is caused by the fact that this search finds the top-level Data constant from Stdlib; therefore, you can avoid this by providing a more specific constant in the resource namespace ( User::Data ). Giving this class inheritance from ActiveResource::Base replicates the behavior that you would get if the constant was not found at all ( see here ).

+1
source

Thanks to phs for analyzing it - it made me point in the right direction.

I had no choice but to hack ActiveResource to fix this problem, because an external service that I have no control over published an API where all response attributes were removed inside the top-level attribute: data.

In this hack, I put in config / initializers / active_resource.rb to make this work for me using active resource 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 
0
source

I solved this with the monkey-patch approach, which changes the "data" to "xdata" before running find_or_create_resource_for (violation method). Thus, when the find_or_create_resource_for method is find_or_create_resource_for , it will not look for the Data class (which will fail). Instead, it searches for the Xdata class, which I hope does not exist, and will be created dynamically using the method. This would be a suitable subclass class from ActiveResource .

Just add a file containing inside 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 
0
source

Source: https://habr.com/ru/post/896628/


All Articles