moldy

A javascript model API for the browser + server that is actually worth using.

Thu, 07 Aug 2014 01:42:15 GMT moldy Express server listening on port 3000 Thu, 07 Aug 2014 01:42:15 GMT moldy Express server listening on port 3000 Thu, 07 Aug 2014 01:42:16 GMT moldy Express server listening on port 3000 Thu, 07 Aug 2014 01:42:16 GMT moldy Express server listening on port 3000

TOC

moldy

Create a Moldy.

var personMoldy = Moldy.extend( 'person' )
    .$property( 'id' )
    .$property( 'name' )
    .$property( 'age' )
    .create();
personMoldy.should.have.a.property( 'id', null );
personMoldy.should.have.a.property( 'name', null );
personMoldy.should.have.a.property( 'age', null );

Properties can by strongly typed. If a type has been defined, values are cast to that type automatically. If a value cannot be cast to a type then the value will be set to null or the default if it has been defined.

var personMoldy = Moldy.extend( 'person', {
                properties: {
                    'age': 'number',
                    'active': {
                        type: 'boolean',
                        default: false
                    },
                    'tags': 'array'
                }
            } ).create();
            /**
             * When a model's properties have been `typed` the assigned values are cast on the fly
             * to ensure the model's data remains sanitized.
             */
            /**
             * Cast a `string` for `age` to a `number`
             */
            personMoldy.age = '13';
            personMoldy.age.should.equal( 13 ).and.be.an.instanceOf( Number );
            /**
             * Cast a truthy `string` for `active` as a `boolean`
             */
            personMoldy.active = 'yes';
            personMoldy.active.should.equal( true ).and.be.an.instanceOf( Boolean );
            /**
             * `active` is typed as a `boolean` _and_ a `default` has been defined. When an
             * assigned value that cannot be cast as a `boolean` is set then the `default` will
             * apply.
             */
            personMoldy.active = 'this is not a boolean';
            should( personMoldy.active ).equal( false ).and.be.an.instanceOf( Boolean );
            /**
             * Cast a `string` for `tags` as an `array`
             */
            personMoldy.tags = 'lorem';
            should( personMoldy.tags ).eql( [ 'lorem' ] ).and.be.an.instanceOf( Array );

Properties can be optional. By making a property optional, isValid() and toJson() will ignore it if is has not been set.

var personMoldy = Moldy.extend( 'person' )
    .$property( 'id' )
    .$property( 'name' )
    .$property( 'age', {
        type: 'number',
        optional: true
    } )
    .$property( 'active', {
        type: 'boolean',
        default: false
    } )
    .$property( 'tags', {
        type: 'array',
        optional: true
    } )
    .create();
/**
 * To ensure this `person` is valid we only need to set the `id` and `name` because
 * the other keys are either `optional` or have `defaults`.
 */
personMoldy.id = 1;
personMoldy.name = 'David';
personMoldy.$isValid().should.be.ok;

A property can be defined as array of a type like an array of strings, or an array of numbers.

var personMoldy = Moldy.extend( 'person' )
    .$property( 'id' )
    .$property( 'tags', {
        type: [ 'string' ]
    } )
    .create();
/**
 * When defining an array of a type, the arrays are normal arrays however they have been
 * extended to allow hooks into the necessary methods for sanitization.
 */
personMoldy.tags.should.be.an.Array;
personMoldy.tags.should.have.a.property( 'length' ).and.be.a.Number;
personMoldy.tags.should.have.a.property( 'pop' ).and.be.a.Function;
personMoldy.tags.should.have.a.property( 'push' ).and.be.a.Function;
personMoldy.tags.should.have.a.property( 'reverse' ).and.be.a.Function;
personMoldy.tags.should.have.a.property( 'shift' ).and.be.a.Function;
personMoldy.tags.should.have.a.property( 'sort' ).and.be.a.Function;
personMoldy.tags.should.have.a.property( 'splice' ).and.be.a.Function;
personMoldy.tags.should.have.a.property( 'unshift' ).and.be.a.Function;
/**
 * Pushing a value - like normal
 */
personMoldy.tags.push( 'yellow' );
/**
 * We are pushing a `number` here to show how the value will be cast to a string
 */
personMoldy.tags.push( 1 );
/**
 * The value `1` is now a string
 */
