Giter Club home page Giter Club logo

meteor-collection2's Introduction

Collection2 (aldeed:collection2 Meteor package)

Project Status: Active โ€“ The project has reached a stable, usable state and is being actively developed. GitHub JavaScript Style Guide Language grade: JavaScript GitHub tag (latest SemVer) Test suite

A Meteor package that allows you to attach a schema to a Mongo.Collection. Automatically validates against that schema when inserting and updating from client or server code.

This package requires the simpl-schema NPM package, which defines the schema syntax and provides the validation logic.

TOC

Installation

In your Meteor app directory, enter:

4.0

Starting 4.0 collection2 ships with built in aldeed:[email protected].

meteor add aldeed:collection2

3.x

meteor add aldeed:collection2
meteor npm install --save [email protected]

Import using static imports

If you come from a previous version and want to "keep things as they were" then this is the option you should choose.

import 'meteor/aldeed:collection2/static';

Import using dynamic imports

Dynamic imports helps to cut down on bundle size but it requires you to manually load the package for things to work.

import 'meteor/aldeed:collection2/dynamic';

Collection2.load();

Why Use Collection2

  • While adding allow/deny rules ensures that only authorized users can edit a document from the client, adding a schema ensures that only acceptable properties and values can be set within that document from the client. Thus, client side inserts and updates can be allowed without compromising security or data integrity.
  • Schema validation for all inserts and updates is reactive, allowing you to easily display customizable validation error messages to the user without any event handling.
  • Schema validation for all inserts and updates is automatic on both the client and the server, providing both speed and security.
  • The aldeed:autoform package can take your collection's schema and automatically create HTML5 forms based on it. AutoForm provides automatic database operations, method calls, validation, and user interface reactivity. You have to write very little markup and no event handling. Refer to the AutoForm documentation for more information.

Attaching a Schema to a Collection

Let's say we have a normal "books" collection, defined in code that runs on both client and server (common.js):

const Books = new Mongo.Collection('books');

Let's create a SimpleSchema schema for this collection. We'll do this in common.js, too:

const Schemas = {};

Schemas.Book = new SimpleSchema({
  title: {
    type: String,
    label: 'Title',
    max: 200,
  },
  author: {
    type: String,
    label: 'Author',
  },
  copies: {
    type: SimpleSchema.Integer,
    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,
  },
});

Once we have the SimpleSchema instance, all we need to do is attach it to our collection using the attachSchema method. Again, we will do this in common.js:

Books.attachSchema(Schemas.Book);

Now that our collection has a schema, we can do a validated insert on either the client or the server:

Books.insert({ title: 'Ulysses', author: 'James Joyce' }, (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 on `error.invalidKeys` or by calling Books.simpleSchema().namedContext().validationErrors()
});

Or we can do a validated update:

Books.update(book._id, { $unset: { copies: 1 } }, (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 on `error.invalidKeys` or by calling Books.simpleSchema().namedContext().validationErrors()
});

Attaching Multiple Schemas to the Same Collection

Normally, if call attachSchema multiple times, the schemas are merged. If you use the replace: true option, then it will replace the previously attached schema. However, in some cases you might actually want both schemas attached, with different documents validated against different schemas.

Here is an example:

Products.attachSchema(BaseProductSchema);
Products.attachSchema(ExtendedProductSchema, {
  selector: { type: 'extended' },
});

This adds support for having a base (default) schema while also using multiple selector schemas.

There are four behaviors for attaching schemas:

  • Attach schema without selector. This will extend the base schema and any attached selector schemas.
  • Attach schema with selector. If the given selector schema exists, extends the found schema with any new fields. Or add an additional schema that matches the selector only. The new selector schema will first be extended by the base schema, if the base schema is already specified.
  • Replace schema without selector. This replaces the base schema and removes any attached selector schemas. If you want to continue to use the original selector schemas on the collection they must be reattached.
  • Replace schema with selector. This replaces the schema with the same selector with the new schema fields extended by the base schema, if the base schema is already specified. Othewise adds the new selector schema if it was not found.

It is possible to use schemas on collections with or without the base schema specified, and with or without selector schemas specified. The selector schemas will always inherit the base schema. Replacing the base schema effectively serves as deleting all schemas off the collection.

Here is another example:

Products.attachSchema(SimpleProductSchema, { selector: { type: 'simple' } });
Products.attachSchema(VariantProductSchema, { selector: { type: 'variant' } });

Now both schemas are attached. When you insert a document where type: 'simple' in the document, it will validate against only the SimpleProductSchema. When you insert a document where type: 'variant' in the document, it will validate against only the VariantProductSchema.

Alternatively, you can pass a selector option when inserting to choose which schema to use:

Products.insert(
  { title: 'This is a product' },
  { selector: { type: 'simple' } }
);

For an update or upsert, the matching selector can be in the query, the modifier $set object, or the selector option.

attachSchema options

transform

If your validation requires that your doc be transformed using the collection's transform function prior to being validated, then you must pass the transform: true option to attachSchema when you attach the schema:

Books.attachSchema(Schemas.Book, { transform: true });

replace

By default, if a collection already has a schema attached, attachSchema will combine the new schema with the existing. Pass the replace: true option to attachSchema to discard any existing schema.

Attach a Schema to Meteor.users

Obviously, when you attach a schema, you must know what the schema should be. For Meteor.users, here is an example schema, which you might have to adjust for your own needs:

const Schema = {};

Schema.UserCountry = new SimpleSchema({
  name: {
    type: String,
  },
  code: {
    type: String,
    regEx: /^[A-Z]{2}$/,
  },
});

Schema.UserProfile = new SimpleSchema({
  firstName: {
    type: String,
    optional: true,
  },
  lastName: {
    type: String,
    optional: true,
  },
  birthday: {
    type: Date,
    optional: true,
  },
  gender: {
    type: String,
    allowedValues: ['Male', 'Female'],
    optional: true,
  },
  organization: {
    type: String,
    optional: true,
  },
  website: {
    type: String,
    regEx: SimpleSchema.RegEx.Url,
    optional: true,
  },
  bio: {
    type: String,
    optional: true,
  },
  country: {
    type: Schema.UserCountry,
    optional: true,
  },
});

