Awesome
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.
:ledger: 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
- Connecting to the Database
- Referring to a Model By String
- Defining a Model
- Model Options
- Options and Callbacks
- db
- bucket
- <del>offset</del>
- limit
- continuation
- sortBy
- indexValue
- indexRange
- index
- reverse
- filter
- depth
- ctx
- returnStream
- Model Factory Methods
- Model Instance Methods
- save
- delete
- addForeign
- removeForeign
- getForeign
- hasForeign
- getReverseForeign
- <del>createChild</del>
- <del>getChild</del>
- <del>getChildren</del>
- <del>getChildrenByIndex</del>
- <del>findChildByIndex</del>
- hasKey
- hasInstance
- toJSON
- toString
- diff
- getChanges
- getOldModel
- loadData
Installing
npm install dulcimer
<a name="connecting"></a>
Connecting to a Database
<a name="conn-level"></a>
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'
});
<a name="conn-riak"></a>
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
});
<a name="conn-from-config"></a>
Connecting Based on Config
var duclimer = require('dulcimer');
var config = require('./config.json');
dulcimer.connect(config.database);
<a name="conn-per-model"></a>
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);
});
<a name="conn-per-comm"></a>
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.
<a name="getModel"></a>
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
- validate
- processIn
- processOut
- onSet
- derive
- index
- foreignKey
- foreignKeys
- foreignCollection
- required
- default
- save
- private
<a name='def-type'></a> 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}}
<a name='def-validate'></a> 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);
}
});
<a name='def-processIn'></a> 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);
},
})
<a name='def-processOut'></a> 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
},
})
<a name='def-onSet'></a> 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();
},
})
<a name='def-index'></a> 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.
<a name='def-derive'></a> 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(" ");
},
}
});
:heavy_exclamation_mark: 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.
<a name='def-foreignKey'></a> 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 foreignKey
s 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'},
});
<a name='def-foreignKeys'></a> 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},
});
<a name='def-foreignCollection'></a> 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'}
});
<a name='def-required'></a> 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'}
});
<a name='def-default'></a> 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'}
});
:heavy_exclamation_mark: 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.
<a name='def-save'></a> 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,
}
});
<a name='def-private'></a> 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.
<a name="model-options"></a>
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'}),
}
);
<a name='mo-name'></a> 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.
<a name='mo-db'></a> db
The db field should refer to a LevelUp or compatible library connection.
The valueEncoding
option must be 'json'.
<a name='mo-bucket'></a> bucket
This is the default bucket name for the model. Each method that interacts with the underlying database may override the bucket.
<a name='mo-onSave'></a> 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.
<a name='mo-onDelete'></a> 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.
<a name='mo-savePrivate'></a> savePrivate
A boolean, false by default, to enable saving of private fields.
<a name='mo-saveKey'></a> saveKey
A boolean, false by default, to enabling saving the key field within the object.
<a name='mo-foreignDepth'></a> 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.
<a name='mo-keyType'></a> 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.
<a name='mo-keyGenerator'></a> 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.
:point_up: Remember, options are always optional, meaning you don't have to the argument at all.
Common Options
- db
- bucket
- <del>offset</del>
- continuation
- limit
- sortBy
- indexValue
- indexRange
- index
- reverse
- filter
- depth
- ctx
- returnStream
<a name='op-db'></a> db
This option overrides the current database defined with options.db for the current call.
<a name='op-bucket'></a> bucket
This overrides the current bucket.
<a name='op-offset'></a> offset
This skips offset
number of entries in a read call.
:heavy_exclamation_mark: This is deprecated (and potentially very resource intensive). Use continuation tokens instead.
<a name='op-limit'></a> limit
This limits the number of results in a read call.
<a name='op-continuation'></a> 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.
<a name='op-sortBy'></a> 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.
<a name='op-indexValue'></a> indexValue
Only get results from this indexed field with a specific value. You must also specify the field with index.
<a name='op-indexRange'></a> 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.
<a name='op-index'></a> index
Index field to use for indexRange and indexValue.
<a name='op-reverse'></a> reverse
Boolean, when true, reverses the result order from a read call.
<a name='op-filter'></a> 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;
}
}
<a name='op-depth'></a> 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.
<a name='op-ctx'></a> 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.
<a name='op-returnStream'></a> 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.
<a name='model-factory-methods'></a>
Model Factory Methods
<a name="create"></a> 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);
});
<a name="get"></a> get(key, options, callback)
Get a specific model instance by key.
Arguments:
- key
- options
- callback:
function (err, model)
Callback Arguments:
- err: An error indicating failure to get the model instance.
- 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());
}
});
<a name="all"></a> 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:
- err: If err is set, there has been an error getting result.
- models: An array of model instances unless the returnStream option is true, at which point it is an object stream of resulting model instances.
- pagination: An object containing specified limit, offset, continuation, an actual
count
and potentialtotal
if no offset/limit had been assigned.
Options:
- db
- bucket
- <del>offset</del>
- continuation
- limit
- sortBy
- index
- indexValue
- indexRange
- reverse
- filter
- depth
- returnStream
:point_up: 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());
});
});
<a name="update"></a> 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:
- err: Only set when there's been an error updating the model.
- 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
});
<a name="factory-delete"></a> delete(key, options, callback)
Deletes a stored model.
Arguments:
- key
- options
- callback --
function (err) {}
Options:
<a name="wipe"></a> wipe(options, callback)
Deletes all models and their children from the database.
Arguments:
- options
- callback --
function (err)
CallBack Arguments:
- err: Only set if an error occurred during wipe.
Options:
:heavy_exclamation_mark: No really, it deletes everything for that model!
<a name="getByIndex"></a> 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:
- err: Set only if there was an error.
- models: An array of model instances unless the returnStream option is true, at which point it is an object stream of resulting model instances.
- pagination: An object containing specified limit, offset, continuation, an actual
count
and potentialtotal
if no offset/limit had been assigned.
Options:
- db
- bucket
- <del>offset</del>
- continuation
- limit
- reverse
- filter
- depth
- returnStream
:point_up: 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);
});
});
<a name="findByIndex"></a> 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:
- err: Set only if there was an error.
- 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.");
}
});
<a name="allSortByIndex"></a> 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:
- err: Set only if there was an error.
- model: Model instance if an index of the specified value was found. Otherwise
undefined
.
Options:
- db
- bucket
- <del>offset</del>
- continuation
- limit
- reverse
- filter
- depth
- returnStream
Person.allSortBy('phoneNumber', {reverse: true}, function (err, persons) {
//persons sorted by phone # in reverse
});
<a name="getTotal"></a> getTotal(options, callback)
Get the total count of all keys.
Arguments:
- options
- callback:
function (err, count)
Callback Arguments:
- err: Set only if there was an error.
- count: A total count of all keys.
Options:
Example:
Person.getTotal(function (err, count) {
if (!err) {
console.log(count);
}
});
<a name="runWithLock"></a> 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()
:heavy_exclamation_mark: 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);
});
});
});
}
:heavy_exclamation_mark: Make sure that the end of all of your code flows end in an unlock()
if you're using if statements!
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 */}
]
<a name="importData">
__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
});
Model Instance Methods
- save
- delete
- addForeign
- removeForeign
- getForeign
- hasForeign
- getReverseForeign
- <del>createChild</del>
- <del>getChild</del>
- <del>getChildren</del>
- <del>getChildrenByIndex</del>
- <del>findChildByIndex</del>
- hasKey
- hasInstance
- toJSON
- toString
- diff
- getChanges
- getOldModel
- loadData
<a name="save"></a> 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:
- err: Only set if an error occurred.
Options:
:heavy_exclamation_mark: 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?
});
<a name="instance-delete"></a> delete(options, callback)
Deletes the instance from the database.
Arguments:
- options
- callback:
function (err)
Callback Arguments:
- err: Only set when an error has occurred while deleting.
Options:
Example:
person.delete({ctx:{userid: someuser}}, function (err) {
//the model option onDelete was called with the ctx object
});
<a name="addForeign"></a> 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) {
//...
});
});
});
<a name="removeForeign"></a> 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) {
//...
});
});
<a name="getForeign"></a> 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:
- err: If err is set, there has been an error getting result.
- models: An array of model instances unless the returnStream option is true, at which point it is an object stream of resulting model instances.
- pagination: An object containing specified limit, offset, continuation, an actual
count
and potentialtotal
if no offset/limit had been assigned.
Options:
- db
- bucket
- <del>offset</del>
- continuation
- limit
- reverse
- filter
- depth
- returnStream
Example:
blogPost.getForeign('contributors', {limit: 10}, function (err, users, page) {
if (users.length > 0) {
console.log(users[0].toJSON());
}
});
<a name="hasForeign"></a> 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:
- err: If err is set, there has been an error getting result.
- has: boolean of whether the foreign key is linked in that field
Options:
- db
- bucket
- <del>offset</del>
- continuation
- limit
- reverse
- filter
- depth
- returnStream
<a name="getReverseForeign"></a> 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:
- err: If err is set, there has been an error getting result.
- models: An array of model instances unless the returnStream option is true, at which point it is an object stream of resulting model instances.
- pagination: An object containing specified limit, offset, continuation, an actual
count
and potentialtotal
if no offset/limit had been assigned.
Options:
- db
- bucket
- <del>offset</del>
- continuation
- limit
- reverse
- filter
- depth
- returnStream
Example:
user.getReverseForeign(BlogPostFactory, 'contributors', {limit: 10}, function (err, posts, page)
posts.forEach(function (post) {
console.log(post.title);
});
});
<a name="createChild"></a> 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);
});
:point_up: Comment.all will not include the children comments. Only this specific person instance can access these comments with getChildren, getChildrenByIndex, and findChildByIndex.
:point_up: Deleting the parent object will delete the children.
<a name="getChild"></a> 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:
- err
- model_instance
Options:
<a name="getChildren"></a> 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:
- err: If err is set, there has been an error getting result.
- models: An array of model instances unless the returnStream option is true, at which point it is an object stream of resulting model instances.
- pagination: An object containing specified limit, offset, continuation, an actual
count
and potentialtotal
if no offset/limit had been assigned.
Options:
:point_up: 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);
});
});
<a name="getChildrenByIndex"></a> 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:
- err: If err is set, there has been an error getting result.
- models: An array of model instances unless the returnStream option is true, at which point it is an object stream of resulting model instances.
- pagination: An object containing specified limit, offset, continuation, an actual
count
and potentialtotal
if no offset/limit had been assigned.
Options:
- db
- bucket
- <del>offset</del>
- continuation
- limit
- reverse
- filter
- depth
- returnStream
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);
});
});
<a name="findChildByIndex"></a> 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:
- err: If err is set, there has been an error getting result.
- 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
});
<a name="hasKey"></a> hasKey(fieldName, key)
Check whether an instance field has a foreign key referenced.
Arguments:
- fieldName
- key
Returns: true/false
hasInstance(fieldName, otherModelInstance)
<a name="hasInstance"></a>
Similar to hasKey, it checks to see whether an instance is referenced in a foreign or foreignCollection field.
Arguments:
- fieldName
- otherModelInstance
Returns: true/false
<a name="toJSON"></a> 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.
:point_up: toJSON does not produce a string, but an object. See: toString.
<a name="toString"></a> toString()
Just like toJSON, but produces a JSON string rather than an object.
<a name="diff"></a> 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'},
}
<a name="getChanges"></a> 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},
}
<a name="getOldModel"></a> getOldModel()
Get a new model instance of this instance with all of the changes since get or create reversed.
Result: Model instance.
<a name="loadData"></a> 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!``