SierraSoftworks / Iridium

A high performance MongoDB ORM for Node.js
http://sierrasoftworks.github.io/Iridium/
Other
570 stars 25 forks source link

Documentation Improvement #113

Open CatGuardian opened 6 years ago

CatGuardian commented 6 years ago

I was looking at the Plugins sub-framework and was hoping to find documentation on when each of the plugin methods are executed.

http://sierrasoftworks.github.io/Iridium/interfaces/plugin.html

I see that there is newInstance and newModel methods. But there is no documentation describing these methods. Specifically I would like to know when newInstance is called and when newModel is called.

Is newInstance called before the instance is saved to the database or is it called after a successful save to the database? Is it called before transforms? Or is it called after transforms? Is it called before validators? Or is it called after validators?

Is newModel called before the collection is created on the database? Or is it called after the collection is created on the database?

I think the best documentation would outline exactly when these methods are called in relation to all of the validations, transforms, or any other hooks and triggers.

For now answering it here would be helpful.

Thank you so much for helping. It is greatly appreciated.

Also, I think it would be good to also add an updateInstance method or something to plug in to all of the updates.

CatGuardian commented 6 years ago

Also, how is the validate property used? That one I'm not sure on. I'm guessing this defines global validators that get executed whenever any other validator would be executed? And what does it operate on (properties? Whole documents?)

notheotherben commented 6 years ago

Hey @CatGuardian, you're absolutely right - it would be super handy if they described when they got called and made it clear what the expected usage was (it always seems really straightforward when you're the one that wrote it).

The newModel method is called whenever you run new Model(core, ...) on a core with the plugin registered. The idea being that you can make changes to any model prior to it being "ready for use". That might mean that the plugin adds new validators, it might mean that it changes the schema and so on.

The newInstance method, similarly, is called whenever Iridium instantiates a new Instance for one of your models. This usually happens as the result of a query, but could also be the result of calling new model.Instance(doc). The idea here is that the plugin can make changes to a specific instance at creation time (although a lot of the stuff you'd assume is a good fit here is probably a better fit for lifecycle hooks on the instance, which could be populated in the newModel plugin method). Primarily this is useful if you want to do clever context logging etc.

The validate property, as you guessed, allows you to add new global validators. Those validators get executed for properties whose schema passes the validator.handles(schema) predicate (which you define), so usually you'll end up with a validator that handles something like "over18" and you'd define your schema as { dateOfBirth: "over18" } or use @Property("over18") to use that validator.

It's important to note that neither newModel or newInstance are intended to replace the use of lifecycle hooks, which are triggered during various stages of your application's interaction with Iridium - instead they are meant to give plugins the ability to configure, at the relevant times, the Models and Instances that Iridium passes around, adding functionality or setting options as needed.

I hope that clears things up a bit, if anything is still unclear for you I'll be happy to try and answer further questions.

I'll also, as soon as I have a spare second to work on Iridium, update the docs to try and better reflect what is intended with all those plugin methods and maybe try to expand the examples with more plugins stuff (there's currently one for forcing all collection names to be lowercase and another that adds lowercase and uppercase validators).

CatGuardian commented 6 years ago

Thank you for the clarification. This is a good explanation.

I'm glad I asked otherwise I would have been programming under incorrect assumptions.

What I'm trying to do is implement a createdAt and updatedAt property for every single document that is created and have it set these for me at the appropriate times so I don't have to write code for each of my Document/Instance/Model declarations.

I see you have a plugin already for this on npm but I'm not exactly sure if it still works with the current version of Iridium. At the very least it confused me on how to work it. Should I make my own?

notheotherben commented 6 years ago

Unfortunately that is for an older version of Iridium and, as you noticed, it doesn't work with the current plugin API.

The following should, hopefully, do the trick for you - it updates the model's schema with those fields and then adds onCreating and onSaving hooks to ensure that those fields are updated correctly. It's worth keeping in mind that you can still bypass those hooks if you interact with the collection directly or use the Model.update() method to update an entity (or multiple entities) since it does so without instantiating Instances for them (and therefore without calling the hooks).

import Iridium = require("../iridium");
import Skmatc = require("skmatc");

export interface TimestampsDoc {
    createdAt?: Date;
    updatedAt?: Date;
}

export class TimestampsPlugin implements Iridium.Plugin {
    newModel(model: Iridium.Model<any, any>): void {
        Object.assign(model.schema, {
            createdAt: "readonly",
            updatedAt: "readonly"
        })

        const oldOnCreating = model.hooks.onCreating
        model.hooks.onCreating = (doc: any) => {
            oldOnCreating && oldOnCreating(doc)

            doc.createdAt = new Date()
            doc.updatedAt = new Date()
        }

        const oldOnSaving = model.hooks.onSaving
        model.hooks.onSaving = (instance: Iridium.Instance<any, any>, changes: Iridium.Changes) => {
            oldOnSaving && oldOnSaving(instance, changes)

            changes.$set = Object.assign(changes.$set, {
                updatedAt: new Date()
            })
        }
    }

    validate = [
        Skmatc.create((schema) => schema === "readonly", function (schema, data, path) { return this.fail("no changes allowed") })
    ];
}

Please let me know if you run into any issues with it and good luck :+1:

CatGuardian commented 6 years ago

Thanks! I'll give it a try. And yeah I realize that any interactions with the non-standard methods provided by InstanceInterface will be generally using MongoDB stuff.