module JsonSchema # Attributes mixes in some useful attribute-related methods for use in # defining schema classes in a spirit similar to Ruby's attr_accessor and # friends. module Attributes # Provides class-level methods for the Attributes module. module ClassMethods # Attributes that should be copied between classes when invoking # Attributes#copy_from. # # Hash contains instance variable names mapped to a default value for the # field. attr_reader :copyable_attrs # Attributes that are part of the JSON schema and hyper-schema # specifications. These are allowed to be accessed with the [] operator. # # Hash contains the access key mapped to the name of the method that should # be invoked to retrieve a value. For example, `type` maps to `type` and # `additionalItems` maps to `additional_items`. attr_reader :schema_attrs # identical to attr_accessible, but allows us to copy in values from a # target schema to help preserve our hierarchy during reference expansion def attr_copyable(attr, options = {}) attr_accessor(attr) ref = :"@#{attr}" # Usually the default being assigned here is nil. self.copyable_attrs[ref] = options[:default] if default = options[:default] # remove the reader already created by attr_accessor remove_method(attr) need_dup = [Array, Hash, Set].include?(default.class) define_method(attr) do val = instance_variable_get(ref) if !val.nil? val else need_dup ? default.class.new : default end end end end def attr_schema(attr, options = {}) attr_copyable(attr, :default => options[:default]) self.schema_attrs[options[:schema_name] || attr] = attr end # Directive indicating that attributes should be inherited from a parent # class. # # Must appear as first statement in class that mixes in (or whose parent # mixes in) the Attributes module. def inherit_attrs @copyable_attrs = self.superclass.instance_variable_get(:@copyable_attrs).dup @schema_attrs = self.superclass.instance_variable_get(:@schema_attrs).dup end # Initializes some class instance variables required to make other # methods in the Attributes module work. Run automatically when the # module is mixed into another class. def initialize_attrs @copyable_attrs = {} @schema_attrs = {} end end def self.included(klass) klass.extend(ClassMethods) klass.send(:initialize_attrs) end # Allows the values of schema attributes to be accessed with a symbol or a # string. So for example, the value of `schema.additional_items` could be # procured with `schema[:additionalItems]`. This only works for attributes # that are part of the JSON schema specification; other methods on the # class are not available (e.g. `expanded`.) # # This is implemented so that `JsonPointer::Evaluator` can evaluate a # reference on an sintance of this class (as well as plain JSON data). def [](name) name = name.to_sym if self.class.schema_attrs.key?(name) send(self.class.schema_attrs[name]) else raise NoMethodError, "Schema does not respond to ##{name}" end end def copy_from(schema) self.class.copyable_attrs.each do |copyable, _| instance_variable_set(copyable, schema.instance_variable_get(copyable)) end end def initialize_attrs self.class.copyable_attrs.each do |attr, _| instance_variable_set(attr, nil) end end end end