Class: WADL::CheapSchema

Inherits:
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

- (CheapSchema) initialize

A new instance of CheapSchema



271
272
273
# File 'lib/wadl/cheap_schema.rb', line 271

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

Instance Attribute Details

- (Object) attributes (readonly)

Returns the value of attribute attributes



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

def attributes
  @attributes
end

- (Object) href

Returns the value of attribute href



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

def href
  @href
end

- (Object) index_key

Returns the value of attribute index_key



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

def index_key
  @index_key
end

- (Object) parent

Returns the value of attribute parent



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

def parent
  @parent
end

Class Method Details

+ (Object) as_collection(collection_name)



79
80
81
# File 'lib/wadl/cheap_schema.rb', line 79

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

+ (Object) as_member(member_name)



83
84
85
# File 'lib/wadl/cheap_schema.rb', line 83

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

+ (Object) contents_are_mixed_data



87
88
89
# File 'lib/wadl/cheap_schema.rb', line 87

def contents_are_mixed_data
  @contents_are_mixed_data = true
end

+ (Object) dereferencing_attr_accessor(*symbols)



139
140
141
142
143
144
# File 'lib/wadl/cheap_schema.rb', line 139

def dereferencing_attr_accessor(*symbols)
  define_dereferencing_accessors(symbols,
    'dereference.attributes["%s"]',
    'dereference.attributes["%s"] = value'
  )
end

+ (Object) dereferencing_instance_accessor(*symbols)



131
132
133
134
135
136
137
# File 'lib/wadl/cheap_schema.rb', line 131

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

+ (Object) from_element(parent, element, need_finalization)

Turn an XML element into an instance of this class.



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
242
243
# File 'lib/wadl/cheap_schema.rb', line 181

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

+ (Object) has_attributes(*names)



146
147
148
# File 'lib/wadl/cheap_schema.rb', line 146

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

+ (Object) has_many(*classes)



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
128
129
# File 'lib/wadl/cheap_schema.rb', line 98

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

+ (Object) has_one(*classes)



91
92
93
94
95
96
# File 'lib/wadl/cheap_schema.rb', line 91

def has_one(*classes)
  classes.each { |klass|
    @members[klass.names[:element]] = klass
    dereferencing_instance_accessor(klass.names[:member])
  }
end

+ (Object) has_required(*names)



150
151
152
# File 'lib/wadl/cheap_schema.rb', line 150

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

+ (Object) in_document(element_name)



73
74
75
76
77
# File 'lib/wadl/cheap_schema.rb', line 73

def in_document(element_name)
  @names[:element]    = element_name
  @names[:member]     = element_name
  @names[:collection] = element_name + 's'
end

+ (Object) inherit(from)



52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/wadl/cheap_schema.rb', line 52

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

+ (Object) inherited(klass)



65
66
67
# File 'lib/wadl/cheap_schema.rb', line 65

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

+ (Object) init



47
48
49
50
# File 'lib/wadl/cheap_schema.rb', line 47

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

+ (Object) may_be_reference



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

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

+ (Boolean) may_be_reference?

Returns:

  • (Boolean)


69
70
71
# File 'lib/wadl/cheap_schema.rb', line 69

def may_be_reference?
  @may_be_reference
end

Instance Method Details

- (Object) dereference

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



287
288
289
# File 'lib/wadl/cheap_schema.rb', line 287

def dereference
  self
end

- (Object) dereference_with_context(referent)

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



281
282
283
# File 'lib/wadl/cheap_schema.rb', line 281

def dereference_with_context(referent)
  referent
end

- (Object) each_attribute



297
298
299
300
301
302
303
304
# File 'lib/wadl/cheap_schema.rb', line 297

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

- (Object) each_collection



313
314
315
316
317
318
# File 'lib/wadl/cheap_schema.rb', line 313

def each_collection
  self.class.collections.each_value { |collection_class|
    collection = send(collection_class.names[:collection])
    yield collection if collection && !collection.empty?
  }
end

- (Object) each_member



306
307
308
309
310
311
# File 'lib/wadl/cheap_schema.rb', line 306

def each_member
  self.class.members.each_value { |member_class|
    member = send(member_class.names[:member])
    yield member if member
  }
end

- (Boolean) matches?(name)

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

Returns:

  • (Boolean)


293
294
295
# File 'lib/wadl/cheap_schema.rb', line 293

def matches?(name)
  index_key == name
end

- (Object) paths(level = default = 0)



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
354
355
# File 'lib/wadl/cheap_schema.rb', line 320

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

- (Object) to_s(indent = 0, collection = false)



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
386
387
# File 'lib/wadl/cheap_schema.rb', line 357

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