Giter VIP home page Giter VIP logo

mongoose's Introduction

General Assembly Logo

An Introduction to Mongoose

As you saw in the previous talk, MongoDB is extremely flexible - if you want, you can store data of literally any structure in a collection, even if you haven't defined that structure beforehand. However, this flexibility has a weakness: since you can enter data in any arbitrary format, and there's no built-in validation to permit/reject new documents, there's no assurance that the documents in a collection will be consistent in any way. Fortunately, there's a tool called Mongoose that will help to address these problems.

Prerequisites

  • MongoDB

Objectives

  • Use Mongoose to access and manipulate a MongoDB database from a JavaScript program.
  • Use JavaScript Promises to combine Mongoose operations.
  • Set up validations in Mongoose to validate data for storage in MongoDB.

Preparation

Fork and clone this repo; then run npm install.

Mongoose Schemas, Models, and Documents

"Mongoose is an Object-Document Mapper"

What does that mean?

When we were learning about Rails, we used a tool called ActiveRecord; ActiveRecord was an "Object-Relational Mapper", a tool that allowed us to take relations (i.e. rows) from a SQL table and represent them with Ruby objects. Mongoose fills a similar role, allowing us to represent documents (the MongoDB analogue to SQL relations) using JavaScript objects. Additionally, because Mongoose fits in between our JS code and Mongo, it's able to add some limitations on how Mongo gets used, so that there's greater consistency in our data.

The core elements of Mongoose are:

  • Documents, JavaScript objects that map to Documents in MongoDB.
  • Models, which are Constructor functions that generate new Documents.
  • Schemas, which specify the properties that the Models give to their respective Documents.

Let's look at an example.

const mongoose = require('mongoose');

const personSchema = new mongoose.Schema({
  name: {
    given: String,
    surname: String
  }
});

const Person = mongoose.model('Person', personSchema);

let person = Person.create({...});
// alternatively,
/*
   let person = new Person({...});
   person.save();
*/

personSchema above is a new Mongoose Schema; it specifies a name property with given and surname sub-properties. That Schema gets passed into mongoose.model as an argument, where it is used to create the Person model; Mongoose uses the first argument to map this model to the MongoDB collection people. Finally, we call Person.create to create a new Person document, and store the result in person.

Other Key Schema/Model Features

Schema Options: Setters

In addition to specifying what type of data each attribute is, we can also specify other features, such as default values or default transformations (i.e. automatically uppercasing or lowercasing strings).

This can be done by replacing the type's name in the Schema with an object, like so:

const someSchema = new mongoose.Schema({
  name: {
    given: {
      type: String,
      set: capitalize
    },
    surname:  {
      type: String,
      set: capitalize
    },
  }
  location: {
    type: String,
    default: 'Boston'
  }
});

// if we were to use `capitalize` it would need to be defined elsewhere
const capitalize = function(val) {
  if (typeof val !== 'string') val = '';
  return val.charAt(0).toUpperCase() + val.substring(1);
};

A full list of these options can be found in the Mongoose API documentation.

Schema Options: Validators

As mentioned, MongoDB does not put any limitations on what you put in your collections. Fortunately, Mongoose provides a way to add some boundaries using validators.

const someSchema = new Schema({
    name: {
      type: String,
      required: true
    },
    height: Number
});

Validators are associated with different 'SchemaTypes', i.e. the kind of data that the attribute holds. Every SchemaType implements the required validator, but they also each have their own type-specific validators built in.

Type Built-In Validators
String enum, match, maxlength, minlength
Number max, min
Date max, min

Additionally, custom validators can be written for any type at any time, using the validate option:

const someSchema = new Schema({
    someEvenValue : {
      type: Number
      validate: {
        validator: function(num){
          return num%2 === 0;
        },
        message: 'Must be even.'
      }
    }
});

Virtual Attributes

Another neat feature of Schemas is the ability to define 'virtual attributes': attributes whose values are interrelated with the values of other attributes. In reality, these 'attributes' are actually just a pair of functions - get and set, specifically.

Assuming we have name.given and name.surname properties: we can derive a name.full property from them.

personSchema.virtual('name.full').get(function () {
  return this.name.given + ' ' + this.name.surname;
});

personSchema.virtual('name.full').set(function (name) {
  let split = name.split(' ');
  this.name.given = split[0];
  this.name.surname = split[1];
});

Code-Along

We're going to create a simple command-line program that allows us to perform CRUD in a MongoDB database called mongoose-crud over a collection called people, and display JSON data back in the console. The code for this program will be found in app-people.js, in the root of this repository. The code for reading from the console has already been written for us so that we can focus exclusively on the Mongoose piece of the puzzle.

