A Better $patch Method for Angular's ngResource

Angular’s ngResource makes a pretty decent starting point for developing a front-end model layer, but tends to be a little simplistic as your backend API gets more sophisticated.

One issue I ran into recently is that the $patch method sends the entire object in the request body. This is usually not what you want to do, because the PATCH method is designed for a partial resource modification, typically using something like JSON Merge Patch or JSON Patch as the request body.

Here’s how to add a $patchFields method to your resource that creates a JSON Merge Patch for a set of fields, and then sends it to the backend.

This is how you’d use it:

var product = new Product({
   id: 231
   name: 'iPad',
   price: 429.99,
   size: {
      height: 9.4,
      width: 6.2,
      depth: 0.2
   }
});

product.price = 400;
product.size.height = 10;

var promise = product.$patchFields(['price', 'size.height']);

/*
 * Sends a request like this:
 * PATCH http://api.example.com/products/231
 * {
 *    "price": 400,
 *    "size": {
 *       "height": 10
 *    }
 * }
 *
 */

And here’s the code:

angular.module('myApp').factory('Product', function($resource) {

   var Product = $resource(
      'http://api.example.com/products/:id',
      { id: 'id' }
   );

   angular.extend(Product.prototype, {
      '$patchFields': function(fields, success, error) {
         var self = this;
         patch = self.generatePatch(fields);
         var result = Product.patch.call(
            this,
            { id: self.id },
            patch,
            success,
            error
         );
         return result.$promise || result;
      },
      generatePatch: function(fields) {
         var self = this;
         if(!angular.isArray(fields)) {
            fields = [ fields ];
         }

         return fields.reduce(function(result, fields) {
            setFieldByPath(result, field, self.getFieldByPath(field));
            return result;
         }, {});
      },
      getFieldByPath: function(path) {
         var ret = angular.toJson(json);
         var paths = path.split('.');
         for(var i = 0; i < paths.length; ++i) {
            if(angular.isUndefined(ret[paths[i]])) {
               return undefined;
            } else {
               ret = ret[paths[i]];
            }
         }
         return ret;
      }
   });

   function setFieldByPath(obj, path, value) {
      var paths = path.split('.');
      vat setOn = obj;

      for(var i = 0; i < paths.length - 1; i++) {
         var path = paths[i];
         if(!angular.isUndefined(setOn[path])) {
            if(
               angular.isObject(setOn[path]) &&
               !angular.isArray(setOn[path])
            ) {
               setOn = setOn[path];
            } else {
               throw new Error(
                  'Path ' +
                  path +
                  ' has an item that is not an object'
               );
            }
         } else {
            setOn[path] = {};
            setOn = setOn[path];
         }
      }

      if(!angular.isFunction(setOn[paths[paths.length - 1]])) {
         setOn[paths[paths.length - 1]] = value;
      } else {
         throw new Error(
            'Cannot set value at ' +
            path +
            ' since it would overwrite a function'
         );
      }
   }

   return Product;

});

Testing Angular $q Promises

This is a quick reference to testing Angular’s $q promises.

Values of promise.$$state.status
Value State
0 Outstanding
1 Resolved
2 Rejected

Using Jupyter for Exploring a Heroku Database

If you want to establish a connection to a Heroku database and import data into Jupyter for some analysis, here’s some boilerplate to help:

# Python 3
from sqlalchemy import create_engine
import pandas as pd
import subprocess

DATABASE_URL = subprocess.check_output("heroku config:get DATABASE_URL --app <your heroku app name here>", shell=True).decode('utf-8')
engine = create_engine(DATABASE_URL)

result = pd.read_sql_query(
    '''
SELECT * FROM my_table;
    ''',
    con=engine)

Now you have result, which is a Pandas DataFrame. Happy data exploration!

Note: It probably goes without saying this is a bad idea to do on a production system. You should use a read-only replica for running queries in production, or use a dump of the database loaded onto another server.

Draft.js Data Model Demo

Facebook released a very interesting React-based rich text editor called Draft.js. The advantage of something like is that you have much more control over the data model for structured documents.

The documentation is a little sparse on examples of what the underlying data model looks like, so I build this quick demo that renders the data model in JSON.

See it live here.

Getting Jetbrains DataGrip to work with Heroku Postgres

Heroku Postgres forces you to use SSL, but the connection isn’t signed with a well-known CA. To get this to work in DataGrip, you have to mess around with some JDBC settings. Here’s how to do it:

In the add connections dialog, enter the username, password, hostname, port, and database name. You’ll have to enter these manually, because DataGrip’s URL support only works with JDBC database URLs, not the more common Postgres URLs used by Heroku.

Next, click on the Advanced tab. Then find the ssl property and set it to true. Then find the sslfactory property and set it to org.postgresql.ssl.NonValidatingFactory.

You should be good to go!