personMoldy.tags[ 1 ].should.equal( '1' );
personMoldy.tags.should.have.a.lengthOf( 2 );
personMoldy.tags.should.eql( [ 'yellow', '1' ] );
/**
 * A gotcha for using primitive types in this context is that they are not sanitized
 * based on the schema if they are changed directly
 */
personMoldy.tags[ 1 ] = 1;
/**
 * Technically this should have cast the number `1` to a string but it was a design
 * decision not to add getters/setters to each item in an array. A santize method will
 * be added in the next version
 */
personMoldy.tags[ 1 ].should.equal( 1 );

Array types can also be model schemas.

var personMoldy = Moldy.extend( 'person' )
    .$property( 'cars', {
        type: [ {
            name: 'car',
            properties: {
                make: 'string',
                model: {
                    type: 'string',
                    default: ''
                },
                year: 'number'
            }
        } ]
    } )
    .create();
/**
 * Note, we are missing the `model` key and the `year` is a string
 */
personMoldy.cars.push( {
    make: 'honda',
    year: '2010'
} );
personMoldy.cars[ 0 ].$json().should.eql( {
    id: undefined,
    make: 'honda',
    model: '',
    year: 2010
} );

A url (endpoint) is automatically generated based on the Moldy name, key, $url() and $baseUrl().

var Person = Moldy.extend( 'person', {
    baseUrl: '/api'
} );
var personMoldy = Person.create();
Person.$url().should.eql( '/api/person' );
/**
 * The url can be changed using either `base()` or `url()`
 */
Person.$url( 'v1' );
Person.$url().should.eql( '/api/person/v1' );

To find by id or key, give an object with appropriate conditions.

var personMoldy = Moldy.extend( 'person', {
    key: 'guid',
    properties: {
        name: ''
    }
} );
personMoldy.$findOne( {
    guid: '5f55821f-3a28-45c3-b91d-7df927a863d8'
}, function ( _error_res ) {
    if ( _error ) {
        return _done( _error );
    }
    _done();
} );

If an adapter responds with an array the first item will be returned.

var Person = Moldy.extend( 'person', {
    key: 'guid',
    properties: {
        name: ''
    }
} );
/**
 * In this example the end point GET `http://localhost:3000/api` returns an array of items.
 * Moldy will return the first item out of the array. If you need to return an array you can
 * use the $collection method.
 */
Person.$findOne( function ( _error_res ) {
    if ( _error ) {
        return _done( _error );
    }
    _res.should.not.be.an.Array;
    _done();
} );

To find an array of models.

var Person = Moldy.extend( 'person', {
    key: 'guid',
    properties: {
        name: 'string'
    }
} );
Person.$find( function ( _error_people ) {
    if ( _error ) {
        return _done( _error );
    }
    _people.should.be.an.Array.with.a.lengthOf( 3 );
    _people.forEach( function ( _person ) {
        _person.should.be.a.Moldy;
    } );
    _done();
} );

To save the model, call save(). If the model is dirty (has not been saved to the server and therefore does not have a valid key) then the http method will be POST. If the model has been saved, then the http method will be PUT.

var personMoldy = Moldy.extend( 'person', {
    properties: {
        name: 'string'
    }
} ).create();
personMoldy.name = 'David';
personMoldy.$save( function ( _error_res ) {
    if ( _error ) {
        return _done( _error );
    }
    personMoldy.$json().should.eql( _res.$json() );
    personMoldy.should.have.a.property( 'id' );
    personMoldy.name = 'Mr David';
    personMoldy.$save( function ( _error_res ) {
        personMoldy.should.eql( _res );
        _done( _error );
    } );
} );

To destroy a model, call destroy().

var personMoldy = Moldy.extend( 'person', {
    key: 'guid',
    properties: {
        name: 'string'
    }
} ).create();
personMoldy.name = 'David';
personMoldy.$save( function ( _error_res ) {
    if ( _error ) {
        return _done( _error );
    }
    personMoldy.$destroy( function ( _error_res ) {
        personMoldy.$isDirty().should.be.true;
        //personMoldy.$isValid().should.be.false; -- DO NOT GET WHY SHOULD BE FALSE 
        //personMoldy.__destroyed.should.be.true; 
        _done( _error );
    } );
} );