Метапрограммирование анонимного подкласса, вызывающее нежелательное поведение (Virtus)

#ruby #metaprogramming

#ruby #метапрограммирование

Вопрос:

Я использую Virtus (в основном API свойств из DataMapper) для создания удаленного API, однако я не думаю, что проблема здесь в Virtus, я думаю, что это мое непонимание того, что делает Ruby.

Я хочу разрешить атрибуты, которые приводят к заданному типу, используя синтаксис:

 Node[AnotherClass]
  

Который просто генерирует на лету подкласс Node и возвращает этот новый класс. Эта часть работает. Но по какой-то причине это имеет нежелательный побочный эффект. Все остальные объекты, которые происходят от Virtus::Attribute::Object , на самом деле также являются подклассами узлов. Я не могу это объяснить, но я думаю, что это должно быть ожидаемое поведение ruby, связанное с моделью наследования. Кто-нибудь может указать мне правильное направление?

(Обратите внимание, что следующий код выполняется без изменений, если вы gem install virtus ).

 require "virtus"

class JsonModel
  include Virtus
end

class Node < Virtus::Attribute::Object
  attr_reader :type

  class << self
    def [](type)
      raise ArgumentError, "Child nodes may only be other JsonModel classes" unless type <= JsonModel

      @generated_class_map       ||= {}
      @generated_class_map[type] ||= Class.new(self) do
        default lambda { |m, a| type.new }

        define_method :type do
          type
        end

        define_method :coerce do |value|
          value.kind_of?(Hash) ? type.new(value) : value
        end
      end
    end
  end
end

class ChildModel < JsonModel
end

class ParentModel < JsonModel
  attribute :child,  Node[ChildModel]
  attribute :string, String
end

# This should be String, but it's a descendant of Node??
puts ParentModel.attributes[:string].class.ancestors.inspect
  

Комментарии:

1. Обратите внимание, что это Virtus::Attribute::String также происходит от Virtus::Attribute::Object , и я думаю, что именно здесь начинается проблема.

Ответ №1:

Я сократил ваш код до следующего, и он по-прежнему ведет себя так же.

Наличие Class.new(Node) — это то, что заставляет :string атрибут иметь Node в своей родословной.

 require "virtus"

class JsonModel
  include Virtus
end

class Node < Virtus::Attribute::Object
end

# this does it...
Class.new(Node)

class ParentModel < JsonModel
  attribute :string, String
end

# This should be String, but it's a descendant of Node??
puts ParentModel.attributes[:string].class.ancestors.inspect
  

Я не знаком с Virtus, но я предполагаю, что это связано с тем, как Virtus реализует типы атрибутов.

Комментарии:

1. Это была ошибка в Virtus, и теперь она исправлена. Он пытался найти атрибут для любого заданного примитивного типа (например, String, Hash, Array, Object …) … и поскольку Node просто использует примитив для Object , он всегда рассматривался как правильный атрибут практически для всего.