Schema.User = new SimpleSchema({
  username: {
    type: String,
    // For accounts-password, either emails or username is required, but not both. It is OK to make this
    // optional here because the accounts-password package does its own validation.
    // Third-party login packages may not require either. Adjust this schema as necessary for your usage.
    optional: true,
  },
  emails: {
    type: Array,
    // For accounts-password, either emails or username is required, but not both. It is OK to make this
    // optional here because the accounts-password package does its own validation.
    // Third-party login packages may not require either. Adjust this schema as necessary for your usage.
    optional: true,
  },
  'emails.$': {
    type: Object,
  },
  'emails.$.address': {
    type: String,
    regEx: SimpleSchema.RegEx.Email,
  },
  'emails.$.verified': {
    type: Boolean,
  },
  // Use this registered_emails field if you are using splendido:meteor-accounts-emails-field / splendido:meteor-accounts-meld
  registered_emails: {
    type: Array,
    optional: true,
  },
  'registered_emails.$': {
    type: Object,
    blackbox: true,
  },
  createdAt: {
    type: Date,
  },
  profile: {
    type: Schema.UserProfile,
    optional: true,
  },
  // Make sure this services field is in your schema if you're using any of the accounts packages
  services: {
    type: Object,
    optional: true,
    blackbox: true,
  },
  // DISCLAIMER: This only applies to the first and second version of meteor-roles package.
  // https://github.com/Meteor-Community-Packages/meteor-collection2/issues/399
  // Add `roles` to your schema if you use the meteor-roles package.
  // Option 1: Object type
  // If you specify that type as Object, you must also specify the
  // `Roles.GLOBAL_GROUP` group whenever you add a user to a role.
  // Example:
  // Roles.addUsersToRoles(userId, ["admin"], Roles.GLOBAL_GROUP);
  // You can't mix and match adding with and without a group since
  // you will fail validation in some cases.
  roles: {
    type: Object,
    optional: true,
    blackbox: true,
  },
  // Option 2: [String] type
  // If you are sure you will never need to use role groups, then
  // you can specify [String] as the type
  roles: {
    type: Array,
    optional: true,
  },
  'roles.$': {
    type: String,
  },
  // In order to avoid an 'Exception in setInterval callback' from Meteor
  heartbeat: {
    type: Date,
    optional: true,
  },
});

Meteor.users.attachSchema(Schema.User);

This schema has not been thoroughly vetted to ensure that it accounts for all possible properties the accounts packages might try to set. Furthermore, any other packages you add might also try to set additional properties. If you see warnings in the console about keys being removed, that's a good indication that you should add those keys to the schema.

Note also that this schema uses the blackbox: true option for simplicity. You might choose instead to figure out a more specific schema.

(If you figure out a more accurate Meteor.users schema, documentation pull requests are welcome.)

Schema Format

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

Use the MyCollection.simpleSchema() method to access the attached SimpleSchema instance for a Mongo.Collection instance. For example:

MyCollection.simpleSchema().validate(doc);

Passing Options

In Meteor, the update function accepts an options argument. Collection2 changes the insert function signature to also accept options in the same way, as an optional second argument. Whenever this documentation says to "use X option", it's referring to this options argument. For example:

myCollection.insert(doc, { validate: false });

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 collection. 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' },
  (error, result) => {
    //The list of errors is available by calling Books.simpleSchema().namedContext("insertForm").validationErrors()
  }
);

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

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()
  .validate(
    { title: 'Ulysses', author: 'James Joyce' },
    { modifier: false, keys: ['title'] }
  );

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')
  .validate(
    { title: 'Ulysses', author: 'James Joyce' },
    { modifier: false, keys: ['title'] }
  );

Refer to the simpl-schema package documentation for more information about these methods.

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. The object is still cleaned and autoValues are still generated.

Inserting or Updating Without Cleaning

Skip removing properties that are not in the schema

To skip object property filtering, set the filter option to false when you call insert or update.

Skip conversion of values to match what schema expects

To skip automatic value conversion, set the autoConvert option to false when you call insert or update.

Skip removing empty strings

To skip removing empty strings, set the removeEmptyStrings option to false when you call insert or update.

Skip generating automatic values

To skip adding automatic values, set the getAutoValues option to false when you call insert or update. This works only in server code.

Pick or omit from the attached schema

To pick or omit fields from the schema for the operation, set the 'pick' or 'omit' option respectively to an array of schema field names. These options are mutually exclusive, so you cannot have both present in the options object at the same time.

This is the implementation of pick and omit functionality from simple-schema, but on your DB calls like this:

// Will insert everything except 'noop'
collection.insert(
  { foo: 'foo', noop: 'nooooo', bar: 'whiskey' },
  { omit: ['noop'] }
);
// Pick only 'foo'
collection.update(
  { _id: 'myid' },
  { $set: { foo: 'test', bar: 'changed' } },
  { pick: ['foo'] }
);

Inserting or Updating Bypassing Collection2 Entirely

Even if you skip all validation and cleaning, Collection2 will still do some object parsing that can take a long time for a large document. To bypass this, set the bypassCollection2 option to true when you call insert or update. This works only in server code.

Additional SimpleSchema Options

In addition to all the other schema validation options documented in the simpl-schema package, the collection2 package adds additional options explained in this section.

index and unique

See https://github.com/aldeed/meteor-schema-index

denyInsert and denyUpdate

See https://github.com/aldeed/meteor-schema-deny

autoValue

The autoValue option is provided by the SimpleSchema package and is documented there. Collection2 adds the following properties to this for any autoValue function that is called as part of a C2 database operation:

  • 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)
  • userId: The ID of the currently logged in user. (Always null for server-initiated actions.)
  • isFromTrustedCode: True if the insert, update, or upsert was initiated from trusted (server) code
  • docId: The _id property of the document being inserted or updated. For an insert, this will be set only when it is provided in the insert doc, or when the operation is initiated on the client. For an update or upsert, this will be set only when the selector is or includes the _id, or when the operation is initiated on the client.

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();  // Prevent user from supplying their own value
      }
    }
  },
  // 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 store
  // the first word of the content into the "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();
      }
    }
  },
  // Whenever the "content" field is updated, automatically
  // update a history array.
  updatesHistory: {
    type: Array,
    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.$': {
    type: Object,
  },
  '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);
      }
    }
  }
}

custom

The custom option is provided by the SimpleSchema package and is documented there. Collection2 adds the following properties to this for any custom function that is called as part of a C2 database operation:

  • 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)
  • userId: The ID of the currently logged in user. (Always null for server-initiated actions.)
  • isFromTrustedCode: True if the insert, update, or upsert was initiated from trusted (server) code
  • docId: The _id property of the document being inserted or updated. For an insert, this will be set only when it is provided in the insert doc, or when the operation is initiated on the client. For an update or upsert, this will be set only when the selector is or includes the _id, or when the operation is initiated on the client.

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 an Error instance. The error message for the first invalid key is set in the error.message, and the full validationErrors array is available on error.invalidKeys. This is true on both client and server, even if validation for a client-initiated operation does not fail until checked on the server.

If you attempt a synchronous operation in server code, the same validation error is thrown since there is no callback to pass it to. If this happens in a server method (defined with Meteor.methods), a more generic Meteor.Error is passed to your callback back on the client. This error does not have an invalidKeys property, but it does have the error message for the first invalid key set in error.reason.

Generally speaking, you would probably not use the Error for displaying to the user. You can instead use the reactive methods provided by the SimpleSchema validation context to display the specific error messages to the user somewhere in the UI. The autoform package provides some UI components and 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. (To skip this, set the filter option to false when you call insert or update.)
  2. Automatically converts some properties to match what the schema expects, if possible. (To skip this, set the autoConvert option to false when you call insert or update.)
  3. Optimizes your operation so that empty string values will not be stored. (To skip this, set the removeEmptyStrings option to false when you call insert or update.)
  4. Adds automatic (forced or default) values based on your schema. Values are added only on the server and will make their way back to your client when your subscription is updated. (To skip this in server code, set the getAutoValues option to false when you call insert or update.)
  5. Validates your document or mongo modifier object. (To skip this, set the validate option to false when you call insert or update.)
  6. Performs the insert or update like normal, only if it was valid.

Collection2 is simply calling SimpleSchema methods to do these things. The validation 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.

Community Add-On Packages

Automatic Migrations

The davidyaha:collection2-migrations package can watch for schema changes between server restarts and perform some automatic data migration and cleanup.

Problems

You might find yourself in a situation where it seems as though validation is not working correctly. First, you should enable SimpleSchema debug mode by setting SimpleSchema.debug = true, which may log some additional information. If you're still confused, read through the following tricky, confusing situations.

SubObjects and Arrays of Objects

One critical thing to know about Collection2 and SimpleSchema is that they don't validate the saved document but rather the proposed insert doc or the update modifier. In the case of updates, this means there is some information unknown to SimpleSchema, such as whether the array object you're attempting to modify already exists or not. If it doesn't exist, MongoDB would create it, so SimpleSchema will validate conservatively. It will assume that any properties not set by the modifier will not exist after the update. This means that the modifier will be deemed invalid if any required keys in the same object are not explicitly set in the update modifier.

For example, say we add the following keys to our "books" schema:

{
    borrowedBy: {
        type: Array
    },
    'borrowedBy.$': {
        type: Object
    },
    "borrowedBy.$.name": {
        type: String
    },
    "borrowedBy.$.email": {
        type: String,
        regEx: SimpleSchema.RegEx.Email
    },
}

Every object in the borrowedBy array must have a name and email property.

Now we discover that the name is incorrect in item 1, although the email address is correct. So we will just set the name to the correct value:

Books.update(id, { $set: { 'borrowedBy.1.name': 'Frank' } });

However, this will not pass validation. Why? Because we don't know whether item 1 in the borrowedBy array already exists, so we don't know whether it will have the required email property after the update finishes.

There are three ways to make this work:

  • $set the entire object
  • $set all required keys in the object
  • Perform the update on the server, and pass the validate: false option to skip validation.

When this situation occurs on the client with an autoForm, it generally does not cause any problems because AutoForm is smart enough to $set the entire object; it's aware of this potential issue. However, this means that you need to ensure that all required properties are represented by an input on the form. In our example, if you want an autoForm that only shows a field for changing the borrowedBy name and not the email, you should include both fields but make the email field hidden. Alternatively, you can submit the autoForm to a server method and then do a server update without validation.

Although these examples focused on an array of objects, sub-objects are treated basically the same way.

Disable displaying collection name in error message

Since version 3.4 an option has been added that disables displaying of collection names in error messages. This is set to false until version 4.0. You can change this by setting the value in your Meteor settings like this:

{
  "packages": {
    "collection2": {
      "disableCollectionNamesInValidation": true
    }
  }
}

Contributors

This project exists thanks to all the people who contribute. [Contribute].

Major Code Contributors

@aldeed @mquandalle @newsiberian @harryadel @StorytellerCZ @SimonSimCity

(Add yourself if you should be listed here.)

meteor-collection2's People

Contributors

aldeed avatar boustanihani avatar captainn avatar coagmano avatar copleykj avatar czeslaaw avatar dependabot[bot] avatar dmitrijs-balcers avatar fatboyxpc avatar grabbou avatar harryadel avatar jankapunkt avatar jimmiebtlr avatar jirikrepl avatar jmariconda avatar lgtm-migrator avatar lourd avatar luckyyang avatar mquandalle avatar mryraghi avatar newsiberian avatar petrometro avatar phocks avatar pierreozoux avatar rcsandell avatar serkandurusoy avatar simonsimcity avatar spencern avatar storytellercz avatar wi2l avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

meteor-collection2's Issues

Upserts populating autoValues into selectors

I've noticed some strange behaviors with Upserts and Collection2. I'd like to give some background on the issue I'm seeing and see if there might be a better way for me to accomplish it. I'm writing Yet Another Basic Chat project to try mind-bending myself into Meteor's way of thinking. In the process I've got Channels and I want to track who is a "visitor" to the channel. My first approach was maintaining an array inside of the Channel document to track users in that channel but I ran into problems (maybe I'll take another look at that another time). I ran away from that approach and I'm now working on a separate collection, Visitors.

Visitors maintains the following properties for now:

user [String]
channel [String]
active [Boolean], autoValue: -> true if not @.isSet
lastSeen [Date], autoValue: -> new Date
firstSeen [Date], autoValue: -> new Date if @.isInsert

I established a MongoDB unique index on user and channel so that there can only be one instance of a Visitor for each user/channel combination. My Collection2 configuration makes use of autoValue for the lastSeen and firstSeen Dates so that I can track those dates effortlessly. (autoValue, for what it's worth, is one of the most incredibly useful features Collection2 adds to Meteor. It's the bee's knees. I cannot say any more explicitly that core Meteor Collections could probably benefit greatly by integrating Collection2. Kudos.) Anyway, getting back to the point... On the server, in the Channel publisher I would like to go ahead and mark a user as a Visitor when they subscribe to a particular channel, like so:

if @.userId
    Channels.update { _id: id }, { $set: { lastVisited: new Date } }, { modifier: true }
    Visitors.upsert { user: @.userId, channel: id }, { }

This was firing off errors within MongoDB because I was attempting to insert a collection that violated the unique constraints. When I executed the same code against the standard Collection (Visitors._collection.upsert) it was OK. When I logged the value of the "args" that Collection2 was passing in to Visitors.upsert I can see it's passing the following data structure:

{
  "args": [
    {
      "user": "TMKNqdWcgS9zavoxA",
      "channel": "RzAW6EHT5Lsar9jFA",
      "active": "true",
      "lastSeen": "Mon Dec 23 2013 11:25:04 GMT-0600 (CST)"
    },
    {
      "$set": {
        "user": "TMKNqdWcgS9zavoxA",
        "channel": "RzAW6EHT5Lsar9jFA",
        "active": "true",
        "lastSeen": "Mon Dec 23 2013 11:25:04 GMT-0600 (CST)"
      }
    }
  ]
}

It would appear to me that the Collection is receiving the autoValue fields as selector fields which means the _insertOrUpdate check will fail to check for existence on the two fields I'd be interested in preventing collisions on. That is, at least, how I'm reading into the issue.

Be able to customize Meteor.users schema

Meteor.users is the default meteor collection storing users documents. It would be cool to be able to define extra fields with extra rules such as index or autoValue on this special collection.

Since this collection already have a kind of default schema, maybe we could use an extendSchema method to define new fields?

Possible bug with allow rules

When using Collection2, insert/update/remove rules for the collection don't seem to be applied. For example,

S = new Meteor.Collection2( "people", { schema: { name: { type: String } } });
S.allow({ insert: function() { return false } });
S.insert({name: 'john doe'})

The insert succeeds even though it shouldn't.

Some checking errors

I noticed that if you have this as the collection 2 definition:

collection = new Meteor.Collection2("ognoCart", {
    'schema' : {
        'product' : {
            'type' : String,
            'label' : "Product _id reference"
        },
        'owner' : {
            'type' : String,
            'label' : "Owner _id reference"
        }
    }
});

Then you get a true in a lot of (to me) weird cases:

collection.insert({ 'product' : 'someId', 'owner'  : 'someId', 'alsoThis' : 'shouldnt work' }); // works
collection.insert({ 'product' : false, 'owner'  : true }); // works
collection.insert({ 'product' : 123, 'owner'  : 321 }); // works

Strange defaultValue behavior, overwriting updates

Hi, I am experiencing a - from my point of view - strange behavior just having the following piece of code as a meteor project:

Books = new Meteor.Collection("books", {
  schema: new SimpleSchema({
    bool1: {
      type: Boolean,
      defaultValue: false
    }
  })
});

Now some commands within my Chromium 33 console:

Books.insert({bool1: false})
"HsCLp3o7y7KMbbHyN"
Books.findOne("HsCLp3o7y7KMbbHyN").bool1
false
Books.update("HsCLp3o7y7KMbbHyN", {$set: {bool1: true}})
undefined
Books.findOne("HsCLp3o7y7KMbbHyN").bool1
false

Shouldn't it be true in the very last line? I just installed all affected packages recently with mrt, so they should be up to date, just the default packages + simple-schema + collection2. Thanks for taking a look at it.

Add latency compensation for autoValue fields

When I call collection2's overridden version of collection's insert it's inserting a copy of the object into client side collection without the autoValue calculated. Once the insert runs on the server, where autoValues are calculated, the client side collection is patched with the autoValue from the server as expected. I think it makes more sense to run it in both places so as to avoid a delay that is visible to the user. Thoughts @aldeed?

Error 0.6.5

When I add my model I get this error I'm running meteor 0.6.5

W20130817-22:40:13.774(-7)? (STDERR) /Volumes/Hybrid/Users/fda/.meteor/tools/4010e5731d/lib/node_modules/fibers/future.js:173
W20130817-22:40:13.856(-7)? (STDERR) throw(ex);
W20130817-22:40:13.857(-7)? (STDERR) ^
W20130817-22:40:13.857(-7)? (STDERR) TypeError: Object.keys called on non-object
W20130817-22:40:13.857(-7)? (STDERR) at Function.keys (native)
W20130817-22:40:13.857(-7)? (STDERR) at new SimpleSchema (packages/simple-schema/simple-schema.js:14)
W20130817-22:40:13.858(-7)? (STDERR) at new Meteor.Collection2 (packages/collection2/collection2.js:11)
W20130817-22:40:13.858(-7)? (STDERR) at app/model.js:14:21
W20130817-22:40:13.858(-7)? (STDERR) at app/model.js:39:3
W20130817-22:40:13.859(-7)? (STDERR) at mains (/Volumes/Hybrid/Users/fda/Documents/Development/Meteor/retrosoda/.meteor/local/build/programs/server/boot.js:153:10)
W20130817-22:40:13.859(-7)? (STDERR) at Array.forEach (native)
W20130817-22:40:13.956(-7)? (STDERR) at Function..each..forEach (/Volumes/Hybrid/Users/fda/.meteor/tools/4010e5731d/lib/node_modules/underscore/underscore.js:79:11)
W20130817-22:40:13.957(-7)? (STDERR) at /Volumes/Hybrid/Users/fda/Documents/Development/Meteor/retrosoda/.meteor/local/build/programs/server/boot.js:80:5
=> Exited with code: 1

Searching virtual fields

I'm not sure if others have found this but I haven't been able to successfully query virtual fields on my Collection2. I can query fields successfully on non-virtual fields as one would expect.

Relationships

One of the natural evolution of the collection2/simpleSchema/autoForm stack is the ability to define some relationships between models. This feature is build in most popular frameworks (Rails [1], Django [2], Symfony [3]...) and there are no good package for this purpose on Meteor right now.

I'm sure you already think of this a lot @aldeed. Could you share what are your plans to support relations at the current state?

Some of the questions raised by this topic:

  • What relations do we want? For instance rails split the one to many relation into belongs_to and has_one. Do we need this kind of distinction?
  • Do we automatically add fields to collections when we need, or do we let the user define explicitly fields names for relationships with other models?
  • How organize the code related to relations, is it more a collection2 or a simpleSchema functionality?
  • How do we manage autoForms with relations?
  • API to declare a relation? Something like this?
    Manufacturers = new Meteor.Collection2('manufacturers', { schema: {
        name: {
            type: String
        }
    }});

    Cars = new Meteor.Collection2('manufacturers', {schema: {
        manufacturer: {
            // Associate a relationship type with another model/collection2
            oneToMany: Manufacturers 
        },
        color: {
            allowedValues: ['white', 'black']
        }
    }});
  • How do we validate a object with relations? Could we add constraints on relations (for instance we might want to limit a blog post to 5 related tags, where posts and tags are both models)?

I think a lot a Meteor users will be interested by this topic, so feel free to post suggestions here :-)

[1] : http://guides.rubyonrails.org/association_basics.html
[2] : https://docs.djangoproject.com/en/1.6/topics/db/models/#relationships
[3] : http://symfony.com/doc/current/book/doctrine.html#entity-relationships-associations

serverOnly

It might be nice to have a serverOnly schema option. Setting this to true would allow the key to be present only when the insert or update operation was initiated on the server. Note that it's not about where the validation is happening but rather where the operation initiated, meaning that the check would happen in the deny function on the server and on the client, but not in _insertOrUpdate on the server.

Access meteor's method context to see if we're in a stub

Hi,

I'm trying to implement a common property, within all my schemas, called onDb which is false if the insert/update is still on the client and true if it has reached the server.

Normally, I'd achieve it by passing the onDb property within my insert/update statement and use this.isSimulation to get the correct value.

But I'm implementing this common property in a base schema which I extend all my other schemas and I like to further improve this abstraction by actually providing the value of onDb from within an autoValue function.

Since the this in this.isSimulation is another context that of which belongs to meteor.methods, is there a way I can access it?

Is there anyway of making collection2 work with meteor-offline-data?

I tried this in coffeescript client side:

@OfflineProducts = new Offline.Collection "products"
@Products = new Meteor.Collection2 @OfflineProducts,
    smart:true
    schema:
        name:
            type:String

Server side I just did this

@Products = new Meteor.Collection2 "products",
    smart:true
    schema:
        name:
            type:String

On-the-Fly Conversions

I have a rough idea of a way to handle on-the-fly conversions.

  1. Add convert option to schema, false by default.
  2. Override the find method. If convert: true and autoValue is set, call an asynchronous update for all of the documents that find is returning, right before they are returned.
  3. For each update, get the complete current doc, pass that to autoValue for each field needing conversion, then do $set for that field with the returned auto value.

The idea is that most schema changes don't require large scale data conversions. Instead, each document can be converted the first time it is requested. Since we'd do the conversion updates asynchronously, it wouldn't affect find performance all that much. Unconverted documents would be used temporarily until the conversion completes, causing deps update.

One key would be to be able to track when the conversion has run for a doc. This likely requires creating an indexed tracking collection to be used internally by C2. This would do two things: prevent running the conversion more than once per doc, and allow the user to see when a conversion has been run on all docs, meaning that any code handling the old schema can be removed. To do this accurately, we might also need a version option, or maybe convert can be set to a version identifier instead of true.

Anyone with thoughts, feel free to comment.

ie8 compatibility?

is ie8 working with collections 2? Or i did something wrong?

I am having errors only in ie8.

SCRIPT1028: Expected identifier, string or number 
    collection2.js, line 28 character 9
SCRIPT1028: Expected identifier, string or number 
    autoform-common.js, line 12 character 9
SCRIPT5009: 'AutoForm' is undefined 
    autoform-client.js, line 1 character 14
SCRIPT445: Object doesn't support this action 
    sites.coffee.js, line 1 character 14
SCRIPT5009: 'Sites' is undefined 
    sites_list.coffee.js, line 26 character 5

Also, using autoform and smart-collections by arunoda.

Collection2 not synching when data is inserted into MongoDB directly.

We have a Python app that is directly inserting information into MongoDB without passing through Meteor, and when using a normal Meteor Collection the Collection automatically updates based on the information entered into the DB, but when we use Collection2 with a schema, the collection doesn't automatically update even though the same information is being added. If we pass the information through a collection it works fine when entering information from the Meteor application, but not when inputting the information into the DB directly. Is this an intended use case of Collection2 that we have to work around (and if so, what is that workaround?), or is this a bug that we've stumbled onto here?

Update fails with embedded objects

It seems that (server side?) updates fail when an it is done on a collection that has "Object" fields.

If you do something like:

@col = new Meteor.Collection2 "col",
    schema:
        "name":
            type: String
        "embed":
            type: Object
        "embed._id":
            type: String

The following will fail:

col.update {_id: ...},{$set:{name:"Updated name"}}

More specifically, you'll get an "invalid key" for "embed._id", even though it is simply not touched by the update. I guess this can be circumvented by using the _collection field directly, but that doesn't sound like what should happen here. (Sorry for using Coffeescript syntax.)

Security Issue

There is a security/data integrity issue with the current implementation. Both client and server just check the doc against the schema and then call the normal collection insert/update/remove. This means that one could insert into the wrapped collection1 directly on the client (c2._collection.insert({})) and the insert would happen on the server without validation.

To fix this I will add the following to the end of the constructor function:

self._collection.deny({
  insert: function (userId, doc) {
    self._simpleSchema.validate(doc);
    return !self._simpleSchema.valid();
  },
  update: function (userId, doc, fields, modifier) {
    self._simpleSchema.validate(modifier);
    return !self._simpleSchema.valid();
  }
});

Click event update fires twice

When I set a min value for a field and make it zero, it wont do an update with an $inc of a negative value.

i.e.
schema:{
Value:{
type:Number,
min:0}
}

BlahBlah.update({_id:Meteor.userId(),{$inc:{Value:-5}}) does not work.

client side update impossible | server side works fine

I have this collection2 :

Items = new Meteor.Collection2("items",{
schema: {
name: { type: String, max: 60 },
mainDirectory: { type: String, max: 65, unique: true },
mainPageThumbnail: { type: [Object]},
mainPageBackground: { type: [Object]},
category: { type: String },
cssStyle: { type: String },
inGridSize: { type: String },
mainRank: { type: Number, max: 999 },
itemSubCategories: { type: [Object] },
'itemSubCategories.$.name': { type: String },
'itemSubCategories.$.text1': { type: String, max: 5000 },
'itemSubCategories.$.text2': { type: String, max: 5000 },
'itemSubCategories.$.video': { type: String, regEx: SchemaRegEx.Url, max: 500 },
'itemSubCategories.$.songs': { type: [Object] },
'itemSubCategories.$.pictures': { type: [Object] },
'itemSubCategories.$.testimonialsPictures': { type: [Object] }
}
});

when I try update itemSubCategories.$.pictures
var modifier = {$set: {}};
modifier.$set[ "itemSubCategories." + activeItemSubCategories + ".pictures" ] = {name: "picture1.jpg", position: '1'},{name: "picture2.jpg", position: '2'} ;

I have this message :
Uncaught Error: When the modifier option is true, validation object must have at least one operator

I try lot of variations, always the same result.
All my others updates on client side work well.
As workaround, I go do my update server side, with the exact same syntax.

Placeholder Attr. in template, is being validated instead of value attribute content

Hi, I think this is a bug..

I've been using the package with 0 issues until I started getting validation errors on the db update method.

Everything seemed to be set up correctly, and it was. I noticed that 2 of 5 fields weren't validating for some reason.

Eventually I discovered only those two fields had placeholder attributes on them in the template, and each value within that placeholder didn't meet the schema. So I deleted them and the document was updated as intended.

Integration with meteor-collection-hooks

matb33/meteor-collection-hooks provides its API via adding two objects (before and after) to Meteor.Collection. The problem is that I can't access it on the collection2. It would be nice to do one of these things, in order of difficulty:

  1. Provide/Document access to the Meteor.Collection.
  2. Alias before and after to the Meteor.Collection.
  3. Provide our own server-side hook system.

invalidkeys() not set when separately constructed schema is included in collection definition

The following is a schema design that incorporates a common base schema. (autovalues etc may not make sense, I just used them to see if features are interchangeable between collection2 and simple-schema)

/********************************
Base Collection Schema
********************************/
CoolProject.Collections.BaseSchema = new SimpleSchema({
  active: {
    type: Boolean
  },
  createdAt: {
    type: Date,
    min: new Date(),
    autoValue: function() {
      if (this.isInsert) {
        return new Date;
      } else if (this.isUpsert) {
        return {$setOnInsert: new Date};
      } else {
        this.unset();
      }
    },
    denyUpdate: true
  },
  updatedAt: {
    type: Date,
    min: new Date(),
    autoValue: function() {
      if (this.isUpdate) {
        return new Date();
      }
    },
    denyInsert: true,
    optional: true
  }
});

/********************************
Tag Collection Schema
********************************/
CoolProject.Collections.TagsSchema = new SimpleSchema([
  CoolProject.Collections.BaseSchema,
  {
    title: {
      type: String,
      label: 'Title',
      min: 5,
      max: 10
    },
    slug: {
      type: String,
      label: 'Slug',
      autoValue: function() {
        var title = this.field("title");
        if (title.isSet) {
          return URLify2(title.value);
        } else {
          this.unset();
        }
      }
    }
  }
]);

/********************************
The Actual Collection
********************************/
CoolProject.Collections.Tags = new Meteor.Collection('tags', {
  schema: CoolProject.Collections.TagsSchema,
  virtualFields: {
    virtualTest: function(tag) {
      return tag.title + ' // ' + tag.slug;
    }
  }});

Then on the browser console, I test the code by inserting some values using:

/********************************
Browser Console
********************************/
CoolProject.Collections.Tags.insert(
  {/*  WRITE SOME DOC HERE */},
  function(err,res) {
    if (err) return CoolProject.Collections.Tags.simpleSchema().namedContext().invalidKeys();   
  }
);

If the document is valid, it passes successfully through and I receive back the inserted document id. The autovalue, and nesting seems to be working. ( Except that the virtual field does not work, but it is already reported on another issue #47 )

THE PROBLEM:

If the doc is invalid (e.g. missing active or too short a title) then all I receive on the console output returns undefined whereas an array of error objects should have returned. This is reproducible on both 0.2.17 and 0.3.0

using transformation function requires adding methods to schema

I am defining models for each of my collection documents following the Animal example pattern in the meteor documentation for collection transforms.

Document inserts fail (with a simple 'access denied', and an empty ctx.invalidKeys() when coming from the client unless I add the method names I define by the transformed object as optional Function schema (makeNoise in the example below). My server side inserts work fine without them.

See example:

// An Animal class that takes a document in its constructor
Animal = function (doc) {
  _.extend(this, doc);
};
_.extend(Animal.prototype, {
  makeNoise: function () {
    console.log(this.sound);
  }
});

// Define a Collection that uses Animal as its document
Animals2 = new Meteor.Collection2("Animals2", {
  transform: function (doc) { return new Animal(doc); },
  schema: {
    name: { type: String },
    sound: { type: String },

    // XXX without this, inserting fails
    makeNoise: { type: Function, optional: true }
  }
});

Animals2.allow({                // even though we have the insecure package, this is necessary with Collection2(?)
  insert: function() {
    return true;
  }
});

if (Meteor.isClient) {
  Meteor.startup(function() {
    // Create an Animal and call its makeNoise method
    Animals2.insert({name: "raptor", sound: "roar"});
    Animals2.findOne({name: "raptor"}).makeNoise(); // prints "roar"
  });
}

I think this should work without adding cruft to the schema like I have above. Is this pattern supported with collection2?

document value as sub-schema lost upon insertion

Hello,
Congratulations for your awsome packages,

I may encounter some kind of bug.

i have schema that includes two other schemas, when inserting a new record, the inserted record loses value of one subschema (informations) and the other one (price) is stored

source : https://github.com/jboulhous/meteor-collection2-bug
demo : http://collection2-lose-sub-schema.meteor.com

go to the demo and insert a new APPLICATION.Produits Model, (there is a helper function newProduct on global scope to generate a model), the demo uses autopublish, you will see that the created entry in mongo loses the "informations" proprety value for an empty object

would you please help me with this.

Enclose collection's allow/deny rules within schema definition

While designing a fairly large domain, I came to the conclusion that, incorporating collection-wide allow/deny rules within the schema definition itself could greatly improve code structuring.

Perhaps with a this similar to that of autovalue's and/or val, obj, operator like valueisallowed's.

I can't imagine how and if it should be implemented on the server, though.

More Smart Collection Support

Collection2 supports passing in an existing SmartCollection, but it might be nice to support a smart: true option in the constructor as well, which would create a new SmartCollection instead of Collection.

Strange error server side

Hi, I got this strange error server-side after updating to the latest version:

(STDERR) myCollection2.callbacks() is deprecated on the server; move to client-only code

I don't run myCollection2.callbacks() at any point though, I put the models in a root .coffee file so they are both server and client. Any ideas on what it could be?

Thanks in advance for your time

Match is not defined

Hey there,

I tried using collection2 in a local package and I got the following exception:

ReferenceError: Match is not defined
at new Meteor.Collection2 (packages/collection2/collection2.js:20)

Is this a bug? I fixed it by adding 'check' as a dependency in collection2's package.js file:

api.use('check');

Subdocuments invalidating and not being stored

I've got a collection like the following:

ConnectionInvitations = new Meteor.Collection2('invitations', {
  schema: {
    'senderId': {
      type: String,
      label: "Sender"
    },
    'receiverId': {
      type: String,
      label: "Receiver"
    },
    'subject': {
      type: String,
      label: "Subject"
    },
    'message': {
      type: String,
      label: "Message"
    },
    'sender': {
      type: {
        'status': {
          type: String,
          allowedValues: [ 'pending', 'accepted' ]
        },
        'deletedAt': {
          type: Date,
          optional: true
        },
        'deletedBy': {
          type: String,
          optional: true
        }
      }
    }
  }
});

I provide all attributes (except the optional ones) on insert.

Problem is:
The document can never be inserted ... unless I stick an "optional: true" attribute on 'sender'. Note, of course, that I don't want the sender to be optional, and I especially do not want the 'sender.status' to be optional. I'm either doing something wrong, or this might best be coded some other way... please advise.

Second problem:
If I stick the "optional: true" attribute on "sender", than the document does get stored but it does not save the 'sender.status'. That is, all attributes are stored except for the sub-attributes.

BTW, in both problem cases I've tried both mongo dot-notation and the regular object hierarchical notation. Same result.

Presence of virtualFields causes fields with autoValues not to be created

With virtualfields included, the fields with autoValue do not make into the database on an insert. If I remove the virtualFields, the autoValue fields are fine. Sample code:

Businesses = new Meteor.Collection2('businesses', {
  schema: {
    businessName: {
      type: String,
      label: "Business name",
      max: 50
    },
    owner: {
      type: String,
      label: "Owner name",
      max: 50,
      optional: true
    },

// autoValue fields:

    createdAt: {
      type: Date,
      autoValue: function() {
        if (this.isInsert) {
          return new Date();
        }
      },
      denyUpdate: true
    },
    createdBy: {
      type: String,
      autoValue: function() {
        if (this.isInsert) {
          return Meteor.userId();
        }
      },
      denyUpdate: true
    },
    updatedAt: {
      type: Date,
      autoValue: function() {
        if (this.isUpdate) {
          return new Date();
        }
      },
      denyInsert: true,
      optional: true
    },
    // Soft delete
    deletedAt: {
      type: Date,
      optional: true
    }
  },

// Virtual field code, e.g.

  virtualFields: {
    neighborhood: function(biz) {
      return "some hood";
    }
  }
});

Some Additional Validation Checks

Add some validation checks for mongo operators that can't be done by SimpleSchema. See Meteor-Community-Packages/meteor-simple-schema#9.

  • $rename (make sure existing values for the renamed key validate for the new key name)
  • $inc (check min/max and allowed values)
  • $addToSet (check maxCount)
  • $push (check maxCount)
  • $pull (check minCount)
  • $pullAll (check minCount)
  • $pop (check minCount)

userId in autoValue

All the collection1 methods for update/insert/remove include the userId. This should also be available in the autoValue method of the schema.

this.fields("xx") undefined in autoValue

Given the following collection as an example:

Assemblies = new Meteor.Collection2("assemblies", {
    schema: {
        name: {
            type: String,
            label: "Name",
            max: 200
        },
        customer: { 
            type: String,
            label: "Customer",      
            optional: true
        },
        coolField: {    
            type: String,
            label: "Cool Field",        
            optional: true,
            autoValue: function() {
                console.log(this.field("name").value);
                return "NEWVALUE";
            }
        }
    }
});

From the client console I do
Assemblies.insert({name: "Name1", customer:"Customer1"});
All normal.. this.field("name").value returns "Name1" (from the autoValue function)
Then I do Assemblies.update('DRCeTWGnMSMfZ4q4s', {$set: {name: "Name2"}});
All normal.. this.field("name").value returns "Name2"
Then I do Assemblies.update('DRCeTWGnMSMfZ4q4s', {$set: {customer: "Customer2"}});
Not Normal.. this.field("name").value returns undefined
Even if I do Assemblies.update('DRCeTWGnMSMfZ4q4s', {$set: {name: "Name3"}}); after that I can never get it to return the name field.. It's broken forever.

Not sure if this is unique to my setup or if you can reproduce this.. I cannot figure out what it is that's causing this.

Responsive autoValue

Consider this:

Customers = new Meteor.Collection2("customers", {
    schema: {
        sales: {
            type: Number,
            label: "Total Sales",       
            allowObserve: true,
            autoValue: function() {
                var salesTotal = 0;
                Invoices.find({_id:this.id}).map(function(doc) {
                  salesTotal += doc.total;
                });
                return salesTotal;
            }
        }
    }
});

So I'm proposing an allowObserve property which would allow autoValue to update any time its calculation changes... observing the collections it's querying against.

Also It would be helpful to have an id property for this on autoValue.. unless I'm missing something.

Has there been any thought on this so far and what do you think?

insert within a method does not pass the error to the caller's callback when the error is from unique constraint

I defined my insert function within a meteor.methods block. Within the insert's own callback, I check the error and if there is one, I throw a meteor.error containing the invalidkeys. Otherwise I return the generated id as the result.

For any validation error except for the unique check, my method callback receives the error.

But for unique error, I receive back an id although no actual db write happens.

If I do normal client side insert, then all is fine.

createdAt field

Issue Initially posted in the autoform repository Meteor-Community-Packages/meteor-autoform#12, I've realized that Collection2 is the right place for this use case.

I've defined a required createdAt field of type: Date in the schema of one Meteor.Collection2 which I'd like to populate just before inserting. To be trustable this field has to be added (or at least validated) on the server.

One solution is to create a myCollection2 that extends Meteor.Collection2, then overwrite the insert method to create the createdAt field before calling super. In coffeescript:

class myCollection2 extends Meteor.Collection2
    insert: (doc, args...) ->
        doc.createdAt = new Date 
        super(doc, args...)

Posts = new myCollection2 'posts',
    schema:
        title:
            type: String
        content:
            type: String
        createdAt:
            type: Date
            # Off topic, but I'd like to define a "immutable",
            # or "not update-able" constraint

if Meteor.isClient
    Posts.insert
        title: "Hello world"
        content: "This is a test"

One other solution might be to add some beforeInsert, beforeUpdate and beforeDelete hooks that take the doc/query and modify it before the real database operation is done.

Moreover I wonder if you would like to have some rails convention over configuration strategy. In other words, if a field called createdAt or updateAt of type Date is present in the schema, fill it automatically without any other code.

Array of Strings failed validation on update push

I just tried this:

@Orders = new Meteor.Collection2(
"orders" # collection name
smart:true # make it a smart collection
schema:
    recipients:
        type:[String]
        optional:true
)

I was able to do an insert of an array of document IDs but updating via $push failed

Orders.update(order._id,{$push:{recipients:@_id}})

I used underscore to check if _id was returning a string by using _.isString and I get true. I also tried changing the schema to this:

@Orders = new Meteor.Collection2(
"orders" # collection name
smart:true # make it a smart collection
schema:
    recipients:
        type:Array
        optional:true
            "recipients.$":
                    type:String
                    optional:true
)

But it still fails. Am I doing something wrong? What can I do to fix this?

Thanks in advance for the help!

Collection2 and meteor collection design practice?

Hi there,
from reading about meteor I picked up a prelimnary list of ideas concerning collection design. I think it would be interesting to discuss them with regard to collection2 and relation handling:

  1. The database collections are primarily modelled for/after different pub/sub queries and to provide atomic documents, not as a normalized data object store.
  2. Publications can contain documents from different collections.
  3. The templates and partials model the (dom) objects that can join data from different (subscribed) collections, and are updated automatically when data changes.
  4. The "less" css styles (with class inheritance) define the appearance (dom view).
  5. Javascript event hooks are added to templates to modify data, which then triggers the computations of invalidated templates and code in Deps.autorun().
  • Thus do a careful analysis of the most commonly used queries in your application.
  • Design collections for optimized server/client data transfer.
  • Collections should reflect/support the performance of the types of queries made.
  • It can be ok to consolidate and merge multiple tables into one collection.
  • Think in terms of documents with different schemas that may be in the same collection, rather than table schemas.
  • In particular, instead of having to rely on a query that joins data from different tables, documents with different schemas may be fetched in one sweep from a single common collection.

However, separate collections can be handled easier when it comes to meteor publication/subscriptions.

And the templates (the dom model) can still join different queries together when rendered. As the data on the client is already reduced to a smaller subset, performance should be reasonably fast even with minimongo not yet supporting indexes.

  • References (relation links) are used to reduce duplication/repetition.
  • A reference may point to another document in the same or another collection.

Bypassing validation

I've been loading some fixture data, where I want some values to be empty when created, but to be validated in all future updates. I was using the collection._collection.insert approach. This is gone now? Is there (or should there be) a parameter that can be passed to skip validation?

Same error on several computers

Hi, I'm getting this error not just on my computer but on other people's meteor installation. What do you think is wrong? Is it the smart.json?

/usr/local/lib/node_modules/meteorite/lib/sources/git.js:151
hrow "There was a problem checking out " + self.checkoutType + ": " + (self.co

$addToSet alongside a denyUpdate field

I found an interesting issue today when exploring the use of $addToSet in conjunction with a Collection2 schema. I'll outline what I've done and what I've found.

First, a Collection:

@TeamMembers = new SimpleSchema
    memberId: 
        type: String
    accessLevel:
        type: String
        allowedValues: [ 'admin', 'developer', 'watcher' ]
    addedOn:
        type: Date
        autoValue: ->
            if @.isInsert
                new Date

@Teams = new Meteor.Collection2 'teams',
    schema:
        name:
            type: String
            unique: true
        members:
            type: [@TeamMembers]
            optional: true
        createdAt:
            type: Date
            autoValue: ->
                if @.isInsert
                    return new Date
            denyUpdate: true
        updatedAt:
            type: Date
            autoValue: ->
                new Date

Of importance here: createdAt is an autoValue for creation date, and it has the denyUpdate property set to true. Members is an array of an object of type TeamMembers. Awesome.

Now on the server I'm going to insert a team and add a team member to it. Maybe kind of like so (boy I hope I'm doing this right):

teamMember = 
    memberId:  userId
    accessLevel:  'admin'
    addedOn:  new Date

Teams.update { _id: team._id }, { $addToSet: { members: teamMember } }

This fails SimpleSchema validation. Upon stepping through in node-inspector I found that it was failing on the createdAt key. "That's weird, I didn't even supply a createdAt value..."

Anyway, in this function:

self._simpleSchema.validator(function(key, val, def, op)

key = createdAt
val = undefined
def = denyUpdate, autoValue, label, type
op = $addToSet

So the issue appears to be in this block:

    if (def.denyUpdate && op !== null) {                                                                       
      // This is an insert of a defined value into a field where denyUpdate=true                               
      if (op === "$set" && val !== void 0) {                                                                   
        return "updateNotAllowed";                                                                             
      } else if (op !== "$set") {                                                                              
        return "updateNotAllowed";     // This is getting hit!                                                                        
      }                                                                                                        
    }                 

My assumption here is that the there either needs to be some logic specifically for $addToSet so that it doesn't evaluate the parent object's keys, or there needs to be a check to see if the keyValue is undefined so that SimpleSchema will ignore it. When I removed the denyUpdate field the update went A-OK.

It may also be worth noting here that I have the updatedAt field which generated an additional $set. This is totally intended in this case.

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.