class WADL::CheapSchema

A cheap way of defining an XML schema as Ruby classes and then parsing documents into instances of those classes.

Constants

ATTRIBUTES

Attributes

attributes[R]
href[RW]
index_key[RW]
parent[RW]

Public Class Methods

as_collection(collection_name) click to toggle source
# File lib/wadl/cheap_schema.rb, line 77
def as_collection(collection_name)
  @names[:collection] = collection_name
end
as_member(member_name) click to toggle source
# File lib/wadl/cheap_schema.rb, line 81
def as_member(member_name)
  @names[:member] = member_name
end
contents_are_mixed_data() click to toggle source
# File lib/wadl/cheap_schema.rb, line 85
def contents_are_mixed_data
  @contents_are_mixed_data = true
end
dereferencing_attr_accessor(*symbols) click to toggle source
# File lib/wadl/cheap_schema.rb, line 137
def dereferencing_attr_accessor(*symbols)
  define_dereferencing_accessors(symbols,
    'dereference.attributes["%s"]',
    'dereference.attributes["%s"] = value'
  )
end
dereferencing_instance_accessor(*symbols) click to toggle source
# File lib/wadl/cheap_schema.rb, line 129
def dereferencing_instance_accessor(*symbols)
  define_dereferencing_accessors(symbols,
    'd, v = dereference, :@%s; ' <<
    'd.instance_variable_get(v) if d.instance_variable_defined?(v)',
    'dereference.instance_variable_set(:@%s, value)'
  )
end
from_element(parent, element, need_finalization) click to toggle source

Turn an XML element into an instance of this class.

