Posts Issued in July, 2014

Group Operators
$last | return the last value in a group
$first | return the first value in a group
$min | return the lowest value in a group
$avg | return an average of all values in a group
$addToSet | return a unique array of values for group
$max | return the highest value in a group
$push | return an array of values for a grouped field
$and | returns true when all values in array are true
$or | returns true when any value in its array are true
$not | returns boolean value that is opposite of input
$eq | return true if two values are equal
Comparison Operators
$cmp | return the result of a compare as an integer

Not sorted commands (in pdf link below grouped by terms)

help | get help for the context you are in
db | show the selected database
show dbs | show databases on server
show collections | show collections in the current db
use foo | select and use the database fooCollection commands (db.collection.)
mapReduce() | performs mapreduce
data aggregate
copyTo() | copy a collection to a new collection name
renameCollection() | rename a collection
drop() | remove the collection from the database
help() | show a list of help commands for a collection
command line options:host
hostname | hostname to connect toport
27017 | connect to port
27017 (default)u
foo | username foop
bar | password barauthenticationDatabase
arg | database
to authenticate to
mongotop | profile the resources mongo is consuming
mongostat | profile the amount of time spent in collections
mongoimport | import data from a file to mongodb
mongodump | dump contents of a database to files
mongorestore | restore contents of a dump to a database
exit | exit the shell
show users | show the users in the current db
getLastError() | get status of last error
stats() | get stats on the current db selected
help() | show a list of help commands for a dbIndexing
ensureIndex() | creates an index if it does not currently exist
reIndex() | rebuilds all existing indexes on a collection
getIndexes() | gets details on the indexes on a collection
dropIndex() | removes a specified index on a collection
compact | defragments a collection and rebuilds the indexesCursors (db.collection.find.)
limit() | constrain the size of a cursors result set
skip() | skip through some documents and then return results
sort() | return results ordered according to a sort specification
next() | return the next document in a cursor
count() | return a count of the documents in a cursor
toArray() | return an array of all documents for the cursor
explain() | get the query execution plan for a cursor
hint() | force db to use a specific index for a query
count() | get number of documents in the collection
stats() | get stats about the collection
hasNext() | true if cursor has documents and can be iterated
it | iterate on a cursorReplication (rs.)
add() | adds a member to a replica set
initiate() | initializes a new replica set
addArb() | adds an arbiter to a replica set
conf() | returns the replica set config document
freeze() | prevents a member from becoming primary
help() | get basic help for replica set functionsSharding (sh.)
addShard() | add a shard to the cluster
addShardTag() | associate a shard with a tagAuthentication
db.addUser() | add a user to system.users or admin collection
db.auth() | authenticates a user to a databaseProjection Operators ( $ )
$slice | limit number of elements projected from array
$elemMatch | project only the first element match
$ | project the first element in an array that matchesAggregation Operators ( $ )
  • Homework: Homework 4.1: queries that utilize index
  • Homework: Homework 4.2: explain index
  • Homework: Homework 4.3: add indexes to the blog
  • Homework: Homework 4.4: analyze a profile log

Homework: Homework 4.1

Suppose you have a collection with the following indexes:

