From: Gregory Seidman Date: 2006-02-25T02:16:34+09:00 Subject: Re: Creating Ruby Classes from XSD? On Fri, Feb 24, 2006 at 10:27:52PM +0900, Geoffrey Lane wrote: [...] } XSD schema (for all of its imperfections) has the ability to specify } constraints on the data that help validate it. Up front your service } can define things like: } } (pseudo-XSD) } US Address: } Street1 = required } Street2 = optional } City = required } State = [A-Z]{2} } USZip = [0-9]{5}(-[0-9]{4})? } } Those kinds of constraints can be done in code, but no language I } know of can make an interface to a method that explicit. Converting those constraints to Ruby is a challenge, but far from impossible. First off, I'd store the fields in a hash and override method_missing to implement accessors and mutators as requested (the same way ActiveRecord implements find_by_*). The regex constraints are relatively simple. They just require minor adjustments to account for the differences in regex flavors between XSD and Ruby. I'd store a hash of symbol to regex-and-optional for use by a generalized setter method. The required/optional issue is dealt with in the constructor. The constructor takes a (possibly nested) hash, raises an exception if a required field is missing or an unknown field is present, and uses mutators to set the fields. You might also have a constructor that converts an XML (sub-)tree into a (nested) hash before passing it to the other constructor. Implementing your example: class XSDclass def set_field(symbol, value) constraint = constraints[symbol] fail "No such field" unless constraint valregex = constraint[:valregex] if valregex && valregex !~ value fail "Invalid value '#{value}' for field #{symbol}" end @fields[symbol] = value end def method_missing(method_id, *arguments) if constraints[method_id] && arguments.length == 0 return @fields[method_id] elsif method_id.to_s[-1].chr == '=' setter_for = method_id.to_s[0...-1].to_sym if constraints[setter_for] && arguments.length == 1 return set_field(setter_for, arguments[0]) end end super end private def constraints {} end def initialize(hashed_fields) required_fields = constraints.select { |k,v| not v[:optional] } required_fields.map! { |f| f[0] } missing = (required_fields - hashed_fields.keys).join(', ') excess = (hashed_fields.keys - constraints.keys).join(', ') if (missing.length + excess.length) != 0 errors = "Validation error constructing #{self.class}:\n\n" errors << "\tMissing fields: #{missing}\n" if missing.length > 0 errors << "\tUnknown fields: #{excess}\n" if excess.length > 0 fail errors end @fields = {} hashed_fields.each { |k,v| set_field(k,v) } end end class US_Address < XSDclass Constraints = { :street1 => { :optional => false }, :street2 => { :optional => true }, :city => { :optional => false }, :state => { :valregex => /[A-Z]{2}/, :optional => false }, :uszip => { :valregex => /[0-9]{5}(-[0-9]{4})?/, :optional => false } } def constraints Constraints end def initialize(hashed_fields) super end end The conversion of XSD into a Ruby class definition, conversion of regular expressions, a better implementation of the method_missing override (which creates accessors/mutators for the next use), and the XML subtree constructor are left as exercises to the reader. Note that I have actually tested the code above; it works. } Geoff Lane --Greg