I’m using Virtus (basically the Property API from DataMapper) to build around a remote API, however I don’t think Virtus is the problem here, I think it’s my lack of understanding of what Ruby is doing.
I want to allow attributes that coerce to a given type, by using the Syntax:
Node[AnotherClass]
Which simply generates on-the-fly a subclass of Node and returns that new class. That part is working. But for some reason, it has an undesirable side-effect. All other objects that descend from Virtus::Attribute::Object are actually also Node subclasses themselves. I can’t explain it, but I think it must be an expected ruby behaviour related to the inheritance model. Can anybody point me in the right direction?
(Note the following code runs without modification if you 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
I’ve reduced your code down to the following, and it still behaves the same.
The presence of
Class.new(Node)is what causes the:stringattribute to haveNodein its ancestry.I’m not familiar with Virtus, but I’m guessing it’s to do with the way Virtus implements attribute types.