Algorithm
To start with something, let’s explain type system. Every concrete type
consists of enclosed class and set of attributes. Every attribute, in
turn, has it’s own type, name, and set of settings. Most of the time
attributes represent the very same concept as the ones defined by
attr_accessor
, however, there is also a concept of virtual attributes -
they are created as a holder for attached type, and this serves for
non-trivial cases (say, all container types, such as collections). Type
classes, among other things, include entity factory, normalizer,
denormalizer, attribute enumerator, attribute extractor and attribute
injector (those will be discussed in a moment).
When mapper is asked to map input I
into types T1...Tn
, it uses
following algorithm:
- If
I
is not aT
, then normalizeI
asI1
, create instanceE1
, and denormalizeI1
intoE1
, otherwise assignI
toE1
- Enumerate all attributes of
E1
. Recursively start scenario from the start for every attribute, validate result, and accumulate results - If none of attributes have changed, just return
E1
- If there is at least one changed attribute, create instance
E2
and set all attributes on it. - If something went wrong or is impossible, throw mapping error
- If there are more target types specified, catch exception and try next type
It is important to understand that mapper works only at one level at a time - denormalization never tries to denormalize entity and it’s attributes, it’s just creates new entity and assigns attribute without paying attention to attribute contents. It just goes level after level, reaching the bottom and then rebuilding target entity level by level, starting with leaves and going closer to root.
Because some of those operations may get tricky (treating every class as a bag of instance variables is bad idea, because Set would be normalized as two-level hash instead of array), they are not made by mapper directly, but rather by type helpers mentioned above:
- Factory allows new entity creation
- Normalizer and denormalizer speak for themselves
- Enumerator enumerates attributes of passed entity
- Extractor extracts attributes out of normalized object
- Acceptor takes created entity on creation and accepts attribute values, setting them on passed entity
Those are described in detail on handlers page.
This allows mapper to never know real classes structure, delegating all weird work onto specific types. The concrete type class provides reasonable defaults for helpers that won’t be changed in most cases, however, as specified above, collections need some special treatment, which is easily targeted by such scheme. Default types (Hash, Enumerable, Set) are already bundled in.
Whenever mapper receives a request for mapping, it analyzes which type it has got, and uses proper algorithm for it. Primitives are mostly just passed through, if class is not registered as entity, defaults are used, otherwise it’s definition is fetched from registry.