Tuesday 12 February 2019

Migrating to ParseServer 3

Introduction

ParseServer 3 comes with a few breaking changes and requires a few type of changes.  I am documenting my experience of migrating my App.  Since we are all coders, I illustrate the code before and after migration in diff output format.

Approach

My current code base do not use backbone style callbacks.  I make extensive use of promises, both sequential and parallel (i.e Parse.Promise.when).  When I do migration, I keep the existing promises and therefore I do not use need to use async function.  These are the main changes:
  • Change Parse.Promise to native Promise
  • Parse.Promise.as to Promise.resolve
  • Parse.Promise.error to Promise.reject
  • Parse.Promise.when to Promise.all
  • Change cloud function
  • response.success("result") to return "result"
  • response.error("problem") to throw new Error("problem")
  • response.error(array) to throw array
  • Remove the redundant error handling block
    }, function(error) {
      response.error(error);
    });

Original

Parse.Cloud.define("getPlayer", function(request, response) {
  var player = request.params.player;

  // get session token for login user.  default to empty for non-login
  var sessionToken = {};
  if (request.user) {
    sessionToken = {sessionToken:request.user.getSessionToken()};
  }

  var p1 = exports.getPlayer(sessionToken, player);

  return p1.then(function(result) {
    response.success(result);
  },
  function(error) {
    response.error(error);
  });
});

exports.getPlayer = function(options, player)
{
  var query = new Parse.Query("Players");
  query.equalTo("name", player);
  return query.find(options);
}

Migrated

Parse.Cloud.define("getPlayer", (request) => {
  var player = request.params.player;

  // get session token for login user.  default to empty for non-login
  var sessionToken = {};
  if (request.user) {
    sessionToken = {sessionToken:request.user.getSessionToken()};
  }

  var p1 = exports.getPlayer(sessionToken, player);

  return p1.then(function(result) {
    return result;
  }); 
});

Diff output

