UNPKG

active-model-adapter

Version:
258 lines (228 loc) 7.69 kB
import RESTSerializer from '@ember-data/serializer/rest'; import { singularize, pluralize } from 'ember-inflector'; import { decamelize, camelize, underscore, classify, dasherize } from '@ember/string'; import { inject } from '@ember/service'; import { isNone } from '@ember/utils'; import { g, i } from 'decorator-transforms/runtime-esm'; /* eslint-disable prettier/prettier, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument */ /** The ActiveModelSerializer is a subclass of the RESTSerializer designed to integrate with a JSON API that uses an underscored naming convention instead of camelCasing. It has been designed to work out of the box with the [active\_model\_serializers](http://github.com/rails-api/active_model_serializers) Ruby gem. This Serializer expects specific settings using ActiveModel::Serializers, `embed :ids, embed_in_root: true` which sideloads the records. This serializer extends the DS.RESTSerializer by making consistent use of the camelization, decamelization and pluralization methods to normalize the serialized JSON into a format that is compatible with a conventional Rails backend and Ember Data. ## JSON Structure The ActiveModelSerializer expects the JSON returned from your server to follow the REST adapter conventions substituting underscored keys for camelcased ones. ### Conventional Names Attribute names in your JSON payload should be the underscored versions of the attributes in your Ember.js models. For example, if you have a `Person` model: ```javascript export default class Person extends Model { @attr() firstName; @attr() lastName; @belongsTo('occupation') occupation; } ``` The JSON returned should look like this: ```json { "famous_person": { "id": 1, "first_name": "Barack", "last_name": "Obama", "occupation": "President" } } ``` Let's imagine that `Occupation` is just another model: ```javascript export default class Person extends Model { @attr() firstName; @attr() lastName; @belongsTo('occupation') occupation; } export default class Occupation extends Model { @attr() name; @attr('number') salary; @hasMany('person') people; } ``` The JSON needed to avoid extra server calls, should look like this: ```json { "people": [{ "id": 1, "first_name": "Barack", "last_name": "Obama", "occupation_id": 1 }], "occupations": [{ "id": 1, "name": "President", "salary": 100000, "person_ids": [1] }] } ``` */ class ActiveModelSerializer extends RESTSerializer { static { g(this.prototype, "store", [inject]); } #store = (i(this, "store"), void 0); // SERIALIZE /** Converts camelCased attributes to underscored when serializing. */ keyForAttribute(attr) { return decamelize(attr); } /** Underscores relationship names and appends "_id" or "_ids" when serializing relationship keys. */ keyForRelationship(relationshipModelName, kind) { const key = decamelize(relationshipModelName); if (kind === 'belongsTo') { return key + '_id'; } else if (kind === 'hasMany') { return singularize(key) + '_ids'; } else { return key; } } /** `keyForLink` can be used to define a custom key when deserializing link properties. The `ActiveModelSerializer` camelizes link keys by default. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars keyForLink(key, _relationshipKind) { return camelize(key); } /* Does not serialize hasMany relationships by default. */ serializeHasMany() {} /** Underscores the JSON root keys when serializing. */ payloadKeyFromModelName(modelName) { return underscore(decamelize(modelName)); } /** Serializes a polymorphic type as a fully capitalized model name. */ serializePolymorphicType(snapshot, json, relationship) { const key = relationship.key; const belongsTo = snapshot.belongsTo(key); const jsonKey = underscore(key + '_type'); if (isNone(belongsTo)) { json[jsonKey] = null; } else { json[jsonKey] = classify(belongsTo.modelName).replace('/', '::'); } } /** Add extra step to `DS.RESTSerializer.normalize` so links are normalized. If your payload looks like: ```json { "post": { "id": 1, "title": "Rails is omakase", "links": { "flagged_comments": "api/comments/flagged" } } } ``` The normalized version would look like this ```json { "post": { "id": 1, "title": "Rails is omakase", "links": { "flaggedComments": "api/comments/flagged" } } } ``` */ normalize(typeClass, hash, prop) { this.normalizeLinks(hash); return super.normalize(typeClass, hash, prop); } /** Convert `snake_cased` links to `camelCase` */ normalizeLinks(data) { if (data.links) { const links = data.links; for (const link in links) { const camelizedLink = camelize(link); if (camelizedLink !== link) { links[camelizedLink] = links[link]; delete links[link]; } } } } /** * @private */ _keyForIDLessRelationship(key, relationshipType) { if (relationshipType === 'hasMany') { return underscore(pluralize(key)); } else { return underscore(singularize(key)); } } extractRelationships(modelClass, resourceHash) { modelClass.eachRelationship((key, relationshipMeta) => { const relationshipKey = this.keyForRelationship(key, relationshipMeta.kind); const idLessKey = this._keyForIDLessRelationship(key, relationshipMeta.kind); // converts post to post_id, posts to post_ids if (resourceHash[idLessKey] && typeof relationshipMeta[relationshipKey] === 'undefined') { resourceHash[relationshipKey] = resourceHash[idLessKey]; } // prefer the format the AMS gem expects, e.g.: // relationship: {id: id, type: type} if (relationshipMeta.options.polymorphic) { extractPolymorphicRelationships(key, relationshipMeta, resourceHash, relationshipKey); } // If the preferred format is not found, use {relationship_name_id, relationship_name_type} if (Object.prototype.hasOwnProperty.call(resourceHash, relationshipKey) && typeof resourceHash[relationshipKey] !== 'object') { const polymorphicTypeKey = this.keyForRelationship(key) + '_type'; if (resourceHash[polymorphicTypeKey] && relationshipMeta.options.polymorphic) { const id = resourceHash[relationshipKey]; const type = resourceHash[polymorphicTypeKey]; delete resourceHash[polymorphicTypeKey]; delete resourceHash[relationshipKey]; resourceHash[relationshipKey] = { id: id, type: type }; } } }, this); return super.extractRelationships(modelClass, resourceHash); } modelNameFromPayloadKey(key) { const convertedFromRubyModule = singularize(key.replace('::', '/')); return dasherize(convertedFromRubyModule); } } function extractPolymorphicRelationships(key, _relationshipMeta, resourceHash, relationshipKey) { const polymorphicKey = decamelize(key); const hash = resourceHash[polymorphicKey]; if (hash !== null && typeof hash === 'object') { resourceHash[relationshipKey] = hash; } } export { ActiveModelSerializer as default }; //# sourceMappingURL=active-model-serializer.js.map