Skip to content
This repository has been archived by the owner on Dec 7, 2019. It is now read-only.

Latest commit

 

History

History
1952 lines (1380 loc) · 50.2 KB

README.md

File metadata and controls

1952 lines (1380 loc) · 50.2 KB

Dulcimer

This project is deprecated and is left intact for history. -@fritzy

Dulcimer is an ORM for an embedded keystore in your Node.js app. The aim is to provide a consistent way of working with keystores that enables enjoyable development.

Riak can be a pain to run on your dev machine. Why not develop against level and deploy against Riak without sacrificing features or the speed of native indexing?

Because when all you have is a hammer, everything looks like a Dulcimer.

Features Include:

  • Models
  • Ordered Objects
  • Lookup by Index
  • Retrieving Models sorted by Index
  • Retrieve index ranges
  • Retrieve with filters
  • Foreign Keys and Foreign Collections
  • Pagination
  • Counts
  • Buckets
  • onSave & onDelete Model Events
  • derived fields
  • field types and validation

The models in this ORM use VeryModel. Dulcimer models extend the definitions and methods.

We currently support Riak and Levelup backends. Redis coming soon.

📒 Licensed MIT

A Quick Example

var dulcimer = require('dulcimer');

dulcimer.connect('./test.db'); //for level
//dulcimer.connect({type: 'riak', host: 'localhost', port: 8087, bucket: 'somebucket'}); //for riak
//dulcimer.connect({type: 'level', path: './test.db'}); //alt for level

var PersonFactory = new dulcimer.Model({
    firstName: {},
    lastName: {},
    fullName: {derive: function () {
        return this.firstName + ' ' + this.lastName;
    }},
}, {name: 'person'});

var nathan = PersonFactory.create({
    firstName: 'Nathan',
    lastName: 'Fritz',
});
nathan.save(function (err) {
    PersonFactory.all(function (err, persons) {
        persons.forEach(function (person) {
            console.dir(person.toJSON());
        });
    });
});

Coming Soon

  • Redis Support
  • Riak Sibling Resolution
  • Import/Export/Transform
  • Write Streams
  • IndexDB in Browsers
  • Patterns Documentation
  • Query and sort against multiple indexes at once
  • Rebuilding Indexes

Index

Installing

npm install dulcimer

Connecting to a Database

Connecting to Level for All Models

Shorthand, just call connect with a string path.

var dulcimer = require('dulcimer');

dulcimer.connect('/some/file/path/to/level');

Or pass a more detailed object, maybe from your config file, to make it easier to switch to Riak with just a configuration change.

var dulcimer = require('dulcimer');

dulcimer.connect({
    type: 'level',
    path: '/some/file/path/to/level',
    bucket: 'defaultbucket'
});

Connecting to Riak for All Models

var dulcimer = require('dulcimer');

dulcimer.connect({
    type: 'riak',
    host: 'localhost',
    port: 8087, //riak protocol buffer port
    bucket: 'somebucket', //default bucket
});

Connecting Based on Config

var duclimer = require('dulcimer');
var config = require('./config.json');

dulcimer.connect(config.database);

Connecting Per Model

The same connect method also exists on Model Factories, which overrides your global connection.

var dulcimer = require('dulcimer');

var Person = new dulcimer.Model({
    first_name: {type: 'string'},
    last_name: {type: 'string'}
});

Person.connect({
    type: 'level',
    path: '/some/level/path'
});

var nathan = Person.create({
    first_name: 'Nathan',
    last_name: 'Fritz'
});

nathan.save(function (err) {
    console.log("Saved %j to %s", nathan.toJSON(), nathan.key);
});

Override Connection Per Command

The connect method returns a levelup-style object with custom extensions to use native-to-db indexes, counters, etc.

You can generate these manually using level-dulcimer and riak-dulcimer (and soon redis-dulcimer).

var LevelDulcimer = require('level-dulcimer');

var db = LevelDulcimer('/some/level/path');

You can then use this database for the db option in relevant commands.

Defining a Model Factory