> db.products.getIndexes()
[
    {
        "v" : 1,
        "key" : {
            "_id" : 1
        },
        "ns" : "store.products",
        "name" : "_id_"
    },
    {
        "v" : 1,
        "key" : {
            "sku" : 1
        },
                "unique" : true,
        "ns" : "store.products",
        "name" : "sku_1"
    },
    {
        "v" : 1,
        "key" : {
            "price" : -1
        },
        "ns" : "store.products",
        "name" : "price_-1"
    },
    {
        "v" : 1,
        "key" : {
            "description" : 1
        },
        "ns" : "store.products",
        "name" : "description_1"
    },
    {
        "v" : 1,
        "key" : {
            "category" : 1,
            "brand" : 1
        },
        "ns" : "store.products",
        "name" : "category_1_brand_1"
    },
    {
        "v" : 1,
        "key" : {
            "reviews.author" : 1
        },
        "ns" : "store.products",
        "name" : "reviews.author_1"
    }
  • List Item db.products.find({$and:[{price:{$gt:30}},{price:{$lt:50}}]}).sort({brand:1})
  • db.products.find({'brand':"GE"}).sort({price:1})

    Homework: Homework 4.2

    Suppose you have a collection called tweets whose documents contain information about the created_at time of the tweet and the user's followers_count at the time they issued the tweet. What can you infer from the following explain output?

db.tweets.find({"user.followers_count":{$gt:1000}}).sort({"created_at" : 1 }).limit(10).skip(5000).explain()
{
        "cursor" : "BtreeCursor created_at_-1 reverse",
        "isMultiKey" : false,
        "n" : 10,
        "nscannedObjects" : 46462,
        "nscanned" : 46462,
        "nscannedObjectsAllPlans" : 49763,
        "nscannedAllPlans" : 49763,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 205,
        "indexBounds" : {
                "created_at" : [
                        [
                                {
                                        "$minElement" : 1
                                },
                                {
                                        "$maxElement" : 1
                                }
                        ]
                ]
        },
        "server" : "localhost.localdomain:27017"
}
  • This query performs a collection scan.
  • The query uses an index to determine the order in which to return result documents.
  • The query visits 46462 documents.

    Homework: Homework 4.3

    Making the Blog fast

To get started, please download hw4.tar or hw4.zip and unpack file to your computer. Files for this homework should be included with your homework handout. This assignment requires Mongo 2.2 or above.

In this homework assignment you will be adding some indexes to the post collection to make the blog fast.

We have provided the full code for the blog application and you don't need to make any changes, or even run the blog. But you can, for fun.

We are also providing a patriotic (if you are an American) data set for the blog. There are 1000 entries with lots of comments and tags. You must load this dataset to complete the problem.

The blog has been enhanced so that it can also display the top 10 most recent posts by tag. There are hyperlinks from the post tags to the page that displays the 10 most recent blog entries for that tag. (run the blog and it will be obvious)

Your assignment is to make the following blog pages fast:

The blog home page
The page that displays blog posts by tag (http://localhost:3000/tag/whatever)
The page that displays a blog entry by permalink (http://localhost:3000/post/permalink)

By fast, we mean that indexes should be in place to satisfy these queries such that we only need to scan the number of documents we are going to return.

To figure out what queries you need to optimize, you can read the code in posts.js and see what queries it is doing to return the data needed for the relevant pages. Isolate those queries and use explain to explore.

Once you have added the indexes to make those pages fast, run the following commands to validate your project just like in previous homeworks. posts.js (indexes added)

/* The PostsDAO must be constructed with a connected database object */
function PostsDAO(db) {
    "use strict";
 
    /* If this constructor is called without the "new" operator, "this" points
     * to the global object. Log a warning and call it correctly. */
    if (false === (this instanceof PostsDAO)) {
        console.log('Warning: PostsDAO constructor called without "new" operator');
        return new PostsDAO(db);
    }
 
    var posts = db.collection("posts");
 
    this.insertEntry = function (title, body, tags, author, callback) {
        "use strict";
        console.log("inserting blog entry" + title + body);
 
        // fix up the permalink to not include whitespace
        var permalink = title.replace( /\s/g, '_' );
        permalink = permalink.replace( /\W/g, '' );
 
        // Build a new post
        var post = {"title": title,
                "author": author,
                "body": body,
                "permalink":permalink,
                "tags": tags,
                "comments": [],
                "date": new Date()}
 
        // now insert the post
        // hw3.2 TODO
        posts.insert(post, function (err, result) {
            "use strict";
 
            if (err) return callback(err, null);
 
            console.log("Inserted new post");
            callback(err, permalink);
        });
    }
 
    this.getPosts = function(num, callback) {
        "use strict";
 
        posts.find().sort('date', -1).limit(num).toArray(function(err, items) {
            "use strict";
 
            if (err) return callback(err, null);
 
            console.log("Found " + items.length + " posts");
 
            callback(err, items);
        });
    }
 
    this.getPostsByTag = function(tag, num, callback) {
        "use strict";
 
        posts.find({ tags : tag }).sort('date', -1).limit(num).toArray(function(err, items) {
            "use strict";
 
            if (err) return callback(err, null);
 
            console.log("Found " + items.length + " posts");
 
            callback(err, items);
        });
    }
 
    this.getPostByPermalink = function(permalink, callback) {
        "use strict";
        posts.findOne({'permalink': permalink}, function(err, post) {
            "use strict";
 
            if (err) return callback(err, null);
 
            callback(err, post);
        });
    }
 
    this.addComment = function(permalink, name, email, body, callback) {
        "use strict";
 
        var comment = {'author': name, 'body': body}
 
        if (email != "") {
            comment['email'] = email
        }
 
        // hw3.3 TODO
        posts.update({'permalink': permalink}, {'$push': {'comments': comment}}, function(err, numModified) {
            "use strict";
 
            if (err) return callback(err, null);
 
            callback(err, numModified);
        });
    }
}
 
module.exports.PostsDAO = PostsDAO;

#### Homework: Homework 4.4 #### In this problem you will analyze a profile log taken from a mongoDB instance. You should have included with your homework files a 'sysprofile.json' file. Import this file with the following command: Now query the profile data, looking for all queries to the students collection in the database school2, sorted in order of decreasing latency.

What is the latency of the longest running operation to the collection, in milliseconds?

db.profile.find({ns:/school2.students/}).sort({millis : -1}).limit(1).pretty()
  • Homework: Homework 3.1: remove the lowest homework score for each student
  • Homework: Homework 3.2: Making blog accept posts
  • Homework: Homework 3.3: Making blog accept comments

Homework: Homework 3.1

To get started, please download hw3.zip or hw3.tar and unpack file to your computer. You should have included with your homework files a "students.json" file. Import this file into your local Mongo instance with this command:

Write a program in Node.js that will remove the lowest homework score for each student. Since there is a single document for each student containing an array of scores, you will need to update the scores array and remove the homework.

Hint: One way to solve this is to find the lowest homework in code and then update the scores array with the low homework removed. If you are struggling with the Node.js side of this, look at the Array.splice method, which can remove elements from a Javascript Array. Note that you should not use the delete operator, because it will delete the element without resizing the array, leaving an empty space.

To confirm you are on the right track, here are some queries to run after you process the data with the correct answer shown:

Let us count the number of students we have:

use school db.students.count() 200

Let's see what Demarcus Audette's record looks like:

db.students.find({_id:100}).pretty() { "_id" : 100, "name" : "Demarcus Audette", "scores" : [ { "type" : "exam", "score" : 47.42608580155614 }, { "type" : "quiz", "score" : 44.83416623719906 }, { "type" : "homework", "score" : 39.01726616178844 } ] }

To verify that you have completed this task correctly, provide the identify of the student with the highest average in the class with following query that uses the aggregation framework. The answer will appear in the _id field of the resulting document.

db.students.aggregate({'$unwind':'$scores'},{'$group':{'_id':'$_id', 'average':{$avg:'$scores.score'}}}, {'$sort':{'average':-1}}, {'$limit':1})

Enter just the numeric value of the _id below.

var client = require('mongodb').MongoClient,
        _ = require('underscore');
 
// parameters:
//  arry - array of student scores
// returns: array with lowest homework score dropped
var dropLowestHomeworkScore = function(arry) {
    var minVal = Number.MAX_VALUE;
    var minIdx = -1;
    var newArray = [];
 
    for (i=0; i<arry.length; i++) {
        var val = arry[i]['score'];
        var type = arry[i]['type'];
        if (type === 'homework' && val < minVal) {
            minVal = val;
            minIdx = i;
        }
    }
 
    for (i=0; i<arry.length; i++) {
        if (i !== minIdx) newArray.push(arry[i]);
    }
 
    return newArray;
};
 
var db = client.connect('mongodb://localhost:27017/school', function(err,db) {
    if (err) throw err;
 
    var students = db.collection('students');
 
    students.find({}).toArray(function(err, docs) {
        if (err) throw err;
 
        // update each doc by removing lowest score 
        _.each(docs, function(doc) {
            doc.scores = dropLowestHomeworkScore(doc.scores);
            students.update({'_id':doc._id}, doc, {}, function(err, result) {
                if (err) throw err;
            });
        });
 
        db.close()
    });
});

Homework: Homework 3.2

Making your blog accept posts

In this homework you will be enhancing the blog project to insert entries into the posts collection. After this, the blog will have the basic functionality. It will allow you to add blog posts with a title, body and tags and have it be added to the posts collection properly.

We have provided the code that creates users and allows you to login (the assignment from last week). Files for this homework and HW 3.3 should be included with your homework handout.

We have removed parts of the code that uses the Node.js driver to query MongoDB from posts.js and marked the area where you need to work for hw3.2 with "hw3.2 TODO". You should not need to touch any other code. The database call that you are going to add will insert a new post into the posts collection. Here is an example of valid blog post:

> db.posts.find().pretty()
{
"_id" : ObjectId("513d396da0ee6e58987bae74"),
"title" : "Martians to use MongoDB",
"author" : "andrew",
"body" : "Representatives from the planet Mars announced today that the planet would adopt MongoDB as a planetary standard. Head Martian Flipblip said that MongoDB was the perfect tool to store the diversity of life that exists on Mars.",
"permalink" : "martians_to_use_mongodb",
"tags" : [
"martians",
"seti",
"nosql",
"worlddomination"
],
"comments" : [ ],
"date" : ISODate("2013-03-11T01:54:53.692Z")
}

To play with the blog you can navigate to the following URLs:

http://localhost:3000/ http://localhost:3000/signup http://localhost:3000/login http://localhost:3000/newpost

#### Homework: Homework 3.3 #### Making your blog accept comments

In this homework you will add code to your blog so that it accepts comments. You will be using the same code as you downloaded for HW 3.2.

We have removed parts of the code that uses the Node.js driver to query MongoDB from posts.js and marked the area where you need to work for HW 3.3 with "hw3.3 TODO".

You should not need to touch any other code. The database call that you are going to add will add a new comment to a given post.

This assignment has fairly little code, but it's a little more subtle than the previous assignment because you are going to be manipulating an array within the Mongo document. For the sake of clarity, here is a document out of the posts collection from a working project that also has comments.

{
"_id" : ObjectId("513d396da0ee6e58987bae74"),
"author" : "andrew",
"body" : "Representatives from the planet Mars announced today that the planet would adopt MongoDB as a planetary standard. Head Martian Flipblip said that MongoDB was the perfect tool to store the diversity of life that exists on Mars.",
"comments" : [
{
"author" : "Larry Ellison",
"body" : "While I am deeply disappointed that Mars won't be standardizing on a relational database, I understand their desire to adopt a more modern technology for the red planet.",
"email" : "larry@oracle.com"
},
{
"author" : "Salvatore Sanfilippo",
"body" : "This make no sense to me. Redis would have worked fine."
}
],
"date" : ISODate("2013-03-11T01:54:53.692Z"),
"permalink" : "martians_to_use_mongodb",
"tags" : [
"martians",
"seti",
"nosql",
"worlddomination"
],
"title" : "Martians to use MongoDB"
}

Note that you add comments in this blog from the blog post detail page, which appears at

http://localhost:3000/post/post_slug

where post_slug is the permalink. For the sake of eliminating doubt, the permalink for the example blog post above is

http://localhost:3000/post/martians_to_use_mongodb

You will run hw3-3_validate.js to check your work, much like the last homework. hw3-3_validate.js will run through and check to make sure it can add blog comments, as required by this problem. This hw3-3_validate.js program will print out a 3.3 validation code that you should enter below.

POSTS.js - both 3.2 and 3.3 assignments

/* The PostsDAO must be constructed with a connected database object */
function PostsDAO(db) {
    "use strict";
 
    /* If this constructor is called without the "new" operator, "this" points
     * to the global object. Log a warning and call it correctly. */
    if (false === (this instanceof PostsDAO)) {
        console.log('Warning: PostsDAO constructor called without "new" operator');
        return new PostsDAO(db);
    }
 
    var posts = db.collection("posts");
 
    this.insertEntry = function (title, body, tags, author, callback) {
        "use strict";
        console.log("inserting blog entry" + title + body);
 
        // fix up the permalink to not include whitespace
        var permalink = title.replace( /\s/g, '_' );
        permalink = permalink.replace( /\W/g, '' );
 
        // Build a new post
        var post = {"title": title,
                "author": author,
                "body": body,
                "permalink":permalink,
                "tags": tags,
                "comments": [],
                "date": new Date()}
 
        // now insert the post
        // hw3.2 TODO
          posts.insert(post, function(err, result) {
                if (err) {
                    callback(err, null);
                } else {
                    callback(null, post.permalink);
                }
          });
    }
 
    this.getPosts = function(num, callback) {
        "use strict";
 
        posts.find().sort('date', -1).limit(num).toArray(function(err, items) {
            "use strict";
 
            if (err) return callback(err, null);
 
            console.log("Found " + items.length + " posts");
 
            callback(err, items);
        });
    }
 
    this.getPostsByTag = function(tag, num, callback) {
        "use strict";
 
        posts.find({ tags : tag }).sort('date', -1).limit(num).toArray(function(err, items) {
            "use strict";
 
            if (err) return callback(err, null);
 
            console.log("Found " + items.length + " posts");
 
            callback(err, items);
        });
    }
 
    this.getPostByPermalink = function(permalink, callback) {
        "use strict";
        posts.findOne({'permalink': permalink}, function(err, post) {
            "use strict";
 
            if (err) return callback(err, null);
 
            callback(err, post);
        });
    }
 
    this.addComment = function(permalink, name, email, body, callback) {
        "use strict";
 
        var comment = {'author': name, 'body': body}
 
        if (email != "") {
            comment['email'] = email
        }
 
        // hw3.3 TODO
          posts.findOne({permalink:permalink}, function(err, doc) {
                if (err) {
                    callback(err, null);
                } else {
                    if (doc) {
                        posts.update({_id:doc._id}, {$push: {comments:comment}}, function(err) {
                            if (err) {
                                callback(err, null);
                            } else {
                                callback(null, 1);
                            }
                        });
                    } else {
                        callback(null, 0);
                    }
                }
          });
    }
}
 
module.exports.PostsDAO = PostsDAO;
  • find, find cursors, find lt & gt, find projection, find And Modify, findOne
  • import JSON, insert Array, insert Doc
  • Regex selector
  • remove
  • save
  • skip Limit Sort
  • update, update Single, update Multi
  • upsert

find

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { 'grade' : 100 };
 
    db.collection('grades').find(query).toArray(function(err, docs) {
        if(err) throw err;
 
        console.dir(docs);
 
        db.close();
    });
});

find cursors

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { 'grade' : 100 };
 
    var cursor = db.collection('grades').find(query);
 
    cursor.each(function(err, doc) {
        if(err) throw err;
 
        if(doc == null) {
            return db.close();
        }
 
        console.dir(doc.student + " got a good grade!");
    });
});