# File lib/wadl/cheap_schema.rb, line 179
def from_element(parent, element, need_finalization)
  attributes = element.attributes

  me = new
  me.parent = parent

  @collections.each { |name, klass|
    me.instance_variable_set("@#{klass.names[:collection]}", [])
  }

  if may_be_reference? and href = attributes['href']
    # Handle objects that are just references to other objects
    # somewhere above this one in the hierarchy
    href = href.dup
    href.sub!(/\A#/, '') or warn "Warning: HREF #{href} should be ##{href}"

    me.attributes['href'] = href
  else
    # Handle this element's attributes
    @required_attributes.each { |name|
      name = name.to_s

      raise ArgumentError, %Q{Missing required attribute "#{name}" in element: #{element}} unless attributes[name]

      me.attributes[name] = attributes[name]
      me.index_key = attributes[name] if name == @index_attribute
    }

    @attributes.each { |name|
      name = name.to_s

      me.attributes[name] = attributes[name]
      me.index_key = attributes[name] if name == @index_attribute
    }
  end

  # Handle this element's children.
  if @contents_are_mixed_data
    me.instance_variable_set(:@contents, element.children)
  else
    element.each_element { |child|
      if klass = @members[child.name] || @collections[child.name]
        object = klass.from_element(me, child, need_finalization)

        if klass == @members[child.name]
          instance_variable_name = "@#{klass.names[:member]}"

          if me.instance_variable_defined?(instance_variable_name)
            raise "#{name} can only have one #{klass.name}, but several were specified in element: #{element}"
          end

          me.instance_variable_set(instance_variable_name, object)
        else
          me.instance_variable_get("@#{klass.names[:collection]}") << object
        end
      end
    }
  end

  need_finalization << me if me.respond_to?(:finalize_creation)

  me
end
has_attributes(*names) click to toggle source
# File lib/wadl/cheap_schema.rb, line 144
def has_attributes(*names)
  has_required_or_attributes(names, @attributes)
end
has_many(*classes) click to toggle source
# File lib/wadl/cheap_schema.rb, line 96
      def has_many(*classes)
        classes.each { |klass|
          @collections[klass.names[:element]] = klass

          collection_name = klass.names[:collection]
          dereferencing_instance_accessor(collection_name)

          # Define a method for finding a specific element of this
          # collection.
          class_eval "            def find_#{klass.names[:element]}(*args, &block)
              block ||= begin
                name = args.shift.to_s
                lambda { |match| match.matches?(name) }
              end

              auto_dereference = args.shift
              auto_dereference = true if auto_dereference.nil?

              match = #{collection_name}.find { |_match|
                block[_match] || (
                  #{klass}.may_be_reference? &&
                  auto_dereference &&
                  block[_match.dereference]
                )
              }

              match && auto_dereference ? match.dereference : match
            end
", __FILE__, __LINE__ + 1
        }
      end
has_one(*classes) click to toggle source
# File lib/wadl/cheap_schema.rb, line 89
def has_one(*classes)
  classes.each { |klass|
    @members[klass.names[:element]] = klass
    dereferencing_instance_accessor(klass.names[:member])
  }
end
has_required(*names) click to toggle source
# File lib/wadl/cheap_schema.rb, line 148
def has_required(*names)
  has_required_or_attributes(names, @required_attributes)
end
in_document(element_name) click to toggle source
# File lib/wadl/cheap_schema.rb, line 71
def in_document(element_name)
  @names[:element]    = element_name
  @names[:member]     = element_name
  @names[:collection] = element_name + 's'
end
inherit(from) click to toggle source
# File lib/wadl/cheap_schema.rb, line 50
def inherit(from)
  init

  ATTRIBUTES.each { |attr|
    value = from.send(attr)
    instance_variable_set("@#{attr}", value.dup) if value
  }

  %w[may_be_reference contents_are_mixed_data].each { |attr|
    instance_variable_set("@#{attr}", from.instance_variable_get("@#{attr}"))
  }
end
inherited(klass) click to toggle source
# File lib/wadl/cheap_schema.rb, line 63
def inherited(klass)
  klass.inherit(self)
end
init() click to toggle source
# File lib/wadl/cheap_schema.rb, line 45
def init
  @names, @members, @collections = {}, {}, {}
  @required_attributes, @attributes = [], []
end
may_be_reference() click to toggle source
# File lib/wadl/cheap_schema.rb, line 152
      def may_be_reference
        @may_be_reference = true

        find_method_name = "find_#{names[:element]}"

        class_eval "          def dereference
            return self unless href = attributes['href']

            unless @referenced
              p = self

              until @referenced || !p
                begin
                  p = p.parent
                end until !p || p.respond_to?(:#{find_method_name})

                @referenced = p.#{find_method_name}(href, false) if p
              end
            end

            dereference_with_context(@referenced) if @referenced
          end
", __FILE__, __LINE__ + 1
      end
may_be_reference?() click to toggle source
# File lib/wadl/cheap_schema.rb, line 67
def may_be_reference?
  @may_be_reference
end
new() click to toggle source
# File lib/wadl/cheap_schema.rb, line 269
def initialize
  @attributes, @contents, @referenced = {}, nil, nil
end

Private Class Methods

define_dereferencing_accessors(symbols, getter, setter) click to toggle source
# File lib/wadl/cheap_schema.rb, line 245
      def define_dereferencing_accessors(symbols, getter, setter)
        symbols.each { |name|
          name = name.to_s

          class_eval "            def #{name}; #{getter % name}; end
            def #{name}=(value); #{setter % name}; end
", __FILE__, __LINE__ + 1 unless name =~ /\W/
        }
      end
has_required_or_attributes(names, var) click to toggle source
# File lib/wadl/cheap_schema.rb, line 256
def has_required_or_attributes(names, var)
  names.each { |name|
    var << name
    @index_attribute ||= name.to_s
    name == :href ? attr_accessor(name) : dereferencing_attr_accessor(name)
  }
end

Public Instance Methods

dereference() click to toggle source

A null implementation so that foo.dereference will always return the “real” object.

# File lib/wadl/cheap_schema.rb, line 285
def dereference
  self
end
dereference_with_context(referent) click to toggle source

This object is a reference to another object. This method returns an object that acts like the other object, but also contains any neccessary context about this object. See the ResourceAndAddress implementation, in which a dereferenced resource contains information about the parent of the resource that referenced it (otherwise, there's no way to build the URI).

# File lib/wadl/cheap_schema.rb, line 279
def dereference_with_context(referent)
  referent
end
each_attribute() { |attr, val| ... } click to toggle source
# File lib/wadl/cheap_schema.rb, line 295
def each_attribute
  [self.class.required_attributes, self.class.attributes].each { |list|
    list.each { |attr|
      val = attributes[attr.to_s]
      yield attr, val if val
    }
  }
end
each_collection() { |collection| ... } click to toggle source
# File lib/wadl/cheap_schema.rb, line 311
def each_collection
  self.class.collections.each_value { |collection_class|
    collection = send(collection_class.names[:collection])
    yield collection if collection && !collection.empty?
  }
end
each_member() { |member| ... } click to toggle source
# File lib/wadl/cheap_schema.rb, line 304
def each_member
  self.class.members.each_value { |member_class|
    member = send(member_class.names[:member])
    yield member if member
  }
end
matches?(name) click to toggle source

Returns whether or not the given name matches this object. By default, checks the index key for this class.

# File lib/wadl/cheap_schema.rb, line 291
def matches?(name)
  index_key == name
end
paths(level = default = 0) click to toggle source
# File lib/wadl/cheap_schema.rb, line 318
def paths(level = default = 0)
  klass, paths = self.class, []
  return paths if klass.may_be_reference? && attributes['href']

  if klass == Resource
    path = attributes['path']
    paths << [level, path] if path
  elsif klass == HTTPMethod
    paths << [level]
  end

  each_member { |member|
    paths.concat(member.paths(level))
  }

  each_collection { |collection|
    collection.each { |member| paths.concat(member.paths(level + 1)) }
  }

  if default
    memo = []

    paths.map { |_level, _path|
      if _path
        memo.slice!(_level..-1)
        memo[_level] = _path

        nil  # ignore
      else
        memo.join('/')
      end
    }.compact
  else
    paths
  end
end
to_s(indent = 0, is_collection = false) click to toggle source
# File lib/wadl/cheap_schema.rb, line 355
def to_s(indent = 0, is_collection = false)
  klass = self.class

  a = '  '
  i = a * indent
  s = "#{is_collection ? a * (indent - 1) + '- ' : i}#{klass.name}\n"

  if klass.may_be_reference? and href = attributes['href']
    s << "#{i}= href=#{href}\n"
  else
    each_attribute { |attr, val|
      s << "#{i}* #{attr}=#{val}\n"
    }

    each_member { |member|
      s << member.to_s(indent + 1)
    }

    each_collection { |collection|
      s << "#{i}> Collection of #{collection.size} #{collection.class}(s)\n"
      collection.each { |member| s << member.to_s(indent + 2, true) }
    }

    if @contents && !@contents.empty?
      sep = '-' * 80
      s << "#{sep}\n#{@contents.join(' ').strip}\n#{sep}\n"
    end
  end

  s
end