Model Factories define the platonic model, and can create model instances. These models are extensions on VeryModel. Creating a new model factory involves passing two arguments: an object describing each field, and an options object defining the configuration of the model (leveldb path, model name, etc).

Model Definitions

Every root property of a model definition is a field name with an object value defining types, restrictions, and processing for a model field. In it's simplest form, a field definition can just be an empty object, and then you can assign anything to that field. If a field isn't mentioned in the definition, it won't be saved or loaded.

Referring to Other Models

With foreign keys and other fields, sometimes models can depend on each other, creating peer requirements. This can be awkward in Node.js, so you may refer to those models by ModelFactory instance or by name string.

If you need to look up a model by name elsewhere in your code you can use dulcimer.getModel.

var dulcimer = require('dulcimer');
var CheesecakeFactory = dulcimer.getModel('cheesecake');

Field Definition Properties

When making a model, you must define the fields in the model. A field definition may be a simple empty {} if anything goes.

Most field definition properties that can be functions are called with the model instance as the this context.

type

A string which references a built in type. Built in types include string, array, integer, numeric, enum, boolean. Strings and arrays may have min and max values, both for validation, and max will truncate the results when saving or on toJSON. Enums may include values, an array (and eventually a ECMAScript 6 set).

You can override any of the definition fields of a specified type. Validate, processIn, processOut, and onSet will use both the built-in and your override. The others will replace the definition field.

type does not need to be set at all. In fact, {} is a perfectly valid definition.

Example:

{field: {type: 'string', max: 140}}

validate

The validate field takes a value and should determine whether that value is acceptable or not. It's run during doValidate() or during save if you set the option validateOnSave: true. The function should return a boolean, an array of errors, an empty array, or an error string.

Example:

new dulcimer.Model({field: {
    validate: function (value) {
        //validate on even
        return (value % 2 === 0);
    }
});

processIn

processIn is a function that is passed a value on loading from the database, create, or loadData. It should return a value.

This function is often paired with processOut in order to make an interactive object when in model form, and a serialized form when saved.

processIn does not handle the case of direct assignment like modelinst.field = 'cheese';. Use onSet for this case.

Example:

new dulcimer.Model({someDateField: {
    processIn: function (value) {
        return moment(value);
    },
})

processOut

processOut is a function that takes a value and returns a value, just like processIn, but is typically used to serialize the value for storage. It runs on save() and toJSON().

Example:

new dulcimer.Model({someDateField: {
    processOut: function (value) {
        return value.format(); //turn moment into string
    },
})

onSet

onSet is just like processIn, except that it only runs on direct assignment. It's a function that takes a value and returns a value.

Example:

new dulcimer.Model({someDateField: {
    processIn: function (value) {
        return moment(value);
    },
    onSet: function (value) {
        if (moment.isMoment(value)) {
            return value;
        } else {
            return moment(value);
        }
    },
    processOut: function (value) {
        return value.format();
    },
})

index

When set to true, this field is indexed with every save. This allows you to getByIndex, findByIndex and the ability to use sortBy in various calls.

An indexed field of type 'integer' is indexed differently. Please make sure indexed numbers are of that type.


derive

derive is a function that returns a value whenever the field is accessed (which can be quite frequent). The this context, is the current model instance, so you can access other fields.

Example:

new dulcimer.Model({
    firstName: {type: 'string'},
    lastName: {type: 'string'},
    fullName: {
        type: 'string',
        derive: function () {
            return [this.firstName, this.lastName].join(" ");
        },
    }
});

❗ Warning! DO NOT REFERENCE THE DERIVE FIELD WITHIN ITS DERIVE FUNCTION! You will cause an infinite recursion. This is bad and will crash your program.


foreignKey

foreignKey should be a Model Factory or a string of the factory name. These fields are saved as their key, but when loaded expanded out to be a model instance of the key's value. get will load and expand foreignKeys and foreignCollections up to the depth option provided (which is 5 by default).

When assigning values to this field, you can either assign a model instance or a key string.

Example:

new dulcimer.Model({
    comment: {type: 'string'},
    author: {foreignKey: 'user'},
});

foreignKeys

foreignKeys should be a Model Factory or a string of the factory name. Using foreignKeys will automatically set the type to array. Unlike foreignKey and foreignCollection, the key values are not stored in the object itself. This way you may add millions of foreign relationships with addForeign to any given key. This also means that you cannot edit the field to edit the relationships.

By default, this field will be populated by up to 10 foreign model instances. You can change that number with by setting an autoLoad key in the definition to another number.

To manipulate and load foreign relationships, use the following methods:

Example:

new dulcimer.Model({
    post: {type: 'string'},
    contributors: {foreignKeys: 'user', autoLoad: 5},
});

foreignCollection

foreignCollection's are like foreignKey's except they are of an array type. Values are saved as an array of key strings, and expanded out by when the model is retrieved with get up to the default depth of 5 or overridden with {depth: 24} on the get command.

When assigning values to these fields, you may either assign an array of model instances or an array of key strings.

Example:

new dulcimer.Model({
    comment: {type: 'string'},
    author: {foreignKey: 'user'},
    starredBy: {foreignCollection: 'user'}
});

required

required is a boolean, false by default. A required field will attempt to bring in the default value if a value is not present.

Example:

new dulcimer.Model({
    comment: {
        type: 'string',
        required: true,
        default: "User has nothing to say."
    },
    author: {foreignKey: 'user'},
    starredBy: {foreignCollection: 'user'}
});

default

default may be a value or a function. Default is only brought into play when a field is required but not assigned. In function form, default behaves similarly to derive, except that it only executes once.

new dulcimer.Model({
    comment: {
        type: 'string',
        required: true,
        default: function () {
            return this.author.fullName + ' has nothing to say.';
        },
    },
    author: {foreignKey: 'user'},
    starredBy: {foreignCollection: 'user'}
});

❗ Warning! Assigning mutable objects as a default can result in the default getting changed over time. When assigning objects, arrays, or essentially any advanced type, set default to a function that returns a new instance of the object.


save

save is a boolean, true by default which determines whether a field should be omitted during save or not.

It can be handy to not save derived fields.

Example:

new dulcimer.Model({
    firstName: {type: 'string'},
    lastName: {type: 'string'},
    fullName: {
        type: 'string',
        derive: function () {
            return [this.firstName, this.lastName].join(" ");
        },
        save: false,
    }
});

private

private is a boolean, false by default, which determines whether a field is saved into the object upon save and included in the object resulting from toJSON().

You can force private methods to be included in saved objects with the model option savePrivate, while preserving toJSON omittion.

Model Options

Model options are the second argument of the VeryLevelModel constructor.

Requirements:

  • Models must have a name option.
  • Models must have a db.
  • Models may have a bucket. You may also define buckets elsewhere if dynamic.

Note: Multiple models can and should use the same bucket. Multiple models SHOULD NOT use the same name.

Buckets are useful for separating groups of data by access groups or other things.

Index:

Example:

new dulcimer.Model({
        someField: {},
        someOtherField: {},
    },
    {
        name: 'person',
        db: level(__dirname + '/thisapp.db', {valueEncoding: 'json'}),
    }
);

name

The name is required should be a short (one or two word) alphanumeric string with no spaces. This name is used as a prefix within the key store, as well as a string reference to the Model Factory itself to prevent circular requirements.


db

The db field should refer to a LevelUp or compatible library connection. The valueEncoding option must be 'json'.


bucket

This is the default bucket name for the model. Each method that interacts with the underlying database may override the bucket.


onSave

{onSave: function (err, details, done) { } }

The details object contains:

{
    model: model-instance,
    changes: changes,
    ctx: ctx
}

The changes property is an object of fields with 'then', 'now', and 'changed', values.

{
    field1: {then: 'cheese', now: 'ham', changed: true},
    field2: {then: 'who', now: 'whom', changed: true}
}

The ctx property is whatever you passed with the ctx option to the save method.

If you require a full model instance of what used to be, do this:

var oldmodel = details.model.getOldModel();

You must execute the done callback when done.


onDelete

{onSave: function (err, details, donecb) { } }

The details object contains:

{
    ctx: ctx
}

The ctx property is whatever you passed to with ctx option to the delete method.

You must execute the done callback.


savePrivate

A boolean, false by default, to enable saving of private fields.


saveKey

A boolean, false by default, to enabling saving the key field within the object.


foreignDepth

An integer, 5 by default, to use as the default option for depth in a get call. The represents the depth by which to expand foreign keys recursively during a get.


keyType

When generating keys, Dulcimer makes a lexically incrementing key, so that keys are in order of insertion by default. To change this to another value set keyType. Right now, the only other option is uuid.

{keyType: 'uuid'}

This will generate uuid-v4 based keys instead.


keyGenerator

If you want to override key generation with your own function, set it to keyGenerator.

The only argument is an error first callback that should pass a key.

{keyGenerator: function (cb) {
    cb(false, "generate a unique key of some kind here");
}}

Options and Callbacks

Most Model Factory and Model Instance methods require a callback. Any method that does require a callback has an optional options object as the argument before the callback.

Most methods will take db, and bucket as options properties, which override the defaults set in factory.options. Some methods will take pagination methods: offset and limit. save, update, and delete methods can take a ctx object in options to pass on to the factory.options.onSave and factory.options.onDelete callbacks.

Callbacks are always required on functions that include them, and lead with an error-first approach.

☝️ Remember, options are always optional, meaning you don't have to the argument at all.

Common Options

db

This option overrides the current database defined with options.db for the current call.


bucket

This overrides the current bucket.


offset

This skips offset number of entries in a read call.

❗ This is deprecated (and potentially very resource intensive). Use continuation tokens instead.


limit

This limits the number of results in a read call.


continuation

This is the token given in the "page.token" information from a all when limit is used. Use this to page through the next set of limited results with the same query.


sortBy

sortBy must be an indexed field. The results of a read call are sorted by the value of this field. Sorted results should be much faster than sorting after retrieval, as indexes are presorted in the db.


indexValue

Only get results from this indexed field with a specific value. You must also specify the field with index.


indexRange

Only get the results from this indexed field within a specific range (in order).

{indexRange: {start: 'start value', end: 'end value'}}

You must also specify the field with index.


index

Index field to use for indexRange and indexValue.


reverse

Boolean, when true, reverses the result order from a read call.


filter

filter is a function that is given a model instance, and returns false to filter out the result, or true to keep the result. Model instances have expanded their foreign values yet.

Example:

{filter: function (inst) {
        if (inst.lastName !== 'Fritz') {
            return false;
        }
        return true;
    }
}

depth

depth is an integer, 5 by default, that determines how many recursive layers to expand foreignKey and foreignCollection fields.

0 means means that it will not expand any keys.


ctx

Whatever you assign to ctx will be passed to the resulting onSave or onDelete callbacks.

Useful for passing the user and other context from an HTTP API call to the model callbacks, and many other similar use cases.


returnStream

A boolean, when true, causes a read function to return an object stream, and call the callback with the stream rather than the concatenated array of models.


Model Factory Methods

create(value_object)

Returns a factory instance model.

Create makes a new instance of the model with specific data. Any fields in the value_object that were not defined get thrown out. Validations are not done on creation, but some values may be processed based on the field definition type and processIn functions.

Create does not save the value; you'll have to run .save(function (err) { ... }) on the returned model instance.

The model instance's private .key field will not be set until it has been saved either.

Logging the model out to console will produce a confusing result. If you want the model's data, run .toJSON() and use the result.

Example:

//assuming Person is a defined Model Factory
var person = Person.create({
    firstName: 'Nathan',
    lastName: 'Fritz',
});
person.save(function (err) {
    console.log("Person saved as:", person.key);
});

get(key, options, callback)

Get a specific model instance by key.

Arguments:

  • key
  • options
  • callback: function (err, model)

Callback Arguments:

  1. err: An error indicating failure to get the model instance.
  2. model: A model instance of the Model Factory that called get (if there was no err).

Options:

Example:

Person.get(someperson_key, {depth: 0}, function (err, person) {
    if (!err) {
        console.dir(person.toJSON());
    }
});

all(options, callback)

Get many/all of the model instances saved of this model factory Results are in order of insertion unless ordered by an indexed field.

Arguments:

  • options
  • callback -- function (err, models, pagination)

Callback Arguments:

  1. err: If err is set, there has been an error getting result.
  2. models: An array of model instances unless the returnStream option is true, at which point it is an object stream of resulting model instances.
  3. pagination: An object containing specified limit, offset, continuation, an actual count and potential total if no offset/limit had been assigned.

Options:

☝️ Internally, all is called by other methods that retrieve multiple results, doing some of the options for you. For example, getByIndex calls all with index and indexValue.

Example:

Person.all({limit: 10}, function (err, persons) {
    persons.forEach(function (person) {
        console.log(person.toJSON());
    });
});

update(key, merge_object, options, callback)

Updates an existing stored model with new data. It only overrides fields that you send.

Arguments:

  • key
  • merge_object
  • options
  • callback -- function (err, newmodel)

Callback Arguments:

  1. err: Only set when there's been an error updating the model.
  2. newmodel: The model instance after it's been updated.

Options:

Example:

Person.update(somekey, {lastName: 'Fritz'}, {bucket: 'peopleILike'}, function (err, person) {
    console.log(person.toJSON()); //lastName will be Fritz, other values unchanged
});

delete(key, options, callback)

Deletes a stored model.

Arguments:

  • key
  • options
  • callback -- function (err) {}

Options:


wipe(options, callback)

Deletes all models and their children from the database.

Arguments:

  • options
  • callback -- function (err)

CallBack Arguments:

  1. err: Only set if an error occurred during wipe.

Options:

❗ No really, it deletes everything for that model!


getByIndex(field, value, options, callback)

Gets the models by an index.

Arguments:

  • field: indexed field
  • value: value to match
  • options
  • callback -- function (err, models, pagination)

Callback Arguments:

  1. err: Set only if there was an error.
  2. models: An array of model instances unless the returnStream option is true, at which point it is an object stream of resulting model instances.
  3. pagination: An object containing specified limit, offset, continuation, an actual count and potential total if no offset/limit had been assigned.

Options:

☝️ This ends up calling all with some index options, so you get the same pagination features.

Person.getByIndex('lastName', 'Fritz', function (err, persons) {
    console.log("All of the Fritzes.");
    persons.forEach(function (person) {
        console.log(person.key, person.fullName);
    });
});

findByIndex(field, value, options, callback)

Just like getByIndex, except only return one value, rather than an array of models, or an error.

Arguments:

  • field: indexed field
  • value: value to match
  • options
  • callback -- function (err, model)

Callback Arguments:

  1. err: Set only if there was an error.
  2. model: Model instance if an index of the specified value was found. Otherwise undefined.

Options:

Person.findByIndex('phoneNumber', '509-555-5555', function (err, person) {
    if (!err && person) {
        console.log("Found person", person.toJSON(), '@ key', person.key);
    } else {
        console.log("Unable to find person with that phone number.");
    }
});

allSortByIndex(field, options, callback)

Just like all with the sortBy option. Sorted results should be much faster than sorting after retrieval, as indexes are presorted in the db.

Arguments:

  • field: indexed field to sort by
  • options
  • callback -- function (err, model)

Callback Arguments:

  1. err: Set only if there was an error.
  2. model: Model instance if an index of the specified value was found. Otherwise undefined.

Options:

Person.allSortBy('phoneNumber', {reverse: true}, function (err, persons) {
    //persons sorted by phone # in reverse
});

getTotal(options, callback)

Get the total count of all keys.

Arguments:

  • options
  • callback: function (err, count)

Callback Arguments:

  1. err: Set only if there was an error.
  2. count: A total count of all keys.

Options:

Example:

Person.getTotal(function (err, count) {
    if (!err) {
        console.log(count);
    }
});

runWithLock(callback)

Node.js is not threaded, but it is asynchronous. This can make database access in keystores hazardous. The issue requires you to understand some subtleties about the event stack. Anytime you're updating a value based on get(s), you should lock around these operations to prevent the operation from changing under you.

An incrementer is a good example. You have to read the previous value, and update based on that. But what if, when the process goes back to the event loop when you call save, an function runs that changes the value? Now that value is lost.

Duclimer keeps an internal lock for writes that runWithLock that runWithLock acquires for you to solve problems like this. runWithLock queues other locking calls until you unlock()

❗ Within a locked function, anytime you call save or delete use the option withoutLock set to true. You need to do this because you already have a write lock. This is the ONLY time you should do so.

Arguments:

  • callback: the function you want to run without any async steps undone before the next time

Callback Arguments:

  • unlock: function to call when all of your async operations are done to release the lock

Example:

function Increment(key, amount, cb) {
    SomeModelFactory.runWithLock(function (unlock) {
        SomeModelFactory.get(key, function (err, model) {
            model.count += amount;
            //note the withoutLock
            model.save({withoutLock: true}, function (err) {
                unlock(); //if you don't do this, this function will only be able to run once.. ever!
                cb(err, model.count);
            });
        });
    });
}

❗ Make sure that the end of all of your code flows end in an unlock() if you're using if statements!

__exportJSON(writeable)__

Sometimes you may need to export your data to a JSON fixture.

Arguments:

  • writeable: (optional: default is stdout) the writeable Stream to write serialized data to such as a fs.createWriteStream instance

Example:

var fs = require('fs');
var outFileStream = fs.createWriteStream(__dirname + '/SomeModel.export.json');
var SomeModelFactory = require('./models/someModelFactory');

SomeModelFactory.exportJSON(outFileStream);
// writes JSON-serialized data to SomeModel.export.json file
[
    {/* first model data */},
    
    {/* last model data */}
]
__importData(arrayOrStream, callback)__

Sometimes you need to import data from a JSON fixture.

Arguments:

  • arrayOrStream: an array of objects or a readable stream of objects where the objects are model data.
  • callback: (optional) called when import is finished

Example:

var fixtureData = require(__dirname + '/SomeModel.export.json');
var SomeModelFactory = require('./models/someModelFactory');

SomeModelFactory.importData(fixtureData, function ()  {
    // Stuff to do after import is complete
});

save(options, callback)

Saves the current model instance to a serialized form in the db.

Fields may be omitted based on the model options saveKey & savePrivate, and field definition parameters of private & save.

Any foreignKey and foreignCollection fields will be collapsed back down to just their key fields.

Any processOut functions will be ran to process the fields into their serialized form.

If the model doesn't already have a key field assigned, a new key will be generated.

Arguments:

  • options
  • callback: function (err)

Callback Arguments:

  1. err: Only set if an error occurred.

Options:

  • db
  • bucket
  • ctx
  • withoutLock: only set to true if you're running save within a runWithLock block.

❗ Foreign objects are not saved.

Example:

var person = Person.create({
    firstName: 'Nathan',
    lastName: 'Fritz',
});

person.save(function (err) {
    console.log("Person:", person.fullName, "saved as", person.key);
    //fullName is a derived field
    //person.key got generated during save
    //didn't pass options because they're optional, remember?
});

delete(options, callback)

Deletes the instance from the database.

Arguments:

  • options
  • callback: function (err)

Callback Arguments:

  1. err: Only set when an error has occurred while deleting.

Options:

  • db
  • bucket
  • ctx
  • withoutLock: only set to true if you're running save within a runWithLock block.

Example:

person.delete({ctx:{userid: someuser}}, function (err) {
    //the model option onDelete was called with the ctx object
});

addForeign(field, other, options, callback)

Add a relationship to this model instance of another model instance or key.

Arguments:

  • field: field in this model's definition with foreignKeys
  • other: key or model instance of the model factory referred to as field's foreignKeys
  • options
  • callback

Callback Arguments:

  • err

Example:

var BlogPostFactory = new dulcimer.Model({
    title: {type: 'string'},
    body: {type: 'string'},
    contributors: {foreignKeys: 'user'}
}, {name: 'blogpost'})

var User = new dulcimer.Model({
    //...
});

//...

var user = User.create({
    //...
});


var post = Post.create({
    title: "Some Blog Title",
    body: "It could happen to you..."
});


user.save(function (err) {
    post.save(function (err) {
        post.addForeign('contributors', user, function (err) {
            //...
        });
    });
});

removeForeign(field, other, options, callback)

Remove a relationship to this model instance of another model instance or key.

Arguments:

  • field: field in this model's definition with foreignKeys
  • other: key or model instance of the model factory referred to as field's foreignKeys
  • options
  • callback

Callback Arguments:

  • err

Example:

BlogPostFactory.get('somekey', function (err, post) {
    post.removeForeign('contributors', 'someuserkeyOrInstance', function (err) {
        //...
    });
});

getForeign(field, options, callback)

Retrieves the model instances from foreign relationships of the field.

Arguments:

  • field: field with foreignKeys
  • options
  • callback function (err, models, callback)

Callback Arguments:

  1. err: If err is set, there has been an error getting result.
  2. models: An array of model instances unless the returnStream option is true, at which point it is an object stream of resulting model instances.
  3. pagination: An object containing specified limit, offset, continuation, an actual count and potential total if no offset/limit had been assigned.

Options:

Example:

blogPost.getForeign('contributors', {limit: 10}, function (err, users, page) {
    if (users.length > 0) {
        console.log(users[0].toJSON());
    }
});

hasForeign(field, foreignKey, options, callback)

Retrieves the model instances from foreign relationships of the field.

Arguments:

  • field: field with foreignKeys
  • foreignKey: foreign key or model instance
  • options
  • callback function (err, has)

Callback Arguments:

  1. err: If err is set, there has been an error getting result.
  2. has: boolean of whether the foreign key is linked in that field

Options:


getReverseForeign(modelfactory, field, options, callback)

Retrieves the model instances of another modelfactory with a foreignKeys field to this's modelfactory. Just like getForeign but from the reverse side.

Arguments:

  • modelfactory: model from which the foreignKeys field exists
  • field: field in the modelfactory with foreignKeys back to this model instance
  • options
  • callback function (err, models, callback)

Callback Arguments:

  1. err: If err is set, there has been an error getting result.
  2. models: An array of model instances unless the returnStream option is true, at which point it is an object stream of resulting model instances.
  3. pagination: An object containing specified limit, offset, continuation, an actual count and potential total if no offset/limit had been assigned.

Options:

Example:

user.getReverseForeign(BlogPostFactory, 'contributors', {limit: 10}, function (err, posts, page) 
    posts.forEach(function (post) {
        console.log(post.title);
    });
});

createChild(ModelFactory, value)

Children are model instances that you can attach to a model instance. They're great for revision logs, comments, etc.

  • ModelFactory: This can be any Dulcimer Model factory, including the same one as the parent.
  • value: initial value, used just like create

Example:

var comment = person.createChild(Comment, {
    body: "I think that guy is pretty great.",
    author: otherperson,
});
comment.save(function (err) {
    console.log("Comment", comment.key, "added to", person.key);
});

☝️ Comment.all will not include the children comments. Only this specific person instance can access these comments with getChildren, getChildrenByIndex, and findChildByIndex.

☝️ Deleting the parent object will delete the children.


getChild(ModelFactory, key, opts, callback)

Just like the get command, but called from a parent model instance for loading a child.

Arguments:

  • key
  • options
  • callback

Callback Arguments:

  1. err
  2. model_instance

Options:


getChildren(ModelFactory, options, callback)

Get the children of this model instance of a specific model factory.

Arguments:

  • ModelFactory: This can be any Dulcimer Model factory, including the same one as the parent.
  • options
  • callback function (err, models, callback)

Callback Arguments:

  1. err: If err is set, there has been an error getting result.
  2. models: An array of model instances unless the returnStream option is true, at which point it is an object stream of resulting model instances.
  3. pagination: An object containing specified limit, offset, continuation, an actual count and potential total if no offset/limit had been assigned.

Options:

☝️ Internally, this method calls all with special internal use only options to work with specifically the children on this model instance.

Example:

person.getChildren(Comment, function (err, comments, pagination) {
    console.log("All of the comments for", person.fullName);
    console.log("-===============================-", pagination.total);
    comments.forEach(function (err, comment) {
        console.log(comment.body);
        console.log("-", comment.author.fullName);
    });
});

getChildrenByIndex(ModelFactory, field, value, options, callback)

Retrieves the children of a specific instance, of a specific model, with a specific indexed field value.

Arguments:

  • ModelFactory: This can be any Dulcimer.Model factory, including the same one as the parent.
  • field: index field
  • value: field value to match
  • options
  • callback function (err, models, callback)

Callback Arguments:

  1. err: If err is set, there has been an error getting result.
  2. models: An array of model instances unless the returnStream option is true, at which point it is an object stream of resulting model instances.
  3. pagination: An object containing specified limit, offset, continuation, an actual count and potential total if no offset/limit had been assigned.

Options:

Example:

person.getChildrenByIndex(Comment, 'date', '2014-02-10', function (err, comments, pagination) {
    console.log("All of the comments for", person.fullName, "today.");
    console.log("-===============================-", pagination.total);
    comments.forEach(function (err, comment) {
        console.log(comment.body);
        console.log("-", comment.author.fullName);
    });
});

findChildByIndex(ModelFactory, field, value, options, callback)

Similar to getChildrenByIndex except that it only returns one result.

Arguments:

  • ModelFactory: This can be any Dulcimer Model factory, including the same one as the parent.
  • field: index field
  • value: field value to match
  • options
  • callback function (err, model, callback)

Callback Arguments:

  1. err: If err is set, there has been an error getting result.
  2. model: A model instance.

Options:

Example:

person.findChildByIndex(Version, 'created', person.created, function (err, version) {
    console.log("The version log entry for the initial creation of", person.fullName);
    console.log(version.toJSON());
    //ok, this one is a bit contrived
});

hasKey(fieldName, key)

Check whether an instance field has a foreign key referenced.

Arguments:

  • fieldName
  • key

Returns: true/false


hasInstance(fieldName, otherModelInstance)

Similar to hasKey, it checks to see whether an instance is referenced in a foreign or foreignCollection field.

Arguments:

  • fieldName
  • otherModelInstance

Returns: true/false


toJSON(flags)

Outputs a JSON style object from the model.

Boolean Flags:

  • noDepth: false by default. If true, does not recursively toJSON objects like foreignKeys and foreignCollections.
  • withPrivate: false by default. If true, includes fields with private set to true.

Example:

You want an example? Look at all of the other examples... most of them use toJSON.

☝️ toJSON does not produce a string, but an object. See: toString.


toString()

Just like toJSON, but produces a JSON string rather than an object.


diff(other)

Arguments:

  • other: model instance to compare this one to.

Result: object of each field with left, and right values.

{
    firstName: {left: 'Nathan', right: 'Sam'},
    lastName: {left: 'Fritz', right: 'Fritz'},
}

getChanges()

Get the changes since get or create.

Result: object of each field with then, now, and changed boolean.

{
    body: {then: "I dont liek cheese.", now: "I don't like cheese.", changed: true},
    updated: {then: '2014-02-10 11:11:11', now: '2014-02-10 12:12:12', changed: true},
    created: {then: '2014-02-10 11:11:11', now: '2014-02-10 11:11:11', changed: false},
}

getOldModel()

Get a new model instance of this instance with all of the changes since get or create reversed.

Result: Model instance.


loadData()

Loads data just like when a model instance is retrieved or created.

processIn is called on any fields specified, but onSet is not.

Essentially the same things happen as when running create but can be done after the model instance is initialized.

Example:

var person = Person.create({
    firstName: 'Nathan',
    lastName: 'Fritz',
});

person.favoriteColor = 'blue';

person.loadData({
    favoriteColor: 'green',
    favoriteFood: 'burrito',
});

console.log(person.toJSON());
// {firstName: 'Nathan', lastName: 'Fritz', favoriteFood: 'burrito', favoriteColor: 'green'}
et!  All acronyms are already a mystery to me!``