find lt and gt

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { 'student' : 'Joe', 'grade' : { '$gt' : 80, '$lt' : 95 } };
 
    db.collection('grades').find(query).each(function(err, doc) {
        if(err) throw err;
 
        if(doc == null) {
            return db.close();
        }
 
        console.dir(doc);
    });
});

find projection

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { 'grade' : 100 };
 
    var projection = { 'student' : 1, '_id' : 0 };
 
    db.collection('grades').find(query, projection).toArray(function(err, docs) {
        if(err) throw err;
 
        docs.forEach(function (doc) {
            console.dir(doc);
            console.dir(doc.student + " got a good grade!");
        });
 
        db.close();
    });
});

findAndModify

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { 'name' : 'comments' };
    var sort = [];
    var operator = { '$inc' : { 'counter' : 1 } };
    var options = { 'new' : true };
 
    db.collection('counters').findAndModify(query, sort, operator, options, function(err, doc) {
        if(err) throw err;
 
        if (!doc) {
            console.log("No counter found for comments.");
        }
        else {
            console.log("Number of comments: " + doc.counter);
        }
 
        return db.close();
    });
});

findOne

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { 'grade' : 100 };
 
    function callback(err, doc) {
        if(err) throw err;
 
        console.dir(doc);
 
        db.close();
    }
 
    /* START STUDENT CODE */
    db.collection('grades').findOne(query, callback);
    /* END STUDENT CODE */
});

import JSON

var MongoClient = require('mongodb').MongoClient
  , request = require('request');
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    request('http://www.reddit.com/r/technology/.json', function (error, response, body) {
        if (!error && response.statusCode == 200) {
            var obj = JSON.parse(body);
 
            var stories = obj.data.children.map(function (story) { return story.data; });
 
            db.collection('reddit').insert(stories, function (err, data) {
                    if(err) throw err;
 
                    console.dir(data);
 
                    db.close();
            });
        }
    });
});

insert Array

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var docs = [ { 'student' : 'Calvin', 'age' : 6 },
                 { 'student' : 'Susie', 'age' : 7 } ];
 
    db.collection('students').insert(docs, function(err, inserted) {
        if(err) throw err;
 
        console.dir("Successfully inserted: " + JSON.stringify(inserted));
 
        return db.close();
    });
});

insert Doc

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var doc = { '_id' : 'calvin', 'age' : 6 };
 
    db.collection('students').insert(doc, function(err, inserted) {
        if(err) throw err;
 
        console.dir("Successfully inserted: " + JSON.stringify(inserted));
 
        return db.close();
    });
});

regex Selector

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { 'title' : { '$regex' : 'Microsoft' } };
 
    var projection = { 'title' : 1, '_id' : 0 };
 
    db.collection('reddit').find(query, projection).each(function(err, doc) {
        if(err) throw err;
 
        if(doc == null) {
            return db.close();
        }
 
        console.dir(doc.title);
    });
});

remove

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { 'assignment' : 'hw3' };
 
    db.collection('grades').remove(query, function(err, removed) {
        if(err) throw err;
 
        console.dir("Successfully updated " + removed + " documents!");
 
        return db.close();
    });
});

save

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { 'assignment' : 'hw2' };
 
    db.collection('grades').findOne(query, function(err, doc) {
        if(err) throw err;
 
        doc['date_returned'] = new Date();
 
        db.collection('grades').save(doc, function(err, saved) {
            if(err) throw err;
 
            console.dir("Successfully saved " + saved + " document!");
 
            return db.close();
        });
    });
});

skip Limit Sort

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var grades = db.collection('grades');
 
    var cursor = grades.find({});
    cursor.skip(1);
    cursor.limit(4);
    cursor.sort('grade', 1);
    //cursor.sort([['grade', 1], ['student', -1]]);
 
    //var options = { 'skip' : 1,
    //                'limit' : 4,
    //                'sort' : [['grade', 1], ['student', -1]] };
    //var cursor = grades.find({}, {}, options);
 
    cursor.each(function(err, doc) {
        if(err) throw err;
        if(doc == null) {
            return db.close();
        }
        console.dir(doc);
    });
});

update

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { 'assignment' : 'hw1' };
    var operator = { 'assignment' : 'hw2', '$set' : { 'date_graded' : new Date() } };
 
    db.collection('grades').update(query, operator, function(err, updated) {
        if(err) throw err;
 
        console.dir("Successfully updated " + updated + " document!");
 
        return db.close();
    });
});

update Single

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { 'assignment' : 'hw1' };
 
    db.collection('grades').findOne(query, function(err, doc) {
        if(err) throw err;
        if(!doc) {
            console.log('No documents for assignment ' + query.assignment + ' found!');
            return db.close();
        }
 
        query['_id'] = doc['_id'];
        doc['date_returned'] = new Date();
 
        db.collection('grades').update(query, doc, function(err, updated) {
            if(err) throw err;
 
            console.dir("Successfully updated " + updated + " document!");
 
            return db.close();
        });
    });
});

update Multi

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { };
    var operator = { '$unset' : { 'date_returned' : '' } };
    var options = { 'multi' : true };
 
    db.collection('grades').update(query, operator, options, function(err, updated) {
        if(err) throw err;
 
        console.dir("Successfully updated " + updated + " documents!");
 
        return db.close();
    });
});

upsert

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
    if(err) throw err;
 
    var query = { 'student' : 'Frank', 'assignment' : 'hw1' };
    //var operator = { 'student' : 'Frank', 'assignment' : 'hw1', 'grade' : 100 };
    var operator = { '$set' : { 'date_returned' : new Date(), 'grade' : 100 } };
    var options = { 'upsert' : true };
 
    db.collection('grades').update(query, operator, options, function(err, upserted) {
        if(err) throw err;
 
        console.dir("Successfully upserted " + upserted + " document!");
 
        return db.close();
    });
});

Homework 2.1

In this problem, you will be using an old weather dataset. You should have included with your homework files a "weather_data.csv" file. This is a comma separated value file that you can import into MongoDB as follows: mongoimport --type csv --headerline weather_data.csv -d weather -c data

You can verify that you've imported the data correctly by running the following commands in the mongo shell:

use weather db.data.find().count() 2963

Reading clockwise from true north, the wind direction is measured by degrees around the compass up to 360 degrees. 90 is East 180 is South 270 is West 360 is North

Your assignment is to figure out the "State" that recorded the lowest "Temperature" when the wind was coming from the west ("Wind Direction" between 180 and 360). Please enter the name of the state that meets this requirement. Do not include the surrounding quotes in providing your answer.

Query: db.data.find({ "Wind Direction" : { "$gt" : 180, "$lt" : 360 } }}.sort({ "Temperature" : 1}).limit(1).pretty()

Homework: Homework 2.2

Write a program that finds the document with the highest recorded temperature for each state, and adds a "month_high" field for that document, setting its value to true. Use the weather dataset that you imported in HW 2.1.

