Наличие «распределителя неопределенного для данных» при сохранении с ActiveResource - PullRequest
6 голосов
/ 05 сентября 2011

Чего мне не хватает?Я пытаюсь использовать службу отдыха для с активным ресурсом, у меня есть следующее:

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}

Ответы [ 4 ]

13 голосов
/ 05 сентября 2011

Существует встроенный тип с именем 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, нашел бы 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}" })

.. и снова воспроизведите ошибку, чтобы увидеть, что на самом деле выходит из вашего json-декодера.

1 голос
/ 30 сентября 2016

Я только что столкнулся с той же ошибкой в ​​последней версии ActiveResource, и я нашел решение, которое не требует монтирования патчей в lib: создайте класс Data в том же пространстве имен, что и объект ActiveResource. 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. Он создаст экземпляр что-то для каждого хэша в вашем ответе. Например, он захочет создать объекты User, Data и Pet для следующего JSON:

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

Механизм поиска класса можно найти здесь . По сути, он проверяет ресурс (User) и его предков на наличие константы, совпадающей с именем подресурса, который он хочет создать (т. Е. Data здесь). Исключение вызвано тем фактом, что этот поиск находит константу Data верхнего уровня из Stdlib; поэтому вы можете избежать этого, предоставив более конкретную константу в пространстве имен ресурса (User::Data). Заставление этого класса наследовать от ActiveResource::Base повторяет поведение, которое вы получите, если константа вообще не найдена ( см. Здесь ).

0 голосов
/ 30 января 2014

Я решил эту проблему, используя метод «monkey-patch», который меняет «data» на «xdata» перед запуском find_or_create_resource_for (оскорбительный метод).Таким образом, при запуске метода 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
0 голосов
/ 29 августа 2012

Спасибо 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
...