GithubHelp home page GithubHelp logo

serkandurusoy / meteor-collection2 Goto Github PK

View Code? Open in Web Editor NEW

This project forked from meteor-community-packages/meteor-collection2

0.0 1.0 0.0 264 KB

A smart package for Meteor that extends Meteor.Collection to provide support for specifying a schema and then validating against that schema when inserting and updating. Also adds support for virtual fields.

License: MIT License

JavaScript 100.00%

meteor-collection2's Introduction

Collection2 Build Status

A smart package for Meteor that extends Meteor.Collection to provide support for specifying a schema and then validating against that schema when inserting and updating. Also adds support for virtual fields.

This package requires and automatically installs the simple-schema package, which provides the SimpleSchema object type for defining and validating against schemas.

Installation

Install using Meteorite. When in a Meteorite-managed app directory, enter:

$ mrt add collection2

Why Use Collection2?

In addition to getting all of the benefits provided by the simple-schema package, Collection2 sets up automatic validation, on both the client and the server, whenever you do a normal insert() or update(). Once you've defined the schema, you no longer have to worry about invalid data. Collection2 makes sure that nothing can get into your database if it doesn't match the schema.

Example

Define the schema for your collection by setting the schema option to a SimpleSchema instance.

Books = new Meteor.Collection("books", {
    schema: new SimpleSchema({
        title: {
            type: String,
            label: "Title",
            max: 200
        },
        author: {
            type: String,
            label: "Author"
        },
        copies: {
            type: Number,
            label: "Number of copies",
            min: 0
        },
        lastCheckedOut: {
            type: Date,
            label: "Last date this book was checked out",
            optional: true
        },
        summary: {
            type: String,
            label: "Brief summary",
            optional: true,
            max: 1000
        }
    })
});

Do an insert:

Books.insert({title: "Ulysses", author: "James Joyce"}, function(error, result) {
  //The insert will fail, error will be set,
  //and result will be undefined or false because "copies" is required.
  //
  //The list of errors is available by calling Books.simpleSchema().namedContext().invalidKeys()
});

Or do an update:

Books.update(book._id, {$unset: {copies: 1}}, function(error, result) {
  //The update will fail, error will be set,
  //and result will be undefined or false because "copies" is required.
  //
  //The list of errors is available by calling Books.simpleSchema().namedContext().invalidKeys()
});

Schema Format

Refer to the simple-schema package documentation for a list of all the available schema rules and validation methods.

Use the MyCollection2.simpleSchema() method to access the bound SimpleSchema instance for a Meteor.Collection instance. For example:

check(doc, MyCollection2.simpleSchema());

Validation Contexts

In the examples above, note that we called namedContext() with no arguments to access the SimpleSchema reactive validation methods. Contexts let you keep multiple separate lists of invalid keys for a single collection2. In practice you might be able to get away with always using the default context. It depends on what you're doing. If you're using the context's reactive methods to update UI elements, you might find the need to use multiple contexts. For example, you might want one context for inserts and one for updates, or you might want a different context for each form on a page.

To use a specific named validation context, use the validationContext option when calling insert or update:

Books.insert({title: "Ulysses", author: "James Joyce"}, { validationContext: "insertForm" }, function(error, result) {
  //The list of errors is available by calling Books.simpleSchema().namedContext("insertForm").invalidKeys()
});

Books.update(book._id, {$unset: {copies: 1}}, { validationContext: "updateForm" }, function(error, result) {
  //The list of errors is available by calling Books.simpleSchema().namedContext("updateForm").invalidKeys()
});

Validating Without Inserting or Updating

It's also possible to validate a document without performing the actual insert or update:

Books.simpleSchema().namedContext().validate({title: "Ulysses", author: "James Joyce"}, {modifier: false});

Set the modifier option to true if the document is a mongo modifier object.

You can also validate just one key in the document:

Books.simpleSchema().namedContext().validateOne({title: "Ulysses", author: "James Joyce"}, "title", {modifier: false});