This is a sample document from the weather data collection:

use weather switched to db weather db.data.findOne() { "_id" : ObjectId("520bea012ab230549e749cff"), "Day" : 1, "Time" : 54, "State" : "Vermont", "Airport" : "BTV", "Temperature" : 39, "Humidity" : 57, "Wind Speed" : 6, "Wind Direction" : 170, "Station Pressure" : 29.6, "Sea Level Pressure" : 150 }

Assuming this document has the highest "Temperature" for the "State" of "Vermont" in our collection, the document should look like this after you run your program:

db.data.findOne({ "_id" : ObjectId("520bea012ab230549e749cff") }) { "_id" : ObjectId("520bea012ab230549e749cff"), "Day" : 1, "Time" : 54, "State" : "Vermont", "Airport" : "BTV", "Temperature" : 39, "Humidity" : 57, "Wind Speed" : 6, "Wind Direction" : 170, "Station Pressure" : 29.6, "Sea Level Pressure" : 150, "month_high" : true }

Note that this is only an example and not the actual document that you would be updating. Note also that our collection only has one month of data for each "State", which is why we are asking you to set "month_high".

Hint: If you select all the weather documents, you can sort first by state, then by temperature. Then you can iterate through the documents and know that whenever the state changes you have reached the highest temperature for that state.

If you got it right, it will provide a validation code for you to enter into the box below. Enter just the code, no spaces. If you fail validation or have messed up your collection beyond repair, run the following to drop the weather database so you can reimport the data and try again:

highTemp.js

var client = require('mongodb').MongoClient;
 
client.connect('mongodb://localhost:27017/weather', function(err, db) {
    if (err) throw err;
 
    var query = {};
    var projection = {'State':1, 'Temperature':1};
 
    var cursor = db.collection('data').find(query, projection);
 
    // Sort by state and then by temperature (decreasing)
    cursor.sort([['State',1], ['Temperature',-1]]);
 
    var state = ''; // initialize to dummy value
    var operator = {'$set':{'month_high':true}};
 
    cursor.each(function(err, doc) {
        if (err) throw err;
 
        if (doc == null) {
            return db.close();
        } else if (doc.State !== state) {
            // first record for each state is the high temp one
            state = doc.State;
 
            db.collection('data').update({'_id':doc._id}, operator, function(err, updated) {
                if (err) throw err;
            });
        }
    });
});

Homework: Homework 2.3

Blog User Sign-up and Login

If you have not already done so download the homework files for this week.

You should see four files at the highest level: app.js, users.js, posts.js and sessions.js. There is also a views directory which contains the templates for the project and a routes directory which contains our express routes.

If everything is working properly, you should be able to start the blog by typing:

npm install node app.js

Note that this requires Node.js to be correctly installed on your computer. After you run the blog, you should see the message:

Express server listening on port 3000

If you goto http://localhost:3000 you should see the front page of the blog. Here are some URLs that must work when you are done.

http://localhost:3000/signup http://localhost:3000/login http://localhost:3000/logout

When you login or sign-up, the blog will redirect to http://localhost:3000/welcome and that must work properly, welcoming the user by username.

We have removed parts of the code that uses the Node.js driver to query MongoDB from users.js and marked the area where you need to work with "TODO: hw2.3". You should not need to touch any other code. The database calls that you are going to add will add a new user upon sign-up and validate a login by retrieving the right user document.

The blog stores its data in the blog database in two collections, users and sessions. Here are two example docs for a username ‘sverch’ with password ‘salty’. You can insert these if you like, but you don’t need to.

use blog switched to db blog db.users.find() { "_id" : "sverch", "password" : "$2a$10$wl4bNB/5CqwWx4bB66PoQ.lmYvxUHigM1ehljyWQBupen3uCcldoW" } db.sessions.find() { "username" : "sverch", "_id" : "8d25917b27e4dc170d32491c6247aabba7598533" }

Once you have the the project working, the following steps should work:

go to http://localhost:3000/signup
create a user

It should redirect you to the welcome page and say: welcome username, where username is the user you signed up with. Now:

Goto http://localhost:3000/logout
Now login http://localhost:3000/login

Ok, now it’s time to validate you got it all working.

users.js

var bcrypt = require('bcrypt-nodejs');
 
/* The UsersDAO must be constructed with a connected database object */
function UsersDAO(db) {
    "use strict";
 
    /* If this constructor is called without the "new" operator, "this" points
     * to the global object. Log a warning and call it correctly. */
    if (false === (this instanceof UsersDAO)) {
        console.log('Warning: UsersDAO constructor called without "new" operator');
        return new UsersDAO(db);
    }
 
    var users = db.collection("users");
 
    this.addUser = function(username, password, email, callback) {
        "use strict";
 
        // Generate password hash
        var salt = bcrypt.genSaltSync();
        var password_hash = bcrypt.hashSync(password, salt);
 
        // Create user document
        var user = {'_id': username, 'password': password_hash};
 
        // Add email if set
        if (email != "") {
            user['email'] = email;
        }
 
          users.insert(user, function(err, inserted) {
                if (err) {
                    callback(err, null);
            } else {
                callback(null, inserted[0]);
            }
          });
    }
 
    this.validateLogin = function(username, password, callback) {
        "use strict";
 
        // Callback to pass to MongoDB that validates a user document
        function validateUserDoc(err, user) {
            "use strict";
 
            if (err) return callback(err, null);
 
            if (user) {
                if (bcrypt.compareSync(password, user.password)) {
                    callback(null, user);
                }
                else {
                    var invalid_password_error = new Error("Invalid password");
                    // Set an extra field so we can distinguish this from a db error
                    invalid_password_error.invalid_password = true;
                    callback(invalid_password_error, null);
                }
            }
            else {
                var no_such_user_error = new Error("User: " + user + " does not exist");
                // Set an extra field so we can distinguish this from a db error
                no_such_user_error.no_such_user = true;
                callback(no_such_user_error, null);
            }
        }
 
        users.findOne({'_id':username}, function(err, user) {
              callback(err,user);
          });
    }
}
 
module.exports.UsersDAO = UsersDAO;
  • Async vs sync
  • Hello Express
  • Hello world http
  • Hello world MongoDB
  • Hello world Swig
  • Express: handling POST requests

Async vs sync

# Installation
npm install mongodb
# Run mongo shell example
mongo script.js
# Run node.js example
node app.js
//app.js file 
var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
 
    if(err) throw err;
 
    // Find one document in our collection
    db.collection('coll').findOne({}, function(err, doc) {
 
        // Print the result
        console.dir(doc);
 
        // Close the DB
        db.close();
    });
 
    // Declare success
    console.dir("Called findOne!");
});
 
//script.js file
// Find one document in our collection
var doc = db.coll.findOne();
// Print the result
printjson(doc);

Hello Express

/app.js
var express = require('express')
  , app = express() // Web framework to handle routing requests
  , cons = require('consolidate'); // Templating library adapter for Express
 
app.engine('html', cons.swig);
app.set('view engine', 'html');
app.set('views', __dirname + '/views');
app.use(app.router);
 
// Handler for internal server errors
function errorHandler(err, req, res, next) {
    console.error(err.message);
    console.error(err.stack);
    res.status(500);
    res.render('error_template', { error: err });
}
 
app.use(errorHandler);
 
app.get('/:name', function(req, res, next) {
    var name = req.params.name;
    var getvar1 = req.query.getvar1;
    var getvar2 = req.query.getvar2;
    res.render('hello', { name : name, getvar1 : getvar1, getvar2 : getvar2 });
});
 
app.listen(3000);
console.log('Express server listening on port 3000');
<h1>Hello, {{name}}, here are your GET variables:</h1>
<ul>
    <li>{{getvar1}}</li>
    <li>{{getvar2}}</li>
</ul>
var express = require('express'),
    app = express();
 
app.get('/', function(req, res){
    res.send('Hello World');
});
 
app.get('*', function(req, res){
    res.send('Page Not Found', 404);
});
 
app.listen(8080);
console.log('Express server started on port 8080');

Hello world http

// Source: howtonode.org/hello-node
 
// Load the http module to create an http server.
var http = require('http');
 
// Configure our HTTP server to respond with Hello World to all requests.
var server = http.createServer(function (request, response) {
  //response.writeHead(200, {"Content-Type": "text/plain"});
  //response.end("Hello World\n");
  response.send("Hello World\n");
});
 
// Listen on port 8000, IP defaults to 127.0.0.1
server.listen(8000);
 
// Put a friendly message on the terminal
console.log("Server running at http://127.0.0.1:8000/");

Hello world MongoDB

var MongoClient = require('mongodb').MongoClient;
//var Server = require('mongodb').Server;
 
