Skip to content

Overview of Types vs Attributes

Josep M. Blanquer edited this page Oct 28, 2015 · 1 revision

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.

A Type

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 the String 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 and String 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 do Tags.example => ["undashed", "epitoxoid"].
  • Types can support different decoding formats when loading values for a type (i.e., MyType.load(value)). Some basic types like a Struct or Hash 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 a 0 values. The Boolean type can understand and coerece even more variations of incoming values such as false, '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 Attribute

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.
Clone this wiki locally