Repository: miragejs/miragejs
Pull request #1086 (2023-05-16 15:53:43)

Allow for keyForId and valueForId on the serializer
Replaces PR https://github.com/miragejs/miragejs/pull/417


cah-brian-gantzler: All the tests ran locally. If they run here. I would say good to merge

Patch:
diff --git a/lib/serializer.js b/lib/serializer.js
index 43c1467..e79eb30 100644
--- a/lib/serializer.js
+++ b/lib/serializer.js
@@ -340,6 +340,48 @@ class Serializer {
       @public
     */
     this.serializeIds = this.serializeIds || undefined; // this is just here so I can add the doc comment. Better way?
+
+    /**
+      Primary Key name of the model
+
+      Defaults to 'id', so a request for an author looks like:
+
+      ```
+      GET /authors/1
+
+      {
+        author: {
+          id: 1,
+          name: 'Link'
+        }
+      }
+      ```
+
+      Setting `primaryKey` to 'authorId changes this:
+
+      ```js
+      Serializer.extend({
+        primaryKey: 'authorId'
+      });
+      ```
+
+      Now the response looks like:
+
+      ```
+      GET /authors/1
+
+      {
+        author: {
+          authorId: 1,
+          name: 'Link'
+        }
+      }
+      ```
+
+      @property primaryKey
+      @public
+    */
+    this.primaryKey = this.primaryKey || undefined; // this is just here so I can add the doc comment. Better way?
   }
 
   /**
@@ -844,6 +886,10 @@ class Serializer {
     @public
    */
   keyForAttribute(attr) {
+    if (attr === "id") {
+      return this.keyForId();
+    }
+
     return attr;
   }
 
@@ -1146,6 +1192,103 @@ class Serializer {
     return this.registry.schema;
   }
 