// Examples of two different ways to connect to mongodb
//var mongoclient = new MongoClient(new Server("localhost", 27017, {native_parser: true}));
 
//mongoclient.open(function(err, mongoclient) {
//    console.log("Open!");
//});
//
 
// Open the connection to the server
MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
//MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {
 
    if(err) throw err;
 
    // Find one document in our collection
    db.collection('coll').findOne({}, function(err, doc) {
 
        if(err) throw err;
 
        // Print the result. Will print a null if there are no documents in the db.
        console.dir(doc);
 
        // Close the DB
        db.close();
    });
 
    // Declare success
    console.dir("Called findOne!");
});

Hello world Swig

var express = require('express'),
    app = express(),
    cons = require('consolidate'); // Templating library adapter for Express
 
app.engine('html', cons.swig);
app.set('view engine', 'html');
app.set('views', __dirname + '/views');
 
app.get('/', function(req, res){
    res.render('hello', { name : 'World' });
});
 
app.get('*', function(req, res){
    res.send('Page Not Found', 404);
});
 
app.listen(8080);
console.log('Express server started on port 8080');

hello.html

<h1>Hello, {{name}}!</h1>

Express: handling POST requests

var express = require('express')
  , app = express()
  , cons = require('consolidate');
 
app.engine('html', cons.swig);
app.set('view engine', 'html');
app.set('views', __dirname + '/views');
app.use(express.bodyParser());
app.use(app.router);
 
// Handler for internal server errors
function errorHandler(err, req, res, next) {
    console.error(err.message);
    console.error(err.stack);
    res.status(500);
    res.render('error_template', { error: err });
}
 
app.use(errorHandler);
 
app.get('/', function(req, res, next) {
    res.render('fruitPicker', { 'fruits' : [ 'apple', 'orange', 'banana', 'peach' ] });
});
 
app.post('/favorite_fruit', function(req, res, next) {
    var favorite = req.body.fruit;
    if (typeof favorite == 'undefined') {
        next(Error('Please choose a fruit!'));
    }
    else {
        res.send("Your favorite fruit is " + favorite);
    }
});
 
app.listen(3000);
console.log('Express server listening on port 3000');

error_template.html

<h1>Error: {{error}}</h1>

fruitPicker.html

<html>
  <head><title>Fruit Picker</title></head>
  <body>
     <form action="/favorite_fruit" method="POST">
        <p>What is your favorite fruit?</p>
        {% for fruit in fruits %}
            <input type="radio" name="fruit" value="{{fruit}}">{{fruit}}</input>
                <br/>
        {% endfor %}
        <p><input type="submit" value="Submit"/></p>
     </form>
  </body>
</html>
  • create replica set
  • connect ot replica set
  • failover
  • read concern
  • write concern
  • Homework: Homework 6.4: sharding

create replica set

//init_replica.js
config = { _id: "m101", members:[
          { _id : 0, host : "jomac.local:27017"},
          { _id : 1, host : "jomac.local:27018"},
          { _id : 2, host : "jomac.local:27019"} ]
};
 
rs.initiate(config);
rs.status();
 
//wait.js
import pymongo
 
read_pref = pymongo.read_preferences.ReadPreference.SECONDARY
 
c = pymongo.MongoClient(host="mongodb://localhost:37017",
                        replicaSet="s0",
                        w=4, j=True, 
                        read_preference=read_pref)
 
db = c.m101
people = db.people
 
print "inserting"
people.insert({"name":"Andrew Erlichson", "favorite_color":"blue"})
print "inserting"
people.insert({"name":"Richard Krueter", "favorite_color":"red"})
print "inserting"
people.insert({"name":"Dwight Merriman", "favorite_color":"green"})

connect ot replica set

//app.js
var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect("mongodb://localhost:30001,localhost:30002,localhost:30003/course", function(err, db) {
    if (err) throw err;
 
    db.collection("repl").insert({ 'x' : 1 }, function(err, doc) {
        if (err) throw err;
 
        db.collection("repl").findOne({ 'x' : 1 }, function(err, doc) {
            if (err) throw err;
 
            console.log(doc);
            db.close();
        });
    });
});

failover

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect("mongodb://localhost:30001,localhost:30002,localhost:30003/course", function(err, db) {
    if (err) throw err;
 
    var documentNumber = 0;
    function insertDocument() {
 
        db.collection("repl").insert({ 'documentNumber' : documentNumber++ }, function(err, doc) {
            if (err) throw err;
            console.log(doc);
        });
 
        console.log("Dispatched insert");
        setTimeout(insertDocument, 1000);
    }
 
    insertDocument();
});

read concern

var MongoClient = require('mongodb').MongoClient;
    ReadPreference = require('mongodb').ReadPreference;
 
MongoClient.connect("mongodb://localhost:30001,localhost:30002,localhost:30003/course?readPreference=secondary", function(err, db) {
    if (err) throw err;
 
    db.collection("repl").insert({ 'x' : 1 }, function(err, doc) {
        if (err) throw err;
        console.log(doc);
    });
 
    function findDocument() {
 
        //db.collection("repl").findOne({ 'x' : 1 }, { 'readPreference' : ReadPreference.PRIMARY }, function(err, doc) {
        db.collection("repl").findOne({ 'x' : 1 }, function(err, doc) {
            if (err) throw err;
            console.log(doc);
        });
 
        console.log("Dispatched find");
        setTimeout(findDocument, 1000);
    }
 
    findDocument();
});

write concern

var MongoClient = require('mongodb').MongoClient;
 
MongoClient.connect("mongodb://localhost:27017,localhost:27018,localhost:27019/course?w=1", function(err, db) {
    if (err) throw err;
 
    // Write concern of one
    db.collection("repl").insert({ 'x' : 1 }, function(err, doc) {
        if (err) throw err;
        console.log(doc);
 
        // Write concern of two
        db.collection("repl").insert({ 'x' : 2 }, { 'w' : 2 }, function(err, doc) {
            if (err) throw err;
            console.log(doc);
            db.close();
        });
 
        // Write concern of four - callback won't be called indefinitely with a write concern greater
          // than the number of nodes in the replica set.
        db.collection("repl").insert({ 'x' : 2 }, { 'w' : 4 }, function(err, doc) {
            if (err) throw err;
            console.log(doc);
            db.close();
        });
    });
});

Homework: Homework 6.4: sharding

#### You have a sharded system with three shards and have sharded the collections "grades" in the "test" database across those shards. The output of sh.status() when connected to mongos looks like this:

mongos> sh.status()
--- Sharding Status --- 
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
    {  "_id" : "s0",  "host" : "s0/localhost:37017,localhost:37018,localhost:37019" }
    {  "_id" : "s1",  "host" : "s1/localhost:47017,localhost:47018,localhost:47019" }
    {  "_id" : "s2",  "host" : "s2/localhost:57017,localhost:57018,localhost:57019" }
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "test",  "partitioned" : true,  "primary" : "s0" }
        test.grades chunks:
                s1  4
                s0  4
                s2  4
            { "student_id" : { $minKey : 1 } } -->> { "student_id" : 0 } on : s1 Timestamp(12000, 0) 
            { "student_id" : 0 } -->> { "student_id" : 2640 } on : s0 Timestamp(11000, 1) 
            { "student_id" : 2640 } -->> { "student_id" : 91918 } on : s1 Timestamp(10000, 1) 
            { "student_id" : 91918 } -->> { "student_id" : 176201 } on : s0 Timestamp(4000, 2) 
            { "student_id" : 176201 } -->> { "student_id" : 256639 } on : s2 Timestamp(12000, 1) 
            { "student_id" : 256639 } -->> { "student_id" : 344351 } on : s2 Timestamp(6000, 2) 
            { "student_id" : 344351 } -->> { "student_id" : 424983 } on : s0 Timestamp(7000, 2) 
            { "student_id" : 424983 } -->> { "student_id" : 509266 } on : s1 Timestamp(8000, 2) 
            { "student_id" : 509266 } -->> { "student_id" : 596849 } on : s1 Timestamp(9000, 2) 
            { "student_id" : 596849 } -->> { "student_id" : 772260 } on : s0 Timestamp(10000, 2) 
            { "student_id" : 772260 } -->> { "student_id" : 945802 } on : s2 Timestamp(11000, 2) 
            { "student_id" : 945802 } -->> { "student_id" : { $maxKey : 1 } } on : s2 Timestamp(11000, 3)

If you ran the query use test db.grades.find({'student_id':530289}) Which shards would be involved in answering the query? s1

  • Homework: Homework 5.1: Finding the most frequent author of comments
  • Homework: Homework 5.2: Crunching the Zipcode dataset
  • Homework: Homework 5.3: analyze dataset of student grades
  • Homework: Homework 5.4: Removing Rural Residents

