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

- (CheapSchema) initialize

Returns a new instance of CheapSchema



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

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

Instance Attribute Details

- (Object) attributes (readonly)

Returns the value of attribute attributes



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

def attributes
  @attributes
end

- (Object) href

Returns the value of attribute href



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

def href
  @href
end

- (Object) index_key

Returns the value of attribute index_key



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

def index_key
  @index_key
end

- (Object) parent

Returns the value of attribute parent



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

def parent
  @parent
end

Class Method Details

+ (Object) as_collection(collection_name)



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

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

+ (Object) as_member(member_name)



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

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

+ (Object) contents_are_mixed_data



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

def contents_are_mixed_data
  @contents_are_mixed_data = true
end

+ (Object) dereferencing_attr_accessor(*symbols)



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

+ (Object) dereferencing_instance_accessor(*symbols)



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

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

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

+ (Object) has_attributes(*names)



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

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

+ (Object) has_many(*classes)



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

+ (Object) has_one(*classes)



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

+ (Object) has_required(*names)



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

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

+ (Object) in_document(element_name)



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

+ (Object) inherit(from)



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

+ (Object) inherited(klass)



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

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

+ (Object) init



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

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

+ (Object) may_be_reference



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

+ (Boolean) may_be_reference?

Returns:

  • (Boolean)


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

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.



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

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



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

def dereference_with_context(referent)
  referent
end

- (Object) each_attribute



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

- (Object) each_collection



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

- (Object) each_member



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

- (Boolean) matches?(name)

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

Returns:

  • (Boolean)


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

def matches?(name)
  index_key == name
end

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



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

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



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