+  /**
+    Used to customize how a model's primary key is formatted in your JSON payload.
+
+    By default, this is 'id'
+
+    ```
+    GET /authors/1
+
+    {
+      author: {
+        id: '1',
+        firstName: 'Link',
+        lastName: 'The WoodElf'
+      }
+    }
+    ```
+
+    If your API expects a different primary key, you could write the following:
+
+    ```js
+    // serializers/application.js
+    export default Serializer.extend({
+      keyForId() {
+        return 'authorId';
+      }
+    });
+    ```
+
+    Now the response would look like:
+
+    ```
+    {
+      author: {
+        authorId: '1',
+        firstName: 'Link',
+        lastName: 'The WoodElf'
+      }
+    }
+    ```
+
+    See the property `primaryKey` for a shorthand way of doing this on a model serializer
+
+    @method keyForId
+    @public
+   */
+  keyForId() {
+    return this.primaryKey;
+  }
+
+  /**
+    Used to customize how a model's primary key value is formatted in your JSON payload.
+
+    By default, the primary key is a string
+
+    ```
+    GET /authors/1
+
+    {
+      author: {
+        id: '1',
+        firstName: 'Link',
+        lastName: 'The WoodElf'
+      }
+    }
+    ```
+
+    If your API expects a integers, you could write the following:
+
+    ```js
+    // serializers/application.js
+    export default Serializer.extend({
+      valueForId(value) {
+        return parseInt(value);
+      }
+    });
+    ```
+
+    Now the response would look like:
+
+    ```
+    {
+      author: {
+        authorId: 1,
+        firstName: 'Link',
+        lastName: 'The WoodElf'
+      }
+    }
+    ```
+
+    @method valueForId
+    @param value
+    @public
+   */
+  valueForId(value) {
+    return value;
+  }
+
   /**
     @method _formatAttributeKeys
     @param attrs
@@ -1156,8 +1299,13 @@ class Serializer {
     let formattedAttrs = {};
 
     for (let key in attrs) {
+      let formattedValue = attrs[key];
+      if (key === "id") {
+        formattedValue = this.valueForId(formattedValue);
+      }
+
       let formattedKey = this.keyForAttribute(key);
-      formattedAttrs[formattedKey] = attrs[key];
+      formattedAttrs[formattedKey] = formattedValue;
     }
 
     return formattedAttrs;
@@ -1170,6 +1318,7 @@ class Serializer {
 Serializer.prototype.include = [];
 Serializer.prototype.root = true;
 Serializer.prototype.embed = false;
+Serializer.prototype.primaryKey = "id";
 Serializer.prototype.serializeIds = "included"; // can be 'included', 'always', or 'never'
 
 Serializer.extend = extend;
diff --git a/lib/serializers/active-model-serializer.js b/lib/serializers/active-model-serializer.js
index 5bdbc0f..fa59ea7 100644
--- a/lib/serializers/active-model-serializer.js
+++ b/lib/serializers/active-model-serializer.js
@@ -10,6 +10,7 @@ export default Serializer.extend({
   },
 
   keyForAttribute(attr) {
+    attr = Serializer.prototype.keyForAttribute.apply(this, arguments);
     return underscore(attr);
   },
 
@@ -46,6 +47,11 @@ export default Serializer.extend({
     let belongsToKeys = Object.keys(belongsToAssociations);
     let hasManyKeys = Object.keys(hasManyAssociations);
 
+    if (this.primaryKey !== "id") {
+      attrs.id = attrs[this.primaryKey];
+      delete attrs[this.primaryKey];
+    }
+
     let jsonApiPayload = {
       data: {
         type: this._container.inflector.pluralize(type),
diff --git a/lib/serializers/rest-serializer.js b/lib/serializers/rest-serializer.js
index e010f88..316c2bb 100644
--- a/lib/serializers/rest-serializer.js
+++ b/lib/serializers/rest-serializer.js
@@ -9,6 +9,10 @@ export default ActiveModelSerializer.extend({
   },
 
   keyForAttribute(attr) {
+    attr = ActiveModelSerializer.prototype.keyForAttribute.apply(
+      this,
+      arguments
+    );
     return camelize(attr);
   },
 


Test patch:
diff --git a/__tests__/external/shared/serializers/base/id-key-formatting-test.js b/__tests__/external/shared/serializers/base/id-key-formatting-test.js
new file mode 100644
index 0000000..1ae5280
--- /dev/null
+++ b/__tests__/external/shared/serializers/base/id-key-formatting-test.js
@@ -0,0 +1,109 @@
+import { Server, Model, Serializer } from "miragejs";
+
+describe("External | Shared | Serializers | Base | Id Key Formatting", function () {
+  test(`keyForId formats the id of a model`, () => {
+    let server = new Server({
+      models: {
+        wordSmith: Model,
+      },
+
+      serializers: {
+        wordSmith: Serializer.extend({
+          keyForId() {
+            return "wordSmithId";
+          },
+        }),
+      },
+    });
+
+    let wordSmith = server.schema.wordSmiths.create({
+      id: 1,
+      firstName: "Link",
+      lastName: "Jackson",
+      age: 323,
+    });
+
+    let result = server.serializerOrRegistry.serialize(wordSmith);
+
+    expect(result).toEqual({
+      wordSmith: {
+        wordSmithId: "1",
+        firstName: "Link",
+        lastName: "Jackson",
+        age: 323,
+      },
+    });
+
+    server.shutdown();
+  });
+
+  test(`primaryKey formats the id of a model`, () => {
+    let server = new Server({
+      models: {
+        wordSmith: Model,
+      },
+
+      serializers: {
+        wordSmith: Serializer.extend({
+          primaryKey: "wordSmithId",
+        }),
+      },
+    });
+
+    let wordSmith = server.schema.wordSmiths.create({
+      id: 1,
+      firstName: "Link",
+      lastName: "Jackson",
+      age: 323,
+    });
+
+    let result = server.serializerOrRegistry.serialize(wordSmith);
+
+    expect(result).toEqual({
+      wordSmith: {
+        wordSmithId: "1",
+        firstName: "Link",
+        lastName: "Jackson",
+        age: 323,
+      },
+    });
+
+    server.shutdown();
+  });
+
+  test(`valueForId formats the id of a model`, () => {
+    let server = new Server({
+      models: {
+        wordSmith: Model,
+      },
+
+      serializers: {
+        wordSmith: Serializer.extend({
+          valueForId(value) {
+            return parseInt(value);
+          },
+        }),
+      },
+    });
+
+    let wordSmith = server.schema.wordSmiths.create({
+      id: 1,
+      firstName: "Link",
+      lastName: "Jackson",
+      age: 323,
+    });
+
+    let result = server.serializerOrRegistry.serialize(wordSmith);
+
+    expect(result).toEqual({
+      wordSmith: {
+        id: 1,
+        firstName: "Link",
+        lastName: "Jackson",
+        age: 323,
+      },
+    });
+
+    server.shutdown();
+  });
+});
diff --git a/__tests__/internal/unit/serializers/active-model-serializer-test.js b/__tests__/internal/unit/serializers/active-model-serializer-test.js
index 40bb901..087b1b1 100644
--- a/__tests__/internal/unit/serializers/active-model-serializer-test.js
+++ b/__tests__/internal/unit/serializers/active-model-serializer-test.js
@@ -44,6 +44,29 @@ describe("Unit | Serializers | ActiveModelSerializer", function () {
     });
   });
 
+  test("normalize primaryKey Works", () => {
+    let payload = {
+      contact: {
+        contactId: 1,
+        name: "Link",
+      },
+    };
+
+    serializer.primaryKey = "contactId";
+
+    let jsonApiDoc = serializer.normalize(payload);
+
+    expect(jsonApiDoc).toEqual({
+      data: {
+        type: "contacts",
+        id: 1,
+        attributes: {
+          name: "Link",
+        },
+      },
+    });
+  });
+
   test("it hyphenates snake_cased words", () => {
     let payload = {
       contact: {