Homework: Homework 5.1

Finding the most frequent author of comments on your blog In this assignment you will use the aggregation framework to find the most frequent author of comments on your blog. We will be using the same dataset as last week.

Start by downloading the posts.json dataset from last week's homework, found in hw4.tar or hw4.zip. Then import into your blog database as follows:

mongoimport -d blog -c posts --drop < posts.json

To avoid any problems with this homework, please reload the posts.json data using the command above even though there is probably still a version of it in your database.

Now use the aggregation framework to calculate the author with the greatest number of comments.

To help you verify your work before submitting, the author with the least comments is Efrain Claw and he commented 384 times.

Homework: Homework 5.2

Crunching the Zipcode dataset Please download the zips.json dataset and import it into a collection of your choice.

Please calculate the average population of cities in California (abbreviation CA) and New York (NY) (taken together) with populations over 25,000.

For this problem, assume that a city name that appears in more than one state represents two separate cities.

Please round the answer to a whole number. Hint: The answer for CT and NJ is 49749.

Homework: Homework 5.3

Who's the easiest grader on campus? In this problem you will be analyzing a dataset of student grades. Please import grades_5-3.json into a database and collection of your choice.

The documents look like this:

{
    "_id" : ObjectId("50b59cd75bed76f46522c392"),
    "student_id" : 10,
    "class_id" : 5,
    "scores" : [
        {
            "type" : "exam",
            "score" : 69.17634380939022
        },
        {
            "type" : "quiz",
            "score" : 61.20182926719762
        },
        {
            "type" : "homework",
            "score" : 73.3293624199466
        },
        {
            "type" : "homework",
            "score" : 15.206314042622903
        },
        {
            "type" : "homework",
            "score" : 36.75297723087603
        },
        {
            "type" : "homework",
            "score" : 64.42913107330241
        }
    ]
}

There are documents for each student (student_id) across a variety of classes (class_id). Note that not all students in the same class have the same exact number of assessments. Some students have three homework assignments, etc.

Your task is to calculate the class with the best average student performance. This involves calculating an average for each student in each class of all non-quiz assessments and then averaging those numbers to get a class average. To be clear, each student's average includes only exams and homework grades. Don't include their quiz scores in the calculation.

What is the class_id which has the highest average student perfomance?

Hint/Strategy: You need to group twice to solve this problem. You must figure out the GPA that each student has achieved in a class and then average those numbers to get a class average. After that, you just need to sort. The hardest class is class_id=2. Those students achieved a class average of 37.6

Below, choose the class_id with the highest average student average.

Homework: Homework 5.4

Removing Rural Residents In this problem you will calculate the number of people who live in a zip code in the US where the city starts with a digit. We will take that to mean they don't really live in a city. Once again, you will be using the zip code collection you imported earlier.

The project operator can extract the first digit from any field. For example, to extract the first digit from the city field, you could write this query:

db.zips.aggregate([
    {$project: 
     {
    first_char: {$substr : ["$city",0,1]},
     }   
   }
])

Using the aggregation framework, calculate the sum total of people who are living in a zip code where the city starts with a digit. Choose the answer below.

Note that you will need to probably change your projection to send more info through than just that first character. Also, you will need a filtering step to get rid of all documents where the city does not start with a digital (0-9).

5.1: db.posts.aggregate([{$unwind: "$comments"},{$group: {_id:"$comments.author", num_comments: {$sum:1}}},{$sort:{num_comments:-1}},{$limit:1}])
 
5.2: db.zips.aggregate([{$match: {state: {$in: ['CA','NY']}}},{$group: {_id: {state:"$state",city:"$city"}, pop: {$sum: "$pop"}}},{$match: {pop: {$gt:25000}}},{$group: {_id:null, avg_pop:{$avg:"$pop"}}}])
 
5.3: db.grades.aggregate([{$unwind: "$scores"},{$match: {"scores.type": {$nin: ["quiz"]}}},{$group: {_id: {class:"$class_id", student:"$student_id"}, avg_student_score: {$avg: "$scores.score"}}},{$group: {_id: "$_id.class", class_avg: {$avg: "$avg_student_score"}}},{$sort: {class_avg:-1}},{$limit:1}])
 
5.4: db.zips.aggregate([{$project: {_id:0, zipcode: "$_id", first_char: {$substr: ["$city",0,1]}, pop: "$pop"}},{$match: {first_char: {$gte: "0", $lte: "9"}}},{$group: {_id:null, total_pop: {$sum: "$pop"}}}])

Start a series of posts how to code apps. First ever app with AngularJS and Yii was very simple. We'll talk about a few topics. Demos find here Simple Single Page app with a table of users - u can browse, edit, delete, see details - without reloading the page ;) Folder with all oof the front (AngularJS files) looked like this (In images folder glyphicons.png files located):

Pix with folders

First, create a view like that:

<?php
//task.php
$url = Yii::app()->request->baseUrl;
     echo '<div ng-app="ngdemoApp">
      <ul class="menu">
        <li><a href="#/dummy">dummy</a></li>
        <li><a href="#/user-list">user-list</a></li>
        <li><a href="#/user-detail/1">user-detail/1</a></li>
        <li><a href="#/user-creation">user-creation</a></li>
      </ul>
 
      <div class="container" ng-view=""></div>
      <script src="'.$url.'/front/tableUser/scripts/app.js"></script>
      <script src="'.$url.'/front/tableUser/scripts/services/services.js"></script>
      <script src="'.$url.'/front/tableUser/scripts/controllers/controllers.js"></script>
 
    </div>';
 
    ?>

In VIEWS folder 4 views located:

<!--dummy.html-->
<div>
    <p>
        Result from Angular Controller is: {{ bla }}
    </p>
    <p>
        Result from REST Web Service is: {{ foo }}
    </p>
    <p>
      The result from REST Web Service should be: <b>JonFromREST</b>
    </p>
    <p>
      If the result from REST Web Service is empty: Check that the backend REST Web Service is running and returning the expected result.
    </p>
    <ol>
      <li>in modern browsers just open a new tab and enter <i>http://localhost:8080/ngdemo/web/dummy</i> <b>or</b></li>
      <li>run the script <i>../test_backend.sh</i>, <b>or</b></li>
      <li>execute curl directly from the command line:<i>curl -i -X GET -H 'Content-Type: application/json' http://localhost:8080/ngdemo/web/dummy </i><b>or</b></li>
    </ol>
    <p>Maybe you just forgot to start the backend REST Web Service? <i>../start_backend_java_backend.sh</i></p>
    All of the above results should contain:
    <div>
      <b>{"id":0,"firstName":"JonFromREST","lastName":"DoeFromREST"}</b>
    </div>
</div>
 
<!--user-detail.html-->
<div class="container">
  <h1>User detail</h1>
 
  <form novalidate="novalidate" class="form-horizontal">
    <div class="control-group">
      <label class="control-label" for="inputFirstName">First name:</label>
 
      <div class="controls">
        <input type="text" id="inputFirstName" ng-model="user.firstName"/>
      </div>
    </div>
    <div class="control-group">
      <label class="control-label" for="inputLastName">Last name:</label>
 
      <div class="controls">
        <input type="text" id="inputLastName" ng-model="user.lastName"/>
      </div>
    </div>
    <div class="control-group">
      <div class="controls">
        <a ng-click="cancel()" class="btn btn-small">cancel</a>
        <a ng-click="updateUser()" class="btn btn-small btn-primary">update user</a>
      </div>
    </div>
  </form>
</div>
 
<!--user-creation.html-->
<div class="container">
  <h1>Create a new user</h1>
 
  <form novalidate="novalidate" class="form-horizontal">
    <div class="control-group">
      <label class="control-label" for="inputFirstName">First name:</label>
 
      <div class="controls">
        <input type="text" id="inputFirstName" ng-model="user.firstName" placeholder="First name"/>
      </div>
    </div>
    <div class="control-group">
      <label class="control-label" for="inputLastName">Last name:</label>
 
      <div class="controls">
        <input type="text" id="inputLastName" ng-model="user.lastName" placeholder="Last name"/>
      </div>
    </div>
    <div class="control-group">
      <label class="control-label" for="inputPass">Password:</label>
 
      <div class="controls">
        <input type="text" id="inputPass" ng-model="user.pass" placeholder="password"/>
      </div>
    </div>
    <div class="control-group">
      <div class="controls">
        <a ng-click="createNewUser()" class="btn btn-small btn-primary">create new user</a>
      </div>
    </div>
  </form>
</div>
 
