Multicolour documentation collections

Collections

Multicolour collections are built on the popular Waterline ORM, this allowed us to minimise the amount of work that you do to get something you’d spend weeks or months doing.

Multicolour expands on Blueprints slightly to give you some extra behavioural and timesaving tweaks as well as extra behaviour for Multicolour.

A simple, example Multicolour blueprint might look something like this.

"use strict"

module.exports = {
  attributes: {
    slug: "string",

    title: {
      type: "string",
      required: true,
      index: true
    },

    description: {
      type: "string",
      required: true
    },

    public: {
      type: "boolean",
      defaultsTo: true,
      metadata: {
        description: "Some projects are private, this flag is the decider."
      }
    },

    // Associations.
    user: {
      model: "user"
    }
  },

  constraints: {
    get: {
      user: "auth.credentials.user.id",
      public: { compile: false, value: true }
    },
  }
}

A few things are happening above, we’re exporting an Object with a few keys. The first key is attributes, this is where you define the properties/columns/keys in your table/document.

Each key of the attributes can be an object of properties or simply the type that key should be. Valid types are:

  • "string"
  • "text"
  • "email"
  • "integer"
  • "float"
  • "date"
  • "time"
  • "datetime"
  • "boolean"
  • "binary"
  • "array"
  • "json"

Properties can also have a metadata key, this is where you can add descriptive and informative notes that will appear in the documentation generated by your chosen server framework.

There is also a constraints key outside of the attributes, this serves as a simple “logic free” way to constrain actions on the database.

If you were to write the JavaScript for that constraint, it might look something like this.

projects.findOne(request.payload, (err, project) => {
  if (row.user.toString() !== request.auth.credentials.user.id.toString()
    || row.public === false) {
    reply("You aren't allowed to do this.").code(412)
  }
  else {
    reply(project)
  }
})
...

A value computed by a constraint will always overwrite any value sent to the server intended for a write request to the database.

Naming and identity

To be as predictable as possible, Multicolour takes the name of your blueprint.js and makes sure it is not pluralised in any way. If you have a blueprint named projects.js in your blueprints folder, Multicolour will name your collection project and your routes will all be /project/.

Lifecycle callbacks

If constraints aren’t for you, you can use lifecycle callbacks to prevent/check writes and reads from your database.

Each model has the following available callbacks.

  • beforeValidate(values, next)
  • afterValidate(values, next)
  • beforeCreate(values, next)
  • afterCreatefn(newlyInsertedRecord, next)
  • beforeUpdate(valuesToUpdate, next)
  • afterUpdate(updatedRecord, next)
  • beforeDestroy(criteria, next)
  • afterDestroy(deletedRecord, next)

To read more on lifecycle callbacks, read the Waterline lifecycle documentation.

Associations

Your blueprints can have associations to other blueprints (even across database technologies) which are automatically populated by Multicolour. Your associations can be one-to-one, one-to-many or many-to-many.

You make associations in your blueprint by specifying the property that should be associated and either a model or collection property within with an optional via value.

Example:

...
attributes: {
  // One-to-one
  user: {
    model: "user"
  },

  // One-to-many
  projects: {
    collection: "project",
    via: "user"
  }
}
...

Adding associations programatically.

There may be a time you need to add a relationship to another collection without modifying the blueprint. The add_relation_to_collection(as, target_collection, related_collection, many) function will do this for you.

This function will add a relationship, either one to one or one to many to your collection. It will not overwrite any relationship by the same name It will throw if either collection does not exist. Passing true to many will make the relationship one to many.

Example

Adding a team relationship to the multicolour_user collection.

// app.js
my_service.get("database")
  .add_relation_to_collection("team", "multicolour_user", "team")
  .add_relation_to_collection("users", "team", "multicolour_user")

A sugar function that does the same in a more terse way is the .join method.

my_service.get("database")
  .join({
    as: "team",
    from: "multicolour_user",
    to: "team"
  })
  .join({
    as: "team",
    from: "team",
    to: "multicolour_user"
  })

You can find more about how Waterline handles associations by reading the documentation

toJSON

Every model has a toJSON method, even if you don’t supply one, Multicolour adds one for you which simply removes any null or undefined keys before conitnueing with the reply.

If you supply a toJSON method, Multicolour wraps your toJSON in the default so behaviour stays predictable.

Example removing a password and salt from a reply:

attributes: {
  // Your model...

  toJSON: function() {
    const model = this.toObject()
    delete model.password
    delete model.salt
    return model
  }
}

Flags

There are flags available to change Multicolour behaviour on your models, they are:

  • NO_AUTO_GEN_ROUTES: Boolean This tells Multicolour whether or not to generate CRUD endpoints for this model. custom_routes are still honoured.
  • NO_AUTO_GEN_FRONTEND: Boolean This tells Multicolour whether or not to generate a frontend (if you have enabled a frontend plugin).

During the generation Multicolour also adds a new property to each model, multicolour_instance which is the current instance of Multicolour from which you can access your entire app.