The old meteor-application-template-react’s Mongo Collection

In the original, pre Fall 2020, meteor-application-template-react we have a single collection Stuffs. I’ve copied the code here:

import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';

/** Define a Mongo collection to hold the data. */
const Stuffs = new Mongo.Collection('Stuffs');

/** Define a schema to specify the structure of each document in the collection. */
const StuffSchema = new SimpleSchema({
  name: String,
  quantity: Number,
  owner: String,
  condition: {
    type: String,
    allowedValues: ['excellent', 'good', 'fair', 'poor'],
    defaultValue: 'good',
  },
}, { tracker: Tracker });

/** Attach this schema to the collection. */
Stuffs.attachSchema(StuffSchema);

/** Make the collection and schema available to other code. */
export { Stuffs, StuffSchema };

As you can see Stuffs is a Mongo collection with a SimpleSchema attached to do validation. We then export the collection and the schema.

To initialize the Stuffs collection we use meteor-application-template-react/app/imports/startup/server/Mongo.js copied here:

import { Meteor } from 'meteor/meteor';
import { Stuffs } from '../../api/stuff/Stuff.js';

/* eslint-disable no-console */

/** Initialize the database with a default data document. */
function addData(data) {
  console.log(`  Adding: ${data.name} (${data.owner})`);
  Stuffs.insert(data);
}

/** Initialize the collection if empty. */
if (Stuffs.find().count() === 0) {
  if (Meteor.settings.defaultData) {
    console.log('Creating default data.');
    Meteor.settings.defaultData.map(data => addData(data));
  }
}

We import the Mongo collection and call methods on the raw Mongo collection, Stuffs.find().count() and Stuffs.insert(data). Similarly, in the meteor-application-template-react/app/imports/ui/pages/EditStuff.jsx we import the raw Mongo collection and call Stuffs.update.

import 'uniforms-bridge-simple-schema-2'; // required for Uniforms
import { Stuffs, StuffSchema } from '../../api/stuff/Stuff';

/** Renders the Page for editing a single document. */
class EditStuff extends React.Component {

  /** On successful submit, insert the data. */
  submit(data) {
    const { name, quantity, condition, _id } = data;
    Stuffs.update(_id, { $set: { name, quantity, condition } }, (error) => (error ?
      swal('Error', error.message, 'error') :
      swal('Success', 'Item updated successfully', 'success')));
  }

  /** If the subscription(s) have been received, render the page, otherwise show a loading icon. */
  render() {
    return (this.props.ready) ? this.renderPage() : <Loader active>Getting data</Loader>;
  }
// additional code snipped.

We use the StuffSchema to create the Uniforms form to edit the Stuff.

Issues with the meteor-application-template-react’s Stuffs collection

There are several issues with the current meteor-application-template-react implementation. We did this on purpose to not overwhelm the ICS 314 students. We’ll talk about two issues with the Stuffs collection.

1. We have little control over inserting, updating and removing Stuff.

The Stuffs collection is very simple, there are no foreign key relationships. If there were, we’d need to do the querying in the UI code and this becomes very difficult to ensure integrity. Even with the simplicity of the Stuffs collection, we don’t validate the update data. Does it make any sense to have a negative quantity? Where should we enforce this requirement? In the StuffSchema? In the UI?

2. We have spread out the publish/subscribe code to many files.

We define the Meteor publications in meteor-application-template/app/imports/startup/server/Publications.js. We have two different publications ‘Stuff’, the stuff owned by the logged in user, and ‘StuffAdmin’, all the stuff. The subscriptions are in the UI code, EditStuff.jsx, ListStuff.jsx, and ListStuffAdmin.jsx.

There are many issues that arise from splitting up the publication and subscriptions into different files.

Solution in meteor-application-template-react-production

To solve these two issues, we’ve wrapped the Stuff collection in a class. This class has methods for accessing the collection, defining, updating, and removing items. It also has a publish method to create all the publications and separate subscribe methods for the subscriptions. This way users of the collection don’t have to worry about strings and typos.

BaseCollection class

We created an abstract BaseCollection class that wraps the Mongo collection.

class BaseCollection {
  /**
   * Superclass constructor for all meteor-application-template-react-production entities.
   * Defines internal fields needed by all entities: _name, _collectionName, _collection, and _schema.
   * @param {String} type The name of the entity defined by the subclass.
   * @param {SimpleSchema} schema The schema for validating fields on insertion to the DB.
   */
  constructor(type, schema) {
    this._name = type;
    this._collectionName = `${type}Collection`;
    this._collection = new Mongo.Collection(this._collectionName);
    this._schema = schema;
    this._collection.attachSchema(this._schema);
  }

It wraps the collection and applys the schema to the collection. We also define the following methods:

StuffCollection class

The StuffCollection now extends BaseCollection.

import BaseCollection from '../base/BaseCollection';

export const stuffConditions = ['excellent', 'good', 'fair', 'poor'];

class StuffCollection extends BaseCollection {
  constructor() {
    super('Stuffs', new SimpleSchema({
      name: String,
      quantity: Number,
      owner: String,
      condition: {
        type: String,
        allowedValues: stuffConditions,
        defaultValue: 'good',
      },
    }));
  }

StuffCollection a subclasses of BaseCollection implements the following methods:

Solution to issue 1. The StuffCollection has full control over insertion, updating and removing items.

The define, update, and removeIt methods allows the StuffCollection to validate the parameters to ensure database integrity.

Solution to issue 2. The StuffCollection provides the two publications and methods to subscribe.

app/imports/startup/server/Publications.js now calls Stuffs.publish(). As shown below.

// imports/startup/server/Publications.js

import { Stuffs } from '../../api/stuff/StuffCollection';

/** Publish all the collections you need. */
Stuffs.publish();

The subscriptions in the UI code changes slightly. EditStuff.jsx, ListStuff.jsx, and ListStuffAdmin.jsx have one line changed. For example the ListStuff.jsx subscriptions now looks like:

/** withTracker connects Meteor data to React components. https://guide.meteor.com/react.html#using-withTracker */
export default withTracker(() => {
  // Get access to Stuff documents.
  const subscription = Stuffs.subscribeStuff(); // was const subscription = Meteor.subscribe('Stuff'); 
  return {
    stuffs: Stuffs.find({}).fetch(),
    ready: subscription.ready(),
  };
})(ListStuff);

We don’t need to worry about the exact name of the publication since the StuffCollection takes care of that.

MATRP