<!--user-list.html-->
<div class="span6">
  <table class="table table-striped table-condensed">
    <thead>
      <tr>
        <th style="min-width: 80px;">First name</th>
        <th style="min-width: 80px;">Last name</th>
        <th style="width:20px;">&nbsp;</th>
        <th style="width:20px;">&nbsp;</th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="user in users">
        <td>{{ user.firstName }}</td>
        <td>{{ user.lastName }}</td>
        <td><a ng-click="editUser(user.id)" class="btn btn-small btn-primary">edit</a></td>
        <td><a ng-click="deleteUser(user.id)" class="btn btn-small btn-danger">delete</a></td>
      </tr>
    </tbody>
  </table>
  <a ng-click="createNewUser()" class="btn btn-small">create new user</a>
</div>

In SCRIPTS folder we got 3 js files:

//app.js - main file - ROUTES defined
'use strict';
 
angular.module('ngdemoApp', [
  'ngRoute',
  'ngdemoApp.services',
  'ngdemoApp.controllers'
  ])
.config(function ($routeProvider, $httpProvider) {
    var baseUrl = "http://localhost:8084/yiiangular/";
  $routeProvider.when('/dummy', {templateUrl: baseUrl + 'front/tableUser/views/dummy.html', controller: 'DummyCtrl'});
  $routeProvider.when('/user-list', {templateUrl: baseUrl + 'front/tableUser/views/user-list.html', controller: 'UserListCtrl'});
  $routeProvider.when('/user-detail/:id', {templateUrl: baseUrl + 'front/tableUser/views/user-detail.html', controller: 'UserDetailCtrl'});
  $routeProvider.when('/user-creation', {templateUrl: baseUrl + 'front/tableUser/views/user-creation.html', controller: 'UserCreationCtrl'});
  $routeProvider.otherwise({redirectTo: '/user-list'});
 
  /* CORS... */
  /* http://stackoverflow.com/questions/17289195/angularjs-post-data-to-external-rest-api */
  $httpProvider.defaults.useXDomain = true;
  delete $httpProvider.defaults.headers.common['X-Requested-With'];
});
 
//controllers.js
'use strict';
 
/* Controllers */
 
var app = angular.module('ngdemoApp.controllers', []);
 
 
// Clear browser cache (in development mode)
//
// http://stackoverflow.com/questions/14718826/angularjs-disable-partial-caching-on-dev-machine
app.run(function ($rootScope, $templateCache) {
  $rootScope.$on('$viewContentLoaded', function () {
    $templateCache.removeAll();
  });
});
 
 
app.controller('DummyCtrl', ['$scope', 'DummyFactory', function ($scope, DummyFactory) {
  $scope.bla = 'тестируем отдачу кириллицй';
  DummyFactory.query({}, function (data) {
    $scope.foo = data.firstName;
  })
}]);
 
app.controller('UserListCtrl', ['$scope', 'UsersFactory', 'UserFactory', '$location',
  function ($scope, UsersFactory, UserFactory, $location) {
 
    /* callback for ng-click 'editUser': */
    $scope.editUser = function (userId) {
      $location.path('/user-detail/' + userId);
    };
 
    /* callback for ng-click 'deleteUser': */
    $scope.deleteUser = function (userId) {
      UserFactory.delete({ id: userId });
      $scope.users = UsersFactory.query();
    };
 
    /* callback for ng-click 'createUser': */
    $scope.createNewUser = function () {
      $location.path('/user-creation');
    };
 
 
    $scope.users = UsersFactory.query();
    var usersList  = UsersFactory.query();
  }]);
 
app.controller('UserDetailCtrl', ['$scope', '$routeParams', 'UserFactory', '$location',
  function ($scope, $routeParams, UserFactory, $location) {
 
    /* callback for ng-click 'updateUser': */
    $scope.updateUser = function () {
      UserFactory.update($scope.user);
      $location.path('/user-list');
    };
 
    /* callback for ng-click 'cancel': */
    $scope.cancel = function () {
      $location.path('/user-list');
    };
 
    $scope.user = UserFactory.show({id: $routeParams.id});
  }]);
 
app.controller('UserCreationCtrl', ['$scope', 'UsersFactory', '$location',
  function ($scope, UsersFactory, $location) {
 
    /* callback for ng-click 'createNewUser': */
    $scope.createNewUser = function () {
      UsersFactory.create($scope.user);
      $location.path('/user-list');
    }
  }]);
 
//services.js - FACTORY defined
'use strict';
 
var services = angular.module('ngdemoApp.services', ['ngResource']);
 
//var baseUrl = 'http://localhost:8084/yiiangular/index.php';
var baseUrl = "http://localhost:8084/yiiangular/index.php/api/";
 
services.factory('DummyFactory', function ($resource) {
    return $resource('../views/dummy',
 
                     {},
                      {
        query: { method: 'GET', params: {} }
    })
});
 
services.factory('UsersFactory', function ($resource) {
    //return $resource(baseUrl + 'user',
    var usersList = $resource(baseUrl + 'user',
                     {}, {
        query: { method: 'GET', isArray: true },
        create: { method: 'POST' }
    });
     var usersList2 = usersList.query(function() {});
    return usersList;
 
});
 
services.factory('UserFactory', function ($resource) {
    return $resource(baseUrl + 'user/:id',
 
                     {}, {
        show: { method: 'GET' },
        update: { method: 'PUT', params: {id: '@id'} },
        delete: { method: 'DELETE', params: {id: '@id'} }
    })
});

Start a series of posts how to code apps. First ever app with AngularJS and Yii was very simple. We'll talk about a few topics. Demos find here Simple Single Page app with lots of data about Racing. I didnot write backend for it, because formatting JSON used in the app wasn't as simple.U can only browse list and details - without reloading the page ;) In AngularJS files u might find where the docs are downloaded. Folder with all oof the front (AngularJS files) looked like this (In images folder glyphicons.png files located):

Pix with folders

Let's start with a view in php:

<p>
example JSON doc {"MRData":{"xmlns":"http:\/\/ergast.com\/mrd\/1.2","series":"f1","url":"http://ergast.com/api/f1/2013/drivers/raikkonen/driverstandings.json","limit":"30","offset":"0","total":"1","StandingsTable":{"season":"2013","driverId":"raikkonen","StandingsLists":[{"season":"2013","round":"19","DriverStandings":[{"position":"5","positionText":"5","points":"183","wins":"1","Driver":{"driverId":"raikkonen","permanentNumber":"7","code":"RAI","url":"http:\/\/en.wikipedia.org\/wiki\/Kimi_R%C3%A4ikk%C3%B6nen","givenName":"Kimi","familyName":"Räikkönen","dateOfBirth":"1979-10-17","nationality":"Finnish"},              "Constructors":[{"constructorId":"lotus_f1","url":"http:\/\/en.wikipedia.org\/wiki\/Lotus_F1","name":"Lotus F1","nationality":"British"}]}]}]}}}<br/>
   </p>
<?php
//TEXT in origin todo._id & todo.text CHANGE to id &descr
$url = Yii::app()->request->baseUrl;
//<script src="'.$url.'/front/f1feeder/js/core.js">
//  <link rel="stylesheet" type="text/css" href="'.$url.'/front/f1feeder/css/app.css" />
echo '<div ng-app="F1FeederApp">
  <ng-view></ng-view>
 
  <script src="'.$url.'/front/f1feeder/js/app.js"></script>
  <script src="'.$url.'/front/f1feeder/js/services.js"></script>
  <script src="'.$url.'/front/f1feeder/js/controllers.js"></script>
</div>';

Next. let's discover html partials:

<!--drivers.html - list of drivers available -->
<input type="text" ng-model="nameFilter" placeholder="Search..."/>
<table>
<thead>
  <tr><th colspan="4">Drivers Championship Standings</th></tr>
</thead>
<tbody>
  <tr ng-repeat="driver in driversList | filter: searchFilter">
    <td>{{$index + 1}}</td>
    <td>
      <a href="#/drivers/{{driver.Driver.driverId}}">
        {{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}}
      </a>
    </td>
    <td>{{driver.Constructors[0].name}}</td>
    <td>{{driver.points}}</td>
  </tr>
</tbody>
</table>
 
<!--driver.html - details about the driver -->

And, at last, the JS folder:

//app.js
angular.module('F1FeederApp', [
  'F1FeederApp.services',
  'F1FeederApp.controllers',
  'ngRoute'
]).
config(['$routeProvider', function($routeProvider) {
    var baseUrl = "http://localhost:8084/yiiangular/";
  $routeProvider.
    when("/drivers", {templateUrl: baseUrl + "front/f1feeder/partials/drivers.html", controller: "driversController"}).
    when("/drivers/:id", {templateUrl: baseUrl + "front/f1feeder/partials/driver.html", controller: "driverController"}).
    otherwise({redirectTo: '/drivers'});
}]);
 
