#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 , он всегда рассматривался как правильный атрибут практически для всего.