Collections and Schemas

This is what I was referring to:

Is there some way we can make the text clearer?

Ohhhhhhhhhh wait I got totally confused. You were referring to the code snippet above from Todos. I was thinking of this section: http://guide.meteor.com/security.html#allow-deny

My mistake.

I have a ValidatedMethod that updates a collection document. Only the document author should be able to make updates. I tried to implement the editableBy helper from the todos example using dburles:collection-helpers as recommend in the Guideā€™s Collection helpers section.

const project = Projects.findOne({ _id: projectId });
if (!project.editableBy(this.userId)) {
  throw new Meteor.Error('Projects.methods.edit.unauthorized', 'Only author can edit a project.');
}

It kept failing and then I re-read the Errors in method simulation section.

ValidatedMethod turns on undocumented option in Meteor to avoid calling the server-side implementation if the simulation throws an error.

I realized that the findOne in my code above would only work on the server. On the client it would always return null and throw and error which would block the server code. As a fix, I just wrapped the above code with !this.isSimulation

How does the Todos example work, because it doesnā€™t use isSimulation?

In Todos we publish the fields required to check the permissions to the client. But if you donā€™t, youā€™ll need to make the check server-only by using isSimulation.

Sorry, Iā€™m having a hard time figuring out where the example makes those fields public. I tried adding publicFields but it had no effect.

Projects.publicFields = {
  authorId: 1,
};

Which file actually makes it happen? Does it require and additional library like reywood:publish-composite?

What about Typescript and schema? aldeed:simple-schema does not seem to provide a definition type. So it could be useful to write just a quote on what to use for schema validation with typescript.

In todos the publicFields restriction is used in the lists.public Meteor.publish declaration. The values set in Lists.publicFields are used in the Collectionā€™s find query, as Mongo field specifiers.

Thanks but my original question was how does the Todos example make certain fields of a collection accessible to a Meteor methodā€™s client side simulation. Sorry if Iā€™m missing something simple.

Right - letā€™s use the todos lists.updateName ValidatedMethod as an example:

export const updateName = new ValidatedMethod({
  ...
  run({ listId, newName }) {
    const list = Lists.findOne(listId);

    if (!list.editableBy(this.userId)) {
      throw new Meteor.Error('lists.updateName.accessDenied',
        'You don\'t have permission to edit this list.');
    }
    ...
  },
});

When run on the client side, the list is first set from the findOne. This findOne is leveraging the data brought over from the lists publications. In those publications (like the lists.public publication) the Lists.publicFields object is used to restrict which fields are sent back over to the client. When the list is set in the above code, only the following fields are sent back to the client:

Lists.publicFields = {
  name: 1,
  incompleteCount: 1,
  userId: 1,
};

Continuing on in the above ValidatedMethod, we get to the list.editableBy call. On the client the passed in this.userId is the logged in user ID. When this parameter is passed into the editableBy collection helper, itā€™s compared against the userId value that was loaded from the Lists collection above. So looking at the editableBy helper:

...
editableBy(userId) {
  if (!this.userId) {
    return true;
  }
  return this.userId === userId;
},
...

The passed in userId is the logged in user ID sent in from the ValidateMethod, which is then compared against this.userId, which in the case of a collection helper is the userId of the loaded collection. Since we allowed userId to be published by our Lists publications, this check can now be performed successfully. If we hadnā€™t allowed userId to be published to the client from our publication, this check would fail on the client.

1 Like

Thanks for the detailed explanation. What your saying all makes sense, but I canā€™t seem to make it work in my code. When I run findOne in my method, it works on the server but returns nothing on the client.

I thought to use publications, you need to create a subscription to it on the client. Somewhere Iā€™m missing the glue that allows the method (in the simulation) to use findOne.

Yes - todos is doing this for lists in the main layout:

Template.App_body.onCreated(function appBodyOnCreated() {
  this.subscribe('lists.public');
  this.subscribe('lists.private');
  ...
2 Likes

Thanks for you patience. I think I understand now. btw: Iā€™m using the React version of Todos.

AppContainer.jsx subscribes to lists.public. I originally thought that only the lists array passed as a prop was visible to the client. I didnā€™t realize that once the subscription was ready, Meteor methods on the clients could query the subset of data returned by the subscription using find or findOne.

My component is a form to edit data. I didnā€™t use a subscription because I donā€™t need the data to be reactive. I just use a method in the constructor to populate the default values. In this scenario, I assume it is more efficient just to stick with the isSimulation check.

By the way, what about https://github.com/matb33/meteor-collection-hooks for denormalizing?

In the absence of a React version of aldeed:autoform, what are folks using to generate forms from SimpleSchema in React?

+1

same issue - what is the most comprehensive solution to use simpleschema in React?

Regarding:
Todos = new Mongo.Collection('Todos');

Wouldnā€™t that create a collection called ā€œTodosā€ when really the preferred Mongo convention for naming of collections is lowercase i.e. ā€œtodosā€

I feel like the correct syntax would be:
Todos = new Mongo.Collection('todos');

Thoughts?

Is this convention recorded somewhere? We felt it was simpler to name it the same as the variable.

Agreed with naming the collection the same as the variable. The Mongo convention of naming collections lowercase is simply a tutorial convention. In production, itā€™s much better to name the collection the same as whatever JSON object is being stored as a record.

For example, if you have a well structured JSON object called ā€˜Patientā€™ thatā€™s been approved by standards groups such as HL7, and supported by all the major EMR vendorsā€¦ you name the collection ā€˜Patientsā€™, not ā€˜patientsā€™.

Itā€™s the convention they use in the documentation: https://docs.mongodb.com/v3.2/reference/method/db.createCollection/

What would be the reason for using a different convention for tutorial sake to the one you would suggest people use in production?

Surely the point of a tutorial is for people to follow them in their own code base?

Furthermore, the collection ā€œvariableā€ in this case as you put it is only capitalised because it is actually an instance of a class that uses the the ā€œConstructor Invocation Patternā€ - and capitalisation is the correct convention only in this case. If it were in fact a simple JSON object as you suggest, the correct naming convention in JS would be camelCase.

In your specific scenario it sounds like the standards groups (HL7/EMR) in your field have chosen to contradict general JS language conventions (which I suppose is their prerogative, perhaps for consistency with older systems / DBs like SQL).

However, this discussion is around the naming conventions of MongoDB not the naming conventions of JS, which I feel are pretty clear-cut