//controllers.js
angular.module('F1FeederApp.controllers', []).
 
  /* Drivers controller */
  controller('driversController', function($scope, ergastAPIservice) {
    $scope.nameFilter = null;
    $scope.driversList = [];
    $scope.searchFilter = function (driver) {
        var re = new RegExp($scope.nameFilter, 'i');
        return !$scope.nameFilter || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName);
    };
 
    ergastAPIservice.getDrivers().success(function (response) {
        //Digging into the response to get the relevant data
        $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings;
    });
  }).
 
  controller('driverController', function($scope, $routeParams, ergastAPIservice) {
    $scope.id = $routeParams.id;
    $scope.races = [];
    $scope.driver = null;
 
    ergastAPIservice.getDriverDetails($scope.id).success(function (response) {
        $scope.driver = response.MRData.StandingsTable.StandingsLists[0].DriverStandings[0]; 
    });
 
    ergastAPIservice.getDriverRaces($scope.id).success(function (response) {
        $scope.races = response.MRData.RaceTable.Races; 
    }); 
  });
 
//directives.js
'use strict';
angular.module('myApp.directives', []).
  directive('appVersion', ['version', function(version) {
    return function(scope, elm, attrs) {
      elm.text(version);
    };
  }]);
 
//filters.js
'use strict';
angular.module('myApp.filters', []).
  filter('interpolate', ['version', function(version) {
    return function(text) {
      return String(text).replace(/\%VERSION\%/mg, version);
    };
  }]);
 
//services.js
angular.module('F1FeederApp.services', [])
  .factory('ergastAPIservice', function($http) {
 
    var ergastAPI = {};
 
    ergastAPI.getDrivers = function() {
      return $http({
        method: 'JSONP', 
        url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK'
      });
    }
 
    ergastAPI.getDriverDetails = function(id) {
      return $http({
        method: 'JSONP', 
        url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/driverStandings.json?callback=JSON_CALLBACK'
      });
    }
 
    ergastAPI.getDriverRaces = function(id) {
      return $http({
        method: 'JSONP', 
        url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/results.json?callback=JSON_CALLBACK'
      });
    }
 
    return ergastAPI;
  });

Start a series of posts how to code apps. First ever app with AngularJS and Yii was very simple. We'll talk about a few topics. Demos find here Simple Single Page app with list of todos - u can browse, add and delete without reloading the page ;) Folder with all oof the front (AngularJS files) looked like this:

Pix with folders

This article doesn't proveide info on creating yii REST - it's reaally simple. Acting the Usual way - gen app, models and CRUD. I had the next view:

<p>API test :id - http://proserge.kh.ua/yiiangular/index.php/api/todo/1<br />
    API test all - http://localhost:8084/yiiangular/index.php/api/todo/<br />
    JSON doc - {
                            "id":"1",
                            "descr":"Task #0-1",
                            "active":"1"
                            }
   </p>
<?php
//TEXT in origin todo._id & todo.text CHANGE to id &descr
$url = Yii::app()->request->baseUrl;
//<link rel="stylesheet" type="text/css" href="'.$url.'/front/NodeToDo/common/todo.css" />
echo '<script src="'.$url.'/front/NodeToDo/js/core.js"></script>
 
    <script src="'.$url.'/front/NodeToDo/js/controllers/main.js"></script>
    <script src="'.$url.'/front/NodeToDo/js/services/todos.js"></script> 
    <!-- ASSIGN OUR ANGULAR MODULE -->
    <div ng-app="scotchTodo" class="html-ex">
 
    <!-- SET THE CONTROLLER AND GET ALL TODOS WITH INITIALIZE FUNCTION -->
    <div class="body-ex" ng-controller="mainController" ng-init="initialize()">
        <div class="container">
 
            <!-- HEADER AND TODO COUNT -->
            <div class="jumbotron text-center">
                <h1>I\'m a Todo-aholic <span class="label label-info">{{ todos.length }}</span></h1>
            </div>
 
            <!-- TODO LIST -->
            <div id="todo-list" class="row">
                <div class="col-sm-4 col-sm-offset-4">
 
                    <!-- LOOP OVER THE TODOS IN $scope.todos -->
                    <div class="checkbox" ng-repeat="todo in todos">
                        <label>
                            <input type="checkbox" ng-click="deleteTodo(todo.id)"> {{ todo.descr }}
                        </label>
                    </div>
 
                </div>
            </div>
 
            <!-- FORM TO CREATE TODOS -->
            <div id="todo-form" class="row">
                <div class="col-sm-8 col-sm-offset-2 text-center">
                    <form>
                        <div class="form-group">
 
                            <!-- BIND THIS VALUE TO formData.text IN ANGULAR -->
                            <input type="text" class="form-control input-lg text-center" placeholder="I want to buy a puppy that will love me forever" ng-model="formData.descr">
                        </div>
 
                        <!-- createToDo() WILL CREATE NEW TODOS -->
                        <button type="submit" class="btn btn-primary btn-lg" ng-click="createTodo()">Add</button>
                    </form>
                </div>
            </div>
 
            <div class="text-center text-muted">
                <p>A demo by <a href="#">Me</a>.</p>
                <p>Read the <a href="http://scotch.io/tutorials/javascript/creating-a-single-page-todo-app-with-node-and-angular">tutorial</a>.</p>
            </div>
 
        </div>
 
    </div>
    </div>';

That's all on the back. I separated Back & Front to different folders. core.js:

var scotchTodo = angular.module('scotchTodo', []);
 
function mainController($scope, $http) {
    $scope.formData = {};
 
    // when landing on the page, get all todos and show them
    $scope.initialize = function() {
        $http.get('/api/todos')
            .success(function(data) {
                $scope.todos = data;
            })
            .error(function(data) {
                console.log('Error: ' + data);
            });
    };
 
    // when submitting the add form, send the text to the node API
    $scope.createTodo = function() {
        $http.post('/api/todos', $scope.formData)
            .success(function(data) {
                $('input').val('');
                $scope.todos = data;
            })
            .error(function(data) {
                console.log('Error: ' + data);
            });
    };
 
    // delete a todo after checking it
    $scope.deleteTodo = function(id) {
        $http.delete('/api/todos/' + id)
            .success(function(data) {
                $scope.todos = data;
            })
            .error(function(data) {
                console.log('Error: ' + data);
            });
    };
 
}

AngularJS controller file - main.js

angular.module('todoController', [])
 
    // inject the Todo service factory into our controller
    .controller('mainController', function($scope, $http, Todos) {
        $scope.formData = {};
        $scope.loading = true;
 
        // GET =====================================================================
        // when landing on the page, get all todos and show them
        // use the service to get all the todos
        Todos.get()
            .success(function(data) {
                $scope.todos = data;
                $scope.loading = false;
            });
 
        // CREATE ==================================================================
        // when submitting the add form, send the text to the node API
        $scope.createTodo = function() {
            $scope.loading = true;
 
            // validate the formData to make sure that something is there
            // if form is empty, nothing will happen
            if ($scope.formData.descr != undefined) {
 
                // call the create function from our service (returns a promise object)
                $scope.formData.active = 1;
                Todos.create($scope.formData)
 
                    // if successful creation, call our get function to get all the new todos
                    .success(function(data) {
                        $scope.loading = false;
                        $scope.formData = {}; // clear the form so our user is ready to enter another
                        //$scope.todos = data; // assign our new list of todos
                        $scope.todos.push((data));
                    });
            }
        };
 
        // DELETE ==================================================================
        // delete a todo after checking it
        $scope.deleteTodo = function(id) {
            $scope.loading = true;
 
            Todos.delete(id)
                // if successful creation, call our get function to get all the new todos
                .success(function(data) {
                    var id = (data)["id"];
                    for (var i = 0; i < $scope.todos.length; i++) {
                        if ($scope.todos[i]['id'] == id ){$scope.todos.splice(i, 1); break;}
                    }
                    //$scope.todos = data; // assign our new list of todos
                    $scope.loading = false;
                });
        };
    });

AngularJS routes - todos.js

angular.module('todoService', [])
 
    // super simple service
    // each function returns a promise object 
    .factory('Todos', function($http) {
        var baseUrl = "http://localhost:8084/yiiangular/index.php/api/";
        return {
            get : function() {
                return $http.get(baseUrl + 'todo/');
            },
            create : function(todoData) {
                return $http.post(baseUrl + 'todo', todoData);
            },
            delete : function(id) {
                return $http.delete(baseUrl + 'todo/' + id);
            }
        }
    });

Go to page: