Repository: parse-community/Parse-SDK-JS
Pull request #544 (2018-01-16 05:52:08)

Adds Purge & Polygon to Parse.Schema
This adds the purge method to ParseSchema. This allows the deletion of all objects without having to run through and query all objects for removal. This was added to ParseSchema as the related delete method requires the class to be first empty of objects.

Polygon type support has also been added to ParseSchema, both via a supported method to add a polygon type field and as a recognized field when asserting types.

codecov[bot]: # [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/544?src=pr&el=h1) Report
> Merging [#544](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/544?src=pr&el=desc) into [master](https://codecov.io/gh/parse-community/Parse-SDK-JS/commit/7ef1b0aa10d570a391c33efb131d94a8486bf6fc?src=pr&el=desc) will **increase** coverage by `0.04%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/544/graphs/tree.svg?width=650&height=150&token=8nCENM0QHj&src=pr)](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/544?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #544      +/-   ##
==========================================
+ Coverage   84.62%   84.66%   +0.04%     
==========================================
  Files          48       48              
  Lines        3960     3971      +11     
  Branches      900      900              
==========================================
+ Hits         3351     3362      +11     
  Misses        609      609
```


| [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/544?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/ParseSchema.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/544/diff?src=pr&el=tree#diff-c3JjL1BhcnNlU2NoZW1hLmpz) | `100% <100%> (ø)` | :arrow_up: |
| [src/ParseError.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/544/diff?src=pr&el=tree#diff-c3JjL1BhcnNlRXJyb3IuanM=) | `100% <100%> (ø)` | :arrow_up: |
| [src/CoreManager.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/544/diff?src=pr&el=tree#diff-c3JjL0NvcmVNYW5hZ2VyLmpz) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/544?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/544?src=pr&el=footer). Last update [7ef1b0a...45df22c](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/544?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

dplewis: I wish I could merge this :)

Patch:
diff --git a/src/CoreManager.js b/src/CoreManager.js
index 997f9cf..0d4a66b 100644
--- a/src/CoreManager.js
+++ b/src/CoreManager.js
@@ -78,6 +78,7 @@ type RESTController = {
   ajax: (method: string, url: string, data: any, headers?: any) => ParsePromise;
 };
 type SchemaController = {
+  purge: (className: string) => ParsePromise;
   get: (className: string, options: RequestOptions) => ParsePromise;
   delete: (className: string, options: RequestOptions) => ParsePromise;
   create: (className: string, params: any, options: RequestOptions) => ParsePromise;
@@ -295,7 +296,7 @@ module.exports = {
   },
 
   setSchemaController(controller: SchemaController) {
-    requireMethods('SchemaController', ['get', 'create', 'update', 'delete', 'send'], controller);
+    requireMethods('SchemaController', ['get', 'create', 'update', 'delete', 'send', 'purge'], controller);
     config['SchemaController'] = controller;
   },
 
diff --git a/src/ParseError.js b/src/ParseError.js
index e03499e..88d069e 100644
--- a/src/ParseError.js
+++ b/src/ParseError.js
@@ -20,7 +20,7 @@ class ParseError {
     this.code = code;
     this.message = message;
   }
-  
+
   toString() {
     return 'ParseError: ' + this.code + ' ' + this.message;
   }
@@ -466,6 +466,14 @@ ParseError.INVALID_LINKED_SESSION = 251;
  */
 ParseError.UNSUPPORTED_SERVICE = 252;
 
+/**
+ * Error code indicating an invalid operation occured on schema
+ * @property INVALID_SCHEMA_OPERATION
+ * @static
+ * @final
+ */
+ParseError.INVALID_SCHEMA_OPERATION = 255;
+
 /**
  * Error code indicating that there were multiple errors. Aggregate errors
  * have an "errors" property, which is an array of error objects with more
diff --git a/src/ParseSchema.js b/src/ParseSchema.js
index 4eb8199..4577616 100644
--- a/src/ParseSchema.js
+++ b/src/ParseSchema.js
@@ -14,7 +14,7 @@ import ParsePromise from './ParsePromise';
 
 import type { RequestOptions, FullOptions } from './RESTController';
 
-const FIELD_TYPES = ['String', 'Number', 'Boolean', 'Date', 'File', 'GeoPoint', 'Array', 'Object', 'Pointer', 'Relation'];
+const FIELD_TYPES = ['String', 'Number', 'Boolean', 'Date', 'File', 'GeoPoint', 'Polygon', 'Array', 'Object', 'Pointer', 'Relation'];
 
 /**
  * A Parse.Schema object is for handling schema data from Parse.
@@ -181,6 +181,7 @@ class ParseSchema {
 
   /**
    * Removing a Schema from Parse
+   * Can only be used on Schema without objects
    *
    * @param {Object} options A Backbone-style options object.
    * Valid options are:<ul>
@@ -207,6 +208,34 @@ class ParseSchema {
       })._thenRunCallbacks(options);
   }
 
+  /**
+   * Removes all objects from a Schema (class) in Parse.
+   * EXERCISE CAUTION, running this will delete all objects for this schema and cannot be reversed
+   *
+   * @param {Object} options A Backbone-style options object.
+   * Valid options are:<ul>
+   *   <li>success: A Backbone-style success callback
+   *   <li>error: An Backbone-style error callback.
+   *   <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
+   *     be used for this request.
+   *   <li>sessionToken: A valid session token, used for making a request on
+   *       behalf of a specific user.
+   * </ul>
+   *
+   * @return {Parse.Promise} A promise that is resolved with the result when
+   * the query completes.
+   */
+  purge(options: FullOptions) {
+    this.assertClassName();
+
+    const controller = CoreManager.getSchemaController();
+
+    return controller.purge(this.className)
+      .then((response) => {
+        return response;
+      })._thenRunCallbacks(options);
+  }
+
   /**
    * Assert if ClassName has been filled
    * @private
@@ -319,6 +348,16 @@ class ParseSchema {
     return this.addField(name, 'GeoPoint');
   }
 
+  /**
+   * Adding Polygon Field
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addPolygon(name: string) {
+    return this.addField(name, 'Polygon');
+  }
+
   /**
    * Adding Array Field
    *
@@ -437,6 +476,16 @@ const DefaultController = {
 
   delete(className: string, options: RequestOptions): ParsePromise {
     return this.send(className, 'DELETE', {}, options);
+  },
+
+  purge(className: string): ParsePromise {
+    const RESTController = CoreManager.getRESTController();
+    return RESTController.request(
+      'DELETE',
+      `purge/${className}`,
+      {},
+      { useMasterKey: true }
+    );
   }
 };
 


Test patch:
diff --git a/integration/test/ParseSchemaTest.js b/integration/test/ParseSchemaTest.js
index 91dfbff..cd50331 100644
--- a/integration/test/ParseSchemaTest.js
+++ b/integration/test/ParseSchemaTest.js
@@ -47,6 +47,7 @@ describe('Schema', () => {
       .addDate('dateField')
       .addFile('fileField')
       .addGeoPoint('geoPointField')
+      .addPolygon('polygonField')
       .addArray('arrayField')
       .addObject('objectField')
       .addPointer('pointerField', '_User')
@@ -62,6 +63,7 @@ describe('Schema', () => {
       assert.equal(result.fields.dateField.type, 'Date');
       assert.equal(result.fields.fileField.type, 'File');
       assert.equal(result.fields.geoPointField.type, 'GeoPoint');
+      assert.equal(result.fields.polygonField.type, 'Polygon');
       assert.equal(result.fields.arrayField.type, 'Array');
       assert.equal(result.fields.objectField.type, 'Object');
       assert.equal(result.fields.pointerField.type, 'Pointer');
@@ -132,6 +134,32 @@ describe('Schema', () => {
     });
   });
 
+  it('purge', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    const obj = new Parse.Object('SchemaTest');
+    obj.save().then(() => {
+        return testSchema.delete().then(() => {
+          // Should never reach here
+          assert.equal(true, false);
+        }).catch((error) => {
+          assert.equal(error.code, Parse.Error.INVALID_SCHEMA_OPERATION);
+          assert.equal(error.message, 'Class SchemaTest is not empty, contains 1 objects, cannot drop schema.');
+          return Parse.Promise.as();
+        });
+    }).then(() => {
+      return testSchema.purge();
+    }).then(() => {
+      const query = new Parse.Query('SchemaTest');
+      return query.count();
+    }).then((count) => {
+      assert.equal(count, 0);
+      // Delete only works on empty schema, extra check
+      return testSchema.delete();
+    }).then(() => {
+      done();
+    });
+  });
+
   it('save index', (done) => {
     const testSchema = new Parse.Schema('SchemaTest');
     const index = {
diff --git a/src/__tests__/CoreManager-test.js b/src/__tests__/CoreManager-test.js
index 259fb13..30775c4 100644
--- a/src/__tests__/CoreManager-test.js
+++ b/src/__tests__/CoreManager-test.js
@@ -328,7 +328,8 @@ describe('CoreManager', () => {
       get: function() {},
       create: function() {},
       update: function() {},
-      delete: function() {}
+      delete: function() {},
+      purge: function() {},
     })).not.toThrow();
   });
 
@@ -338,7 +339,8 @@ describe('CoreManager', () => {
       get: function() {},
       create: function() {},
       update: function() {},
-      delete: function() {}
+      delete: function() {},
+      purge: function() {},
     };
 
     CoreManager.setSchemaController(controller);
diff --git a/src/__tests__/ParseSchema-test.js b/src/__tests__/ParseSchema-test.js
index 51e74ea..cdc0046 100644
--- a/src/__tests__/ParseSchema-test.js
+++ b/src/__tests__/ParseSchema-test.js
@@ -47,6 +47,7 @@ describe('ParseSchema', () => {
       .addDate('dateField')
       .addFile('fileField')
       .addGeoPoint('geoPointField')
+      .addPolygon('polygonField')
       .addArray('arrayField')
       .addObject('objectField')
       .addPointer('pointerField', '_User')
@@ -59,6 +60,7 @@ describe('ParseSchema', () => {
     expect(schema._fields.dateField.type, 'Date');
     expect(schema._fields.fileField.type, 'File');
     expect(schema._fields.geoPointField.type, 'GeoPoint');
+    expect(schema._fields.polygonField.type, 'Polygon');
     expect(schema._fields.arrayField.type, 'Array');
     expect(schema._fields.objectField.type, 'Object');
     expect(schema._fields.pointerField.type, 'Pointer');
@@ -162,19 +164,13 @@ describe('ParseSchema', () => {
     done();
   });
 
-  // CoreManager.setSchemaController({
-  //   send() {},
-  //   get() {},
-  //   create() {},
-  //   update() {},
-  //   delete() {},
-  // });
   it('can save schema', (done) => {
     CoreManager.setSchemaController({
       send() {},
       get() {},
       update() {},
       delete() {},
+      purge() {},
       create(className, params, options) {
         expect(className).toBe('SchemaTest');
         expect(params).toEqual({
@@ -202,6 +198,7 @@ describe('ParseSchema', () => {
       get() {},
       create() {},
       delete() {},
+      purge() {},
       update(className, params, options) {
         expect(className).toBe('SchemaTest');
         expect(params).toEqual({
@@ -229,6 +226,7 @@ describe('ParseSchema', () => {
       create() {},
       update() {},
       get() {},
+      purge() {},
       delete(className, options) {
         expect(className).toBe('SchemaTest');
         expect(options).toEqual({});
@@ -243,12 +241,33 @@ describe('ParseSchema', () => {
     });
   });
 
+  it('can purge schema', (done) => {
+    CoreManager.setSchemaController({
+      send() {},
+      create() {},
+      update() {},
+      get() {},
+      delete() {},
+      purge(className) {
+        expect(className).toBe('SchemaTest');
+        return ParsePromise.as([]);
+      },
+    });
+
+    var schema = new ParseSchema('SchemaTest');
+    schema.purge().then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
   it('can get schema', (done) => {
     CoreManager.setSchemaController({
       send() {},
       create() {},
       update() {},
       delete() {},
+      purge() {},
       get(className, options) {
         expect(className).toBe('SchemaTest');
         expect(options).toEqual({});
@@ -269,6 +288,7 @@ describe('ParseSchema', () => {
       create() {},
       update() {},
       delete() {},
+      purge() {},
       get(className, options) {
         expect(className).toBe('SchemaTest');
         expect(options).toEqual({ sessionToken: 1234 });
@@ -289,6 +309,7 @@ describe('ParseSchema', () => {
       create() {},
       update() {},
       delete() {},
+      purge() {},
       get(className, options) {
         expect(className).toBe('SchemaTest');
         expect(options).toEqual({});
@@ -313,6 +334,7 @@ describe('ParseSchema', () => {
       create() {},
       update() {},
       delete() {},
+      purge() {},
       get(className, options) {
         expect(className).toBe('');
         expect(options).toEqual({});
@@ -334,6 +356,7 @@ describe('ParseSchema', () => {
       create() {},
       update() {},
       delete() {},
+      purge() {},
       get(className, options) {
         expect(className).toBe('');
         expect(options).toEqual({ sessionToken: 1234 });
@@ -355,6 +378,7 @@ describe('ParseSchema', () => {
       create() {},
       update() {},
       delete() {},
+      purge() {},
       get(className, options) {
         expect(className).toBe('');
         expect(options).toEqual({});
@@ -418,4 +442,12 @@ describe('SchemaController', () => {
       done();
     });
   });
+
+  it('purge schema', (done) => {
+    var schema = new ParseSchema('SchemaTest');
+    schema.purge().then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
 });