Class: WADL::CheapSchema

Inherits:
Object
  • Object
show all
Defined in:
lib/wadl/cheap_schema.rb

Overview

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

Direct Known Subclasses

Documentation, HasDocs

Constant Summary

ATTRIBUTES =
%w[names members collections required_attributes attributes]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCheapSchema



269
270
271
# File 'lib/wadl/cheap_schema.rb', line 269

def initialize
  @attributes, @contents, @referenced = {}, nil, nil
end

Instance Attribute Details

#attributesObject (readonly)

Returns the value of attribute attributes



267
268
269
# File 'lib/wadl/cheap_schema.rb', line 267

def attributes
  @attributes
end

#hrefObject

Returns the value of attribute href



266
267
268
# File 'lib/wadl/cheap_schema.rb', line 266

def href
  @href
end

#index_keyObject

Returns the value of attribute index_key



266
267
268
# File 'lib/wadl/cheap_schema.rb', line 266

def index_key
  @index_key
end

#parentObject

Returns the value of attribute parent



266
267
268
# File 'lib/wadl/cheap_schema.rb', line 266

def parent
  @parent
end

Class Method Details

.as_collection(collection_name) ⇒ Object



77
78
79
# File 'lib/wadl/cheap_schema.rb', line 77

def as_collection(collection_name)
  @names[:collection] = collection_name
end

.as_member(member_name) ⇒ Object



81
82
83
# File 'lib/wadl/cheap_schema.rb', line 81

def as_member(member_name)
  @names[:member] = member_name
end

.contents_are_mixed_dataObject



85
86
87
# File 'lib/wadl/cheap_schema.rb', line 85

def contents_are_mixed_data
  @contents_are_mixed_data = true
end

.dereferencing_attr_accessor(*symbols) ⇒ Object



137
138
139
140
141
142
# 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) ⇒ Object



129
130
131
132
133
134
135
# 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) ⇒ Object

Turn an XML element into an instance of this class.



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# 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) ⇒ Object



144
145
146
# File 'lib/wadl/cheap_schema.rb', line 144

def has_attributes(*names)
  has_required_or_attributes(names, @attributes)
end

.has_many(*classes) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# 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 <<-EOT, __FILE__, __LINE__ + 1
      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
    EOT
  }
end

.has_one(*classes) ⇒ Object



89
90
91
92
93
94
# 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) ⇒ Object



148
149
150
# File 'lib/wadl/cheap_schema.rb', line 148

def has_required(*names)
  has_required_or_attributes(names, @required_attributes)
end

.in_document(element_name) ⇒ Object



71
72
73
74
75
# 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) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
# 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) ⇒ Object



63
64
65
# File 'lib/wadl/cheap_schema.rb', line 63

def inherited(klass)
  klass.inherit(self)
end

.initObject



45
46
47
48
# File 'lib/wadl/cheap_schema.rb', line 45

def init
  @names, @members, @collections = {}, {}, {}
  @required_attributes, @attributes = [], []
end

.may_be_referenceObject



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/wadl/cheap_schema.rb', line 152

def may_be_reference
  @may_be_reference = true

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

  class_eval <<-EOT, __FILE__, __LINE__ + 1
    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
  EOT
end

.may_be_reference?Boolean



67
68
69
# File 'lib/wadl/cheap_schema.rb', line 67

def may_be_reference?
  @may_be_reference
end

Instance Method Details

#dereferenceObject

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



285
286
287
# File 'lib/wadl/cheap_schema.rb', line 285

def dereference
  self
end

#dereference_with_context(referent) ⇒ Object

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).



279
280
281
# File 'lib/wadl/cheap_schema.rb', line 279

def dereference_with_context(referent)
  referent
end

#each_attributeObject



295
296
297
298
299
300
301
302
# 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_collectionObject



311
312
313
314
315
316
# 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_memberObject



304
305
306
307
308
309
# 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) ⇒ Boolean

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



291
292
293
# File 'lib/wadl/cheap_schema.rb', line 291

def matches?(name)
  index_key == name
end

#paths(level = default = 0) ⇒ Object



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# 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, collection = false) ⇒ Object



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/wadl/cheap_schema.rb', line 355

def to_s(indent = 0, collection = false)
  klass = self.class

  a = '  '
  i = a * indent
  s = "#{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