-
Notifications
You must be signed in to change notification settings - Fork 16
Overview of Types vs Attributes
The main primitives of the library are types and Attributes.
A type represents a data structure of any complexity, represented by a Ruby class. An attribute is represented by an instance of the Attribute
class that is associated to given type through some type-specific options, as well as attribute properties such as required:
, description:
, default:
... For example: I can have an attribute named grade
that is of type Integer
, that it is required, and is bound to the min: 0
and max: 10
options for its values.
Combining Types and Attributes, allows the creation of very expressive and complex multi-level structures. For example, a Struct/Hash
types allow you to define named sub-attributes that are bound to certain existence or defaults requirements, and that are bound to other (potentially multi-nested) Attributor types.
Let's take a look at some more specifics.
An Attributor "Type" is a ruby Class. In many ways, Types include all of the basic functionality of any Ruby type, but they also pack much more:
- They can be instantiated. For example, one can do
MyType.new
and then set its attributes, or by simply loading all its attributes from a given existing value (MyType.load(value)
) - Types can have as many specific options. For example, an
Integer
type has a:min
and:max
options and theString
type has a:regexp
one which would always be checked against their values. Options for a type can be as complex as necessary. - Types are easily composable, including their recursive loading and validation. There are many basic types included within the library, which makes creating supertypes a breeze. For example, one could define a type for array of tags (i.e. Tags), by combining the existing
Collection
andString
types like so:Tags = Attributor::Collection.of(String)
- Instances can have their values be validated using
MyType.validate
, which will return a well-defined list of all validation errors that have been found. - Examples of type instances can be automatically generated. For example, with our
Tags
type above I can easily doTags.example => ["undashed", "epitoxoid"]
. - Types can support different decoding formats when loading values for a type (i.e.,
MyType.load(value)
). Some basic types like aStruct
orHash
in the existing library support loading structured values not just from a native Ruby hash, but also from a JSON string. - Types can support coercion of the loaded values to their native types. For example, the
Integer
type can load both a"1"
or a0
values. TheBoolean
type can understand and coerece even more variations of incoming values such asfalse, 'false', 'FALSE', '0', 0, 'f', 'F'
for the false value. - Any type can generate a JSON document describing its definition. The JSON document is similar in concept to "json-schema", but not quite compatible. (It is on the roadmap to see if it is possible to output pure json-schema, although not clear it is, as there are some powerful things in Attributor types that might not be describable in json-schema)
An Attributor attribute
can be thought of a named variable that is defined to be of a given (Attributor) Type. For example, I can define an attribute named "email" which is of type String
with a given regular expression. attribute :email, String, regexp: /\w+@\w+.com/
:
- Attributes can be defined against unmodified existing types (i.e. of type Integer), or applying given options to it (i.e., a String with a regexp like the case above).
- Attribute can be required (or conditionally) required. Validation of an type containing a required attribute will fail if not provided.
- Attributes can have defaults. Non-required attributes will acquire these default value if none is passed.
- Attributes can have a restricted set of valid values, which will always be checked against during validation. Example values for an attribute will always generated according the valid values when specified.
- Attributes can also have a
:description
associated with them as well as contain any opaque set of data under the:custom_data
option.