Or you can specify a certain validation context when calling either method:

Books.simpleSchema().namedContext("insertForm").validate({title: "Ulysses", author: "James Joyce"}, {modifier: false});
Books.simpleSchema().namedContext("insertForm").validateOne({title: "Ulysses", author: "James Joyce"}, "title", {modifier: false});

Inserting or Updating Without Validating

To skip validation, use the validate: false option when calling insert or update. On the client (untrusted code), this will skip only client-side validation. On the server (trusted code), it will skip all validation.

Additional SimpleSchema Options

In addition to all the other schema validation options documented in the simple-schema package, the collection2 package adds unique, denyInsert, denyUpdate, and autoValue.

unique

Set unique: true in your schema to ensure that non-unique values will never be set for the key. You may want to ensure a unique mongo index on the server as well. Refer to the documentation for the index option.

The error message for this is very generic. It's best to define your own using MyCollection.simpleSchema().messages(). The error type string is "notUnique".

denyInsert and denyUpdate

If you set denyUpdate: true, any collection update that modifies the field will fail. For instance:

Posts = new Meteor.Collection('posts', {
  schema: new SimpleSchema({
    title: {
      type: String
    },
    content: {
      type: String
    },
    createdAt: {
      type: Date,
      denyUpdate: true
    }
  })
});

var postId = Posts.insert({title: 'Hello', content: 'World', createdAt: new Date});

The denyInsert option works the same way, but for inserts. If you set denyInsert to true, you will need to set optional: true as well.

autoValue

NOTE: If you are using a Meteor release prior to 0.7.0, the autoValue feature does not work when you also have virtual fields or a transform.

The autoValue option allows you to specify a function that is called on every insert or update to determine what the value for the field should be. This is a powerful feature that allows you to set up either forced values or default values.

An autoValue function is passed the document or modifier as its only argument, but you will generally not need it. Instead, the function context provides a variety of properties and methods to help you determine what you should return.

If an autoValue function returns undefined, the field's value will be whatever the document or modifier says it should be. Any other return value will be used as the field's value. You may also return special pseudo-modifier objects for update operations. Examples are {$inc: 1} and {$push: new Date}.

The following properties and methods are available in this for an autoValue function:

  • isInsert: True if it's an insert operation
  • isUpdate: True if it's an update operation
  • isUpsert: True if it's an upsert operation (either upsert() or upsert: true)
  • isSet: True if the field is already set in the document or modifier
  • unset(): Call this method to prevent the original value from being used when you return undefined.
  • value: If isSet = true, this contains the field's current (requested) value in the document or modifier.
  • operator: If isSet = true and isUpdate = true, this contains the name of the update operator in the modifier in which this field is being changed. For example, if the modifier were {$set: {name: "Alice"}}, in the autoValue function for the name field, this.isSet would be true, this.value would be "Alice", and this.operator would be "$set".
  • field(): Use this method to get information about other fields. Pass a field name (schema key) as the only argument. The return object will have isSet, value, and operator properties for that field.

Note that autoValue functions are run on the client only for validation purposes, but the actual value saved will always be generated on the server, regardless of whether the insert/update is initiated from the client or from the server.

There are many possible use cases for autoValue. It's probably easiest to explain by way of several examples:

{
  // Force value to be current date (on server) upon insert
  // and prevent updates thereafter.
  createdAt: {
    type: Date,
      autoValue: function() {
        if (this.isInsert) {
          return new Date;
        } else if (this.isUpsert) {
          return {$setOnInsert: new Date};
        } else {
          this.unset();
        }
      },
      denyUpdate: true
  },
  // Force value to be current date (on server) upon update
  // and don't allow it to be set upon insert.
  updatedAt: {
    type: Date,
    autoValue: function() {
      if (this.isUpdate) {
        return new Date();
      }
    }
    denyInsert: true,
    optional: true
  },
  // Whenever the "content" field is updated, automatically set
  // the first word of the content into firstWord field.
  firstWord: {
    type: String,
    optional: true,
    autoValue: function() {
      var content = this.field("content");
      if (content.isSet) {
        return content.value.split(' ')[0];
      } else {
        this.unset(); // Prevent user from supplying her own value
      }
    }
  },
  // Whenever the "content" field is updated, automatically
  // update a history array.
  updatesHistory: {
    type: [Object],
    optional: true,
    autoValue: function() {
      var content = this.field("content");
      if (content.isSet) {
        if (this.isInsert) {
          return [{
              date: new Date,
              content: content.value
            }];
        } else {
          return {
            $push: {
              date: new Date,
              content: content.value
            }
          };
        }
      } else {
        this.unset();
      }
    }
  },
  'updatesHistory.$.date': {
    type: Date,
    optional: true
  },
  'updatesHistory.$.content': {
    type: String,
    optional: true
  },
  // Automatically set HTML content based on markdown content
  // whenever the markdown content is set.
  htmlContent: {
    type: String,
    optional: true,
    autoValue: function(doc) {
      var markdownContent = this.field("markdownContent");
      if (Meteor.isServer && markdownContent.isSet) {
        return MarkdownToHTML(markdownContent.value);
      }
    }
  }
}

index

Use the index option to ensure a MongoDB index for a specific field:

{
  title: {
    type: String,
    index: 1
  }
}

Set to 1 or true for an ascending index. Set to -1 for a descending index. Or you may set this to another type of specific MongoDB index, such as "2d". Indexes works on embedded sub-documents as well.

If you have created an index for a field by mistake and you want to remove it, set index to false:

{
  "address.street": {
    type: String,
    index: false
  }
}

If a field has the unique option set to true, the MongoDB index will be a unique index as well. Then on the server, Collection2 will rely on MongoDB to check uniqueness of your field, which is more efficient than our custom checking.

{
  "pseudo": {
    type: String,
    index: true,
    unique: true
  }
}

Indexes are built in the background so indexing does not block other database queries.

AutoForms

Another great reason to use Collection2 is so that you can use the autoform package. AutoForm makes use of Collection schemas to help you quickly develop forms that do complex inserts and updates with automatic client and server validation. Refer to the autoform documentation for more information.

What Happens When The Document Is Invalid?

The callback you specify as the last argument of your insert() or update() call will have the first argument (error) set to a generic error. But generally speaking, you would probably use the reactive methods provided by the SimpleSchema validation context to display the specific error messages to the user somewhere. The autoform package provides some handlebars helpers for this purpose.

More Details

For the curious, this is exactly what Collection2 does before every insert or update:

  1. Removes properties from your document or mongo modifier object if they are not explicitly listed in the schema.
  2. Automatically converts some properties to match what the schema expects, if possible.
  3. Validates your document or mongo modifier object.
  4. Performs the insert or update like normal, only if it was valid.

Collection2 is simply calling SimpleSchema methods to do these things.

This check happens on both the client and the server for client-initiated actions, giving you the speed of client-side validation along with the security of server-side validation.

Virtual Fields

You can also implement easy virtual fields. Here's an example of that:

Persons = new Meteor.Collection("persons", {
    schema: new SimpleSchema({
        firstName: {
            type: String,
            label: "First name",
            max: 30
        },
        lastName: {
            type: String,
            label: "Last name",
            max: 30
        }
    }),
    virtualFields: {
        fullName: function(person) {
            return person.firstName + " " + person.lastName;
        }
    }
});

This adds the virtual field to documents retrieved with find(), etc., which means you could now do {{fullName}} in your HTML as if fullName were actually stored in the MongoDB collection. However, you cannot query on a virtual field.

Contributing

Anyone is welcome to contribute. Fork, make and test your changes (mrt test-packages ./), and then submit a pull request.

Major Contributors

@mquandalle

meteor-collection2's People

Contributors

aldeed avatar mquandalle avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.