]
} 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[

ptional] }
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 => {

ptional => false },
:street2 => {

ptional => true },
:city => {

ptional => false },
:state => { :valregex => /[A-Z]{2}/,

ptional => false },
:uszip => { :valregex => /[0-9]{5}(-[0-9]{4})?/,

ptional => 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 <
[email protected]>
--Greg