Skip to content

fritzy/posthole

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PostHole.js

a Postgres Object Store for Node.js / IO.js

Postgres recently gave a very thoughtful gift to the JavaScript community by adding support for JSON types. PostHole allows you to assign models to your JSON fields for easy management, must like a keystore, but with the full power of Postgres behind it. Write your queries with standard SQL, and get your JSON fields back as instantiated models.

PostHole uses VeryModel does to make working with models easy and powerful.
PostHole uses node-postgres aka module pg for the connection and queries.

Example

var PostHole = require('posthole');

var PersonFactory = new PostHole.Model({
    firstName: {},
    lastName: {},
    fullName: {derive: function () {
        return this.firstName + ' ' + this.lastName;
    }},
}, {
    table: 'person',
    field: 'personjson'
});

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

function startDemo() {
    author.save(function (err, id) {
        if (err) {
            console.log(err);
            return;
        }
        //query can be any query, with the fields from many models, renamed with AS or not.
        //query function is the same as postgres module 'pg's query
        PostHole.query("SELECT * FROM person WHERE id = $1", [id], function(err, result) {
            if (err || result.rows.length !== 1) {
                console.log(err);
                return;
            }
            //every returned field mapped to a model will be a VeryModel instance
            //results are otherwise the same as the node postgres module 'pg'
            console.log(result.rows[0].personjson.toJSON());
            /* {
            *       firstName: "Nathan",
            *       lastName: "Fritz",
            *       fullName: "Nathan Fritz"
            * }
            */
            console.log("Saved to id: %s", result.rows[0].personjson.id);
        });
    });
}

PostHole.connect('postgres://fritzy@localhost/fritzy', function (err) {
    if (err) {
        throw err;
    }
    //grab the meta data for the fields of the table
    //figures out which field is the primary key
    //sets up table if id doesn't exist
    PersonFactory.setup(startDemo);
});

Index

connect

Sets the default connection and returns a standard node-postgres pg instance.

Arguments:

  • Postgres connection URI

disconnect

Disconnects the default connection.

query

Identical to node-postgres's query, except that resulting fields that match a registered Model Factory will be cast as VeryModel instances even if the names are aliased.

Arguments:

  • SQL Query String
  • optional array of arguments
  • optional callback

Model

Constructor for making and registering VeryModel Model Factories that can be associated with JSON/JSONB Postgres Table Fields.

Arguments:

  • Definition Object
  • Model Options

Example:

var posthole = require('posthole');

var Person = new posthole.Model({
    //some fields here
    name: {type: 'string'},
    phone: {type: 'string'}
},
{
    //options here
    table: 'some_table',
    field: 'some_field'
});

<a name='defining-a-model-factory'></a>
## Defining a Model

A model defines the types of fields that you might use in an object.

#### 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](#def-type)
* [validate](#def-validate)
* [processIn](#def-processIn)
* [processOut](#def-processOut)
* [onSet](#def-onSet)
* [derive](#def-derive)
* [required](#def-required)
* [default](#def-default)
* [private](#def-private)
* [model](#def-model)
* [collection](#def-collection)

<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:

```js
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();
    },
})

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.


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."
    },
});

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.';
        },
    },
});

❗ 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.


model

Models within models. What will we think of next? model can either be a definition object or a Model Factory instance.


collection

Just like sub models, collection is a definition object or Model Factory instance, however, the data stored in this field will be an array of structures defined by the definition.


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

  • table: required for associating the model with a table
  • field: required for associating the model with a field
  • primaryKey: optional as the primary key should be detected automatically

Model Instance Methods

save(callback)

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

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

Arguments:

  • callback: function (err)

Callback Arguments:

  1. err: Only set if an error occurred.
  2. id: if this model is new

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
    //didn't pass options because they're optional, remember?
});

delete(callback)

Deletes the instance from the database. :warning: This removes the entire row!

Arguments:

  • callback: function (err)

Callback Arguments:

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

Example:

person.delete(function (err) {
});

toJSON(flags)

Outputs a JSON style object from the model.

Boolean Flags:

  • 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'}
``

About

Postgres Object Store ORM for Node.js

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published