-Parse.Cloud.define("getPlayer", function(request, response) {
+Parse.Cloud.define("getPlayer", (request) => {
   var player = request.params.player;
   // get session token for login user.  default to empty for non-login
@@ -935,37 +919,13 @@ Parse.Cloud.define("getPlayer", function(request, response) {
   var p1 = teamMod.getPlayerTeamElo(sessionToken, player);
   return p1.then(function(result) {  
-    response.success(result);
-  },
-  function(error) {
-    response.error(error);
+    return elo;
   });
 });

Cloud Function

Ordinary Cloud Function

Most of my cloud function are migrated with minor changes.  Example:

-Parse.Cloud.define("getGroup", function(request, response)
+Parse.Cloud.define("getGroup", (request) =>
 {
   if (!userMod.isLogon(request))
   {
-    response.error("Uh oh, you are not allowed to run this.");
-    return;
+    throw new Error("Uh oh, you are not allowed to run this.");
   }

   var sessionToken = userMod.getSessionToken(request);
@@ -1976,23 +1886,22 @@ Parse.Cloud.define("getGroup", function(request, response)
   var p1 = groupMod.getGroup(sessionToken,username);

   return p1.then(function(result) {
-    response.success(result);
-  }, function(error) {
-    response.error(error);
+    return groups;
   }); // p2 = p1.then

 }); // getGroup

Returning Array as Error

If you need to return array of PFObject as error.  You would need to use throw array instead of throw new Error(array).

This is the diff output.
-      response.error(matches);
+      // Note: Use throw array instead of throw new Error(array) to pass
+      //       array of PFObjects to caller.
+      //
+      // Result of throw array is
+      // error: [ ParseObject { _objCount: 6, className: 'Match', id: '12345678' },
+      //  ParseObject { _objCount: 9, className: 'Match', id: '22345678' },
+      //  ParseObject { _objCount: 11, className: 'Match', id: '32345678' },
+      //  ParseObject { _objCount: 13, className: 'Match', id: '4234567' },
+      //  ParseObject { _objCount: 15, className: 'Match', id: '52345678' } ]
+      //
+      // Result of throw new Error(array) is
+      // error: [object Object],[object Object],[object Object],[object Object],[object Object]
+      throw matches;

Triggers

Triggers are function such as beforeSave, afterSave, beforeDelete, afterDelete.  These functions are migrated in similar way as cloud functions.

-Parse.Cloud.beforeSave("Player", function(request, response) {
+Parse.Cloud.beforeSave("Player", (request) => {
   var player = request.object;

   // check if the object isNew (i.e have not been save before)
   // skip if the team record is not new
   if (!player.isNew()) {
     // no need to process and just return
-    response.success();
+    return;
   }

@@ -905,12 +892,13 @@ Parse.Cloud.beforeSave("Player", function(request, response) {

   return p1.then(function(status) {
     if (status && status.length > 0) {
-      response.success();
+      return;
     } else {
-      response.error("Player.beforeSave failed to save Player for " + game + "," + circle + ".");
+      throw new Error("Player.beforeSave failed to save Player for " + game + "," + circle + ".");
     }
   }); // p2 = p1.then

 }); // beforeSave Player

Reference


Wednesday 6 February 2019

Using Git Worktree, Branch and Merge on Multiple Heroku Environments

Introduction
Git worktree is a handy feature when a developer is working on major upgrade, such as between Swift upgrade or any major version upgrade that are breaking existing API.

Create a Staging Environment on Heroku
If you are hosting your app on Heroku, you can create a staging app using heroku CLI (command line interface).  The following two command create elostaging remote and rename the auto-created app name, salty-garden-12345 to elostaging.
$ heroku create --remote elostaging
$ heroku apps:rename elostaging --app salty-garden-12345

Create a Branch and Worktree
Organise your directory structure
Git stores it depository in local .git folder and therefore it allows you to restructure your working folder easily.  My original working folder is ~/code/elo.  To allow easier organization of main and branches.  I re-structure my app, elo, working folders to the followings structure.
~/code/elo/main
~/code/elo/branch

This can be done via 3 commands.
$ mv ~/code/elo main 
$ mkdir ~/code/elo
$ mv main ~/code/elo/

Create a Branch
$ cd ~/code/elo/main
$ git branch branch-4.0

Create a Worktree for the Branch
$ cd ~/code/elo/main
$ git worktree add ../branch-4.0 branch-4.0

Commit Changes to Branch
$ cd ~/code/elo/main
$ git add file1 file2
$ git commit -a "Updated"

Deploy from a Branch to Heroku Staging
You can deploy from a branch to staging with the following syntax.
$ cd ~/code/elo/branch-4.0
$ git push elostaging branch-4.0:master

Merge a Branch into Master
Suppose you are done with all works in the branch, you can merge the changes back to master using the following commands.  You will need to issue the merge command in the master worktree.

$ cd ~/code/elo/main
$ git merge branch-4.0
Updating xxxxxxa..yyyyyy2
Fast-forward
 file1 |  9 +++++++++
 file2 | 16 ++++++++++++++++
 2 files changed, 29 insertions(+)

Delete Worktree
You can delete a worktree by deleting the folder and run
$ cd ~/code/elo
$ rm -rf branch-4.0
$ cd ~/code/elo/main
$ git worktree prune

Merge Production Fixes in Master to Branch
While you are working on a branch, you also make a hot fix in the master.  You can bring the changes in master back into the branch so that you have less work to merge in the future.

$ cd ~/code/elo/branch-4.0
$ git merge master
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Auto-merging xxx/yyy.js
Auto-merging xxx/zzz.js
Automatic merge failed; fix conflicts and then commit the result.

In this case, merge the conflict in readme.txt manually.  Test and then commit the changes into branch4.0.

git add readme.txt
$ git commit -m "merged production hotfix"

References
These are useful references on worktree, heroku, git branching and merging.
  1. SaltyCrane git-worktree-notes
  2. Useful commands from Matts
  3. Git Branching and Merging Basic
  4. Managing Multiple Environments for Heroku App