Serialization

getMemento

The memento pattern is an established pattern in ColdBox apps. A memento in this case is a simple representation of your entity using arrays, structs, and simple values.

For instance, the following example shows a User entity and its corresponding memento:

component extends="quick.models.BaseEntity" accessors="true" {

    property name="id";
    property name="username";
    property name="email";
    property name="password";
    property name="createdDate";
    property name="modifiedDate";

}
{
    "id" = 1,
    "username" = "JaneDoe",
    "email" = "jane@example.com",
    "password" = "$2a$04$2nVI5rPOfl6.hrflkhBWOObO5Z7lXGJpi1vlosY74NrL/CKdpWqZS"
    "createdDate" = "{ts '2018-03-12 16:14:10'}",
    "modifiedDate" = "{ts '2018-03-12 16:14:10'}"
}

Quick bundles in the excellent Mementifier library to handle converting entities to mementos. This gives you excellent control over serialization using a this.memento struct on the entity and passing in arguments to the getMemento function.

this.memento

By default, Quick includes all defined attributes as includes. You can change this or add other Mementifier options by defining your own this.memento struct on your entity. Your custom this.memento struct will be merged with Quick's default, so you can only define what changes you need.

Here is the default Quick memento struct. It is inside the instanceReady() lifecycle method in this example because retrieveAttributeNames() relies on the entity being wired (though not loaded); it is not otherwise necessary to put this.memento inside instanceReady().

function instanceReady() {
    this.memento = {
        "defaultIncludes" : retrieveAttributeNames( withVirtualAttributes = true ),
          "defaultExcludes" : [],
          "neverInclude"    : [],
          "defaults"        : {},
          "mappers"         : {},
          "trustedGetters"  : true,
          "ormAutoIncludes" : false
    };
}

getMemento Arguments

You can also control the serialization of a memento at call time using Mementifier's getMemento arguments.

struct function getMemento(
    includes = "", // or []
    excludes = "", // or []
    struct mappers = {},
    struct defaults = {},
    boolean ignoreDefaults = false,
    boolean trustedGetters
)

Custom getMemento

If this does not give you the control you need, you can further modify the memento by overriding the getMemento function on your entity. In this case, a $getMemento function will be available which is the Mementifier function.

component extends="quick.models.BaseEntity" accessors="true" {

    property name="id";
    property name="username";
    property name="email";
    property name="password";
    property name="createdDate";
    property name="modifiedDate";

    function getMemento() {
        return {
            id = getId(),
            username = getUsername(),
            email = getEmail(),
            createdDate = dateFormat( getCreatedDate(), "MM/DD/YYYY" ),
            // can also use getAttribute if you want to bypass a custom getter
            modifiedDate = dateFormat( retrieveAttribute( "modifiedDate" ), "MM/DD/YYYY" )
        };
    }

}
{
    "id" = 1,
    "username" = "JaneDoe",
    "email" = "jane@example.com",
    "createdDate" = "03/12/2018",
    "modifiedDate" = "03/12/2018"
}

asMemento

Sometimes when retrieving entities or executing a Quick query, you already know you want mementos back. You can skip the step of calling getMemento yourself or mapping over the array of results returned by calling asMemento before executing the query. asMemento takes the same arguments that getMemento does. It will pass those arguments on and convert your entities to mementos after executing the query. This works for all the query execution methods - find, first, get, paginate, etc.

getInstance( "User" )
    .asMemento( excludes = [ "password" ] )
    .paginate( 1, 10 );

$renderData

The $renderData method is a special method for ColdBox. When returning a model from a handler, this method will be called and the value returned will be used as the serialized response. This let's you simply return an entity from a handler for your API. By default this will call getMemento().

component {

    // /users/:id
    function show( event, rc, prc ) {
        return getInstance( "User" ).findOrFail( rc.id );
    }

}
{
    "id" = 1,
    "username" = "JaneDoe",
    "email" = "jane@example.com",
    "createdDate" = "03/12/2018",
    "modifiedDate" = "03/12/2018"
}

QuickCollection also defines a $renderData method, which will delegate the call to each entity in the collection and return the array of serialized entities.

Automatically serializing a returned collection only works when using the QuickCollection as your entity's newCollection.

component {

    function index( event, rc, prc ) {
        return getInstance( "User" ).all();
    }

}
[
    {
        "id" = 1,
        "username" = "JaneDoe",
        "email" = "jane@example.com",
        "createdDate" = "03/12/2018",
        "modifiedDate" = "03/12/2018"
    },
    {
        "id" = 2,
        "username" = "JohnDoe",
        "email" = "john@example.com",
        "createdDate" = "03/14/2018",
        "modifiedDate" = "03/15/2018"
    }
]

asQuery

Quick can skip creating entities all together and return an array of structs from qb using the asQuery method.

getInstance( "User" )
    .asQuery()
    .paginate( 1, 10 );

asQuery also supports Eager Loading. It will add a key matching the relationship name to the returned struct.

getInstance( "Post" )
    .with( "author" )
    .asQuery()
    .paginate( 1, 10 );
{
    "id": 1,
    "title": "My First Post",
    "body": "....",
    "author": {
        "id": 1,
        "firstName": "Eric",
        "lastName": "Peterson"    
    }
}

Using asQuery is usually superior to dropping down qb using retrieveQuery since it achieves the same purpose while also including automatic aliases and eager loading.

Signature

The withAliases property is recursive across eager loads. If you ask for aliases for your initial query (which is the default), all eager loaded queries will also use their respective column aliases.

Last updated