Naive pairing

The simplest pairing system we can implement is to simply lookup if there is another user available without a pair whenever someone signs up.

In order to do so, we'll start a new collection and model: Meeting, which will be the base matching structure we'll be expanding on. The fundamental idea here is that each document will represent a meeting; either it's in the request phase, already set or occurred, finally will also store the feedback.

We'll be elaborating and defining the structure for it as it goes. For an initial implementation, let's run the scheduling logic right when the user decides to be matched. The strategy will be to look for a meeting document, where only the first user is set, and update it. In case there is no document like that, let's create a new one.

There are a couple of race conditions that might kick in, which we certainly want to avoid. These are as follows:

  • The user who's trying to find someone to schedule gets scheduled in the middle of the process.
  • The user who's available to be scheduled is selected but then reserved by someone else.

Lucky MongoDB offers the findAndModify() method, which can find and update on a single document automatically, while also returning the updated document. Keep in mind that it also offers an update() method to update multiple methods.

Let's get started with a new collection, Meeting, where we will keep track of a user's interest in finding a pair as well as keeping track of meetings as follows:

  1. This document will contain all of the user's info up to that point in time, so we can use it as a history, as well as use its contents to send e-mails and setup reviews.
  2. Let's see what the code looks like in src/models/meeting.js:
    '''javascript
      var arrangeTime = function() {
        var time = moment().add(1,'d'),
        time.hour(12);
        time.minute(0);
        time.second(0);
        return time.toDate();
      };
    
      methods.pairNaive = function(user, done) {
        /**
         * Try to find an unpaired User in Meeting collection,
         * at the same time, update with an pair id, it either:
         * 1. Add the new created user to  Meeting collection, or
         * 2. The newly created user was added to a Meeting document
         */
        Meeting.findAndModify({
          new: true,
          query: {
            user2: { $exists: false },
          },
          update: {
            $set: {
              user2: user,
              at: arrangeTime()
            }
          }
        }, function(err, newPair) {
          if (err) { return done(err) }
    
          if (newPair){
            return done(null, newPair);
          }
    
          // no user currently waiting for a match
          Meeting.insert({user1: user}, function(err,meeting) {
            done();
          })
        });
      };
    '''
  3. In the case of a successful pairing, user2 would be set in the Meeting object to meet the following day at noon, as you can see on the attribute at, which we set via the aux arrangeTime() function and the lightweight library moment.js (http://momentjs.com/). It's amazing to deal with dates in a super readable way. It is recommended that you take a look and become more familiar with it.

Also, notice new: true as a parameter. It ensures that MongoDB returns the updated version of the object, so we don't need to duplicate the logic in the app.

The new object Meeting needs to be created, as it carries the information of the users at that point in time and can be used to compose the emails/notification for both.

This is a good opportunity to define some basic structure for our tests that will follow a pattern of making several calls to the endpoints and asserting the response. There is a thorough explanation about the decisions to implement tests immediately, as shown in the following code:

'''
describe('Naive Meeting Setup', function() {
  // will go over each collection and remove all documents
  before(dbCleanup);

  var userRes1, userRes2;

  it("register user 1", function(done){
    var seed = Math.random()
    var user = {
      'name': 'Super'+seed,
      'email': 'supertest'+seed+'@example.com',
    }

    request(app)
      .post('/register')
      .send(user)
      .expect(200, function(err,res){
        userRes1 = res.body
        done(err)
      })
  });

  it('should be no meeting for one user', function(done) {
    models.Meeting.all(function(err,meetings) {
      expect(meetings).to.have.length(1);
      var meeting = meetings[0];
      expect(meeting.user1).to.be.an("object");
      expect(meeting.user2).to.be.an("undefined");
      done(err);
    });
  });

  it("register user 2", function(done){
    var seed = Math.random();
    var user = {
      'name': 'Super'+seed,
      'email': 'supertest'+seed+'@example.com',
    };

    request(app)
      .post('/register')
      .send(user)
      .expect(200, function(err,res){
        userRes2 = res.body
        done(err)
      });
  });

  it('should be a meeting setup with the 2 users', function(done) {
    models.Meeting.all(function(err,meetings) {
      expect(meetings).to.have.length(1)
      var meeting = meetings[0]
      expect(meeting.user1.email).to.equal(userRes1.email)
      expect(meeting.user2.email).to.equal(userRes2.email)
      done(err)
    });
  });
});
'''

(Source:git checkout e4fbf672d409482028de7c7427eab769ab0a20d2)

Naive pairing

Notes about tests

When using Mocha, a test is much like any javascript file and is executed as expected, allowing for any sort of regular Node.js require as you would do usually.

The describe() method is the context on which our tests execute; in our case, it's a full run of a certain functionality. The before() method will run once; in this case, our logic is set to clean up all of our MongoDB collections.

It stands for a simple expectation to be fulfilled. It will run in the same order it's declared, and we will try to make the assertions small and predictable as far as possible. Each of these functions defines steps and in this case, because we are doing end-to-end tests, we make requests to the API and check the results, and sometimes save it to variables that are used later to assert.

There are advices that say that tests shouldn't be dependent on the previous state, but those don't usually test the application flow, rather, individual portions of logic. For this particular test scenario, in case of a failure, it's important to interpret the error from the first it that fails; fixing it will likely fix errors after it. You can configure Mocha to stop at the first error by using the -b flag.

While testing, the most important point to make sense is that our test cases should make sure all of the expected cases are checked, and bad behaviors don't happen. We can never expect to predict everything that may go wrong of course, but it's still our duty to test as many points as we are certain about common issues.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.36.231