As you can see, the code in that section is incomplete.

const create = function(givenName, surname, dob, gender) {
  /* Add Code Here */
};

const index = function() {
  /* Add Code Here */
};

const show = function(id) {
  /* Add Code Here */
};

const update = function(id, field, value) {
  /* Add Code Here */
};

const destroy = function(id) {
  /* Add Code Here */
};

We're going to add the missing code so that our app can do CRUD.

First, we need to create the database that app-people.js references, mongoose-crud.

mongo mongoose-crud

Inside person.js, which is located in the models directory, let's first define a Schema for Person. A person should have several properties: name.given, name.surname, dob, gender, height, weight, and age (a virtual property). Additionally, each Person document should have timestamps indicating when it was created and when it was last modified.

Next, we'll use the Schema to generate a new Model, and export that Model out of the module.

Finally, we'll need to require this Model from app-people.js if we want to be able to use it there. And then we can run node scripts/people.js to load people into our mongoose-crud people collection in the correct format of our mongoose schema.

Now let's actually get into writing the CRUD actions.

Create

Finishing the create method will be pretty straightforward, since Mongoose already gives us a create method.

According to the documentation, here is the signature for create:

Model.create(doc(s), [callback])

This means that the create method takes an object representing a document (or several objects, representing multiple documents) with an optional callback as the final argument. That callback will be handed several arguments: first, a reference to any errors created during the create operation, and second, a list of references to the newly created documents (one for each object passed in).

Please follow as I code along this action.

Read

Next, let's fill in the index and read (i.e. search) methods. To do this, we're going to need to query MongoDB using Mongoose. Mongoose has a couple of methods for doing this, just like ActiveRecord did.

Mongoose Method Rough ActiveRecord Equivalent
find where (or, with no arguments, all)
findById find
findOne find_by

For index, we want to get all People, so we'll use find.

The Mongoose documentation gives the signature of find as

Model.find(conditions, [projection], [options], [callback])

where conditions are the search parameters, i.e. {'name.given': 'Bob'}; optional parameters projection and options offer additional configuration; lastly, find accepts a callback.

Please follow along as I code this action.

Now let's implement show. We'll use findById instead of find, since we specifically want to look up a document by its ID.

Please follow along as I code this action

Update

To do an update in Rails, you need to (a) look up the record you want by its ID, and then (b) have it update one or more of its values. As we've just seen, the first of these can be accomplished using findById. To do the second, we need to actually change a property on the document, and then run .save.

Please follow along as I code

Destroy

The destroy method should look a lot like the show and update methods.

The Mongoose method we want to use here is remove;

Please follow along as I code

Lab

In your squads, repeat this exercise for a new resource, Places. Places have the following features:

  • name (required)
  • latitude (required)
  • longitude (required)
  • country
  • isNorthernHemisphere? (virtual)
  • isWesternHemisphere? (virtual)

You should ensure that only reasonable values of latitude and longitude are allowed to be added to the database.

Create a new file for your Mongoose model, and load it from the app-places.js file; that file will provide a command-line UI for performing CRUD on you new Places resource.

Like in the code-along, the 'action' methods in app-places.js have no content; you'll need to fill them up with code for doing CRUD on your new model.

Additional Resources

  1. All content is licensed under a CC­BY­NC­SA 4.0 license.
  2. All software code is licensed under GNU GPLv3. For commercial use or alternative licensing, please contact [email protected].

mongoose's People

Contributors

bengitscode avatar bernardlee avatar cwilbur avatar dependabot[bot] avatar ga-meb avatar jrhorn424 avatar payne-chris-r avatar rdegnen avatar realweeks avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

mongoose's Issues

Does this code work? (From README)

const someSchema = new mongoose.Schema({
  name: {
    given: {
      type: String
      set: capitalize
    },
    surname:  {
      type: String
      set: capitalize
    },
  }
  location: {
    type: String,
    default: 'Boston'
  }
});

It has no , after String, and atom doesn't seem to know what capitalize is. Do we need to require something else? I think this syntax is incorrect...

Drop `findByIdAndUpdate`

Instead, have students do the following:

Thing.findById(id)
    .then(thing => {
      thing[field] = value;
      return thing.save();
    })
    .then(thing => console.log(thing.toJSON()))
    .catch(console.error);

Do not use findByIdAndUpdate, because that prevents you from implementing a lot of things.
Also be careful to mention that update does not pass along the modified document.

I'm assuming h stands for hash

Hashes don't exist in javascript.

const mapPerson = function (h) {
  let newPerson = {
    name: {}
  };
  Object.keys(h).forEach(function() {
    newPerson.name.given = h.given_name;
    newPerson.name.surname = h.surname;
    newPerson.dob = h.dob;
    newPerson.gender = h.gender;
    newPerson.height = h.height;
    newPerson.weight = h.weight;
  });
  return newPerson;
};

Remove `if(true)`

Why have the true at all? This is the same as writing if(true)

if (true || givenName) {
        create(givenName, surname, dob, gender, height, weight);
      } 

dob{ match: /\d{4}-\d{2}-\d{2}/ } does not work as expected in Person model

Example of dates I am able to enter for dob key on create:
Enter: 1-1-1 Result: dob: Mon Jan 01 2001 00:00:00 GMT-0500 (EST),
Enter: 12-16-1 Result: dob: Sun Dec 16 2001 00:00:00 GMT-0500 (EST),

Examples of dates I am not able to enter for dob key on create:
Enter: 13-16-1 (It throws a validation error)

So our match is not working as we expect it to. Also, it seems to flip values around, as 12-16-1 returns Sun Dec 16 as the key value, even though we are trying to get dates in the format of YYYY-MM-DD

Issues and branches need clean

Some issues have been resolved on some branches, others have not. There there is a master, 011/master and 012/master. What is the best fix here @ga-wdi-boston/core

Typo

var peronsSchema in the Schema code snippet.

Lesson too long

During last two iterations instructors were not able to get to or start the lab in the time allotted. All estimates do not take into account the lab. Maybe add as practice?

Modify check is probably unnecessary

This is on the solution branch.
@payne-chris-r sez:

const update = function(id, field, value) {
  let modify = {};
  modify[field] = value;
  Person.findById(id).then(function(person) {
    person[field] = value;
    return person.save();
  }).then(function(person) {
    console.log(person.toJSON());
  }).catch(console.error).then(done);
};

The o.O is about the two modify lines at the top of the function.
It will throw an error if field is undefined. The modify object is not used. If anything, this should be an if(field), but I don't think even that is needed. (Says me.)

Need an additional `done`?

Do we need a done( ) in the catch section as well? Does it auto exit?

const create = function(givenName, surname, dob, gender, height, weight) {
  Person.create({
    'name.given': givenName,
    'name.surname': surname,
    dob: dob,
    gender: gender,
    height: height,
    weight: weight
  }).then((person) => {
    console.log(person.toJSON());
    done();   // We need to call this to terminate the connection.
  }).catch((err) => {
    console.error(err);
  });
};

Extraneous code

const update = function(id, field, value) {
-  let modify = {};
+ // let modify = {};
-  modify[field] = value;
+ // modify[field] = value;
  Person.findById(id).then(function(person) {
    person[field] = value;
    return person.save();
  }).then(function(person) {
    console.log(person.toJSON());
  }).catch(console.error).then(done);
};

Annotate solutions

It would likely be useful for the consultant delivering the lesson and the developers when they look back at the lesson to have an annotated solution branch given the new patterns.

For example: https://github.com/ga-wdi-boston/mongoose/blob/solution/app-people.js
(rough annotation below just an example and should be re-written)

// index should return all of the documents in a collection if no arguments are passed
// index should return all of the documents in a collection that match a specific criteria if arguments are passed 
const index = function() {
  let search = {}; // create an empty object that will be passed to .find()
  if (arguments[0] && arguments[1]) { // check if there are two arguments for the attribute name and value to search for
    let field = arguments[0]; // store the first argument (attribute name)
    let criterion = arguments[1]; // store the second argument (attribute value)
    if (criterion[0] === '/') { // if the criterion starts with / then it is regex
      let regex = new RegExp(criterion.slice(1, criterion.length - 1));
      search[field] = regex; // set search object property of field to have a value of regex
    } else {
      search[field] = criterion; // set search object property of field to have a value of criterion
    }
  }

  // at this point the search object 
  // could be an empty object if no arguments were passed {}
  // could be a key value pair {name: "Mike"}
  // could be a key value pair with regex {name: /Mike/}

  Person.find(search).then(function(people) { // find all people that have the matching field and criterion pair  or all people if no arguments were passed
    people.forEach(function(person) { // loop through all the people that matched the criteria
      console.log(person.toJSON()); // log each person as JSON
    });
  }).catch(console.error).then(done);
};

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.