シリアライザー

シリアライザーは、ルートハンドラーのレスポンスのフォーマットを担当します。

アプリケーションシリアライザーは、すべてのレスポンスに適用されます。特定のカスタマイズを行うには、モデルごとのシリアライザーを定義します。

import { Server, RestSerializer } from 'miragejs';

new Server({
  serializers: {
    application: RestSerializer,
    user: RestSerializer.extend({
      // user-specific customizations
    })
  }
})

ルートハンドラーから返されるすべてのモデルまたはコレクションは、シリアライザーレイヤーを通過します。最も高い優先度はモデル固有のシリアライザーに与えられ、次にアプリケーションシリアライザー、最後にデフォルトのシリアライザーとなります。

Mirageには、3つの名前付きシリアライザーが用意されています。

  • JSONAPISerializer は、JSON:API準拠のAPIサーバーをシミュレートします。

    import { Server, JSONAPISerializer } from 'miragejs';
    
    new Server({
      serializers: {
        application: JSONAPISerializer
      }
    })
  • ActiveModelSerializer は、AMSスタイルのレスポンスを使用するRails APIをモックします。

    import { Server, ActiveModelSerializer } from 'miragejs';
    
    new Server({
      serializers: {
        application: ActiveModelSerializer
      }
    })
  • RestSerializer は、多くの一般的なREST APIの出発点として適しています。

    import { Server, RestSerializer } from 'miragejs';
    
    new Server({
      serializers: {
        application: RestSerializer
      }
    })

さらに、Mirageには基本的なSerializerクラスがあり、以下に記載されているフックを使用してカスタマイズできます。

import { Server, Serializer } from 'miragejs';

new Server({
  serializers: {
    application: Serializer
  }
})

モデル固有のシリアライザーを作成する際には、共有ロジックがモデル固有のクラスで使用されるように、アプリケーションシリアライザーから拡張することを忘れないでください。

import { Server, Serializer } from 'miragejs';

const ApplicationSerializer = Serializer.extend()

new Server({
  serializers: {
    application: ApplicationSerializer,
    blogPost: ApplicationSerializer.extend({
      include: ['comments']
    })
  }
})

プロパティ

schema: オブジェクト

スキーマインスタンスへの参照。

登録されたスキーマ情報を参照するのに役立ちます。たとえば、シリアライザーの`include`フックで、リソースのすべての関連付けを含める場合などです。

Serializer.extend({
  include(request, resource) {
    return Object.keys(this.schema.associationsFor(resource.modelName));
  }
})

attrs: 任意

モデルシリアライザーで、JSONペイロードで使用される属性をホワイトリストするために使用します。

たとえば、データベースに次の様なblog-postモデルがあるとします。

{
  id: 1,
  title: 'Lorem ipsum',
  createdAt: '2014-01-01 10:00:00',
  updatedAt: '2014-01-03 11:42:12'
}

そして、idtitleだけが必要な場合は、次のように記述できます。

Serializer.extend({
  attrs: ['id', 'title']
});

すると、ペイロードは次のようになります。

{
  id: 1,
  title: 'Lorem ipsum'
}

embed: 任意

関連モデルを埋め込むか、サイドロードするかどうかを設定します。

JSONAPISerializerには適用されません。

デフォルトではfalseなので、リレーションシップはサイドロードされます。

GET /authors/1

{
  author: {
    id: 1,
    name: 'Link',
    blogPostIds: [1, 2]
  },
  blogPosts: [
    { id: 1, authorId: 1, title: 'Lorem' },
    { id: 2, authorId: 1, title: 'Ipsum' }
  ]
}

embedをtrueに設定すると、関連レコードが埋め込まれます。

Serializer.extend({
  embed: true
});

レスポンスは次のようになります。

GET /authors/1

{
  author: {
    id: 1,
    name: 'Link',
    blogPosts: [
      { id: 1, authorId: 1, title: 'Lorem' },
      { id: 2, authorId: 1, title: 'Ipsum' }
    ]
  }
}

include: 任意

モデルシリアライザーで、JSONペイロードに含めたい関連モデルを指定するために使用します。(これらは、デフォルトのサーバーサイドインクルードと見なすことができます。)

たとえば、多くのblog-postを持つauthorがあり、これらをサイドロードしたい場合は、includeキーで指定します。

new Server({
  models: {
    author: Model.extend({
      blogPosts: hasMany()
    })
  },
  serializers: {
    author: Serializer.extend({
      include: ['blogPosts']
    });
  }
})

すると、authorのリクエストに対するレスポンスは次のようになります。

GET /authors/1

{
  author: {
    id: 1,
    name: 'Link',
    blogPostIds: [1, 2]
  },
  blogPosts: [
    {id: 1, authorId: 1, title: 'Lorem'},
    {id: 2, authorId: 1, title: 'Ipsum'}
  ]
}

includeを関数として定義して、動的に決定することもできます。

たとえば、includeクエリパラメーターに基づいて、条件付きでリレーションシップを含めることができます。

// Include blog posts for a GET to /authors/1?include=blogPosts

Serializer.extend({
  include: function(request) {
    if (request.queryParams.include === "blogPosts") {
      return ['blogPosts'];
    } else {
      return [];
    }
  }
});

JSONAPISerializerのクエリパラメーターインクルード

JSONAPISerializerは、includeクエリパラメーターを使用して、すぐに複合ドキュメントを返すことをサポートしています。

たとえば、アプリが次のリクエストを行う場合

GET /api/authors?include=blogPosts

JSONAPISerializerはリクエストのクエリパラメーターを検査し、blogPostsリレーションシップが存在することを確認し、このリレーションシップがシリアライザー自体の`include: []`配列に直接指定されているかのように処理を続けます。

仕様に従って、Mirageは、シリアライザーに直接指定されている可能性のあるデフォルトの`include: []`配列よりも、`?include`クエリパラメーターを優先します。ただし、リクエストに`?include`クエリパラメーターがない場合は、デフォルトのインクルードが有効なままです。

また、include: []配列で指定されたデフォルトのインクルードは、単一のモデルのみを受け入れることができ、ネストされたリレーションシップへのドット区切りのパスを受け入れることはできません。

リソースのデフォルトのドット区切り(ネストされた)インクルードパスを設定する場合は、request.queryParamsのデフォルト値を設定することで、ルートレベルで行う必要があります。

this.get('/users', function(schema, request) => {
  request.queryParams = request.queryParams || {};
  if (!request.queryParams.include) {
    request.queryParams.include = 'blog-posts.comments';
  }

  // rest of route handler logic
});

root: 任意

JSONレスポンスにルートキーを含めるかどうかを設定します。

JSONAPISerializerには適用されません。

デフォルトはtrueなので、authorのリクエストは次のようになります。

GET /authors/1

{
  author: {
    id: 1,
    name: 'Link'
  }
}

rootをfalseに設定すると、これが無効になります。

Serializer.extend({
  root: false
});

レスポンスは次のようになります。

GET /authors/1

{
  id: 1,
  name: 'Link'
}

serializeIds: 任意

シリアライザーがリレーションシップキーのシリアライズをどのように処理するかを定義するために使用します。3つの値のいずれかを取ることができます。

  • included(デフォルト)は、リレーションシップがモデルまたはコレクションと共にレスポンスに含まれている場合(サイドロードされている場合)、そのリレーションシップのIDをシリアライズします。
  • alwaysは、レスポンス内のモデルまたはコレクションのすべてのリレーションシップのIDを常にシリアライズします。
  • neverは、レスポンス内のモデルまたはコレクションのリレーションシップのIDをシリアライズしません。

メソッド

keyForAttribute(attr: 任意): 任意

モデルの属性がJSONペイロードでどのようにフォーマットされるかをカスタマイズするために使用します。

デフォルトでは、モデル属性はcamelCaseです。

GET /authors/1

{
  author: {
    firstName: 'Link',
    lastName: 'The WoodElf'
  }
}

APIがsnake_caseを期待する場合は、次のように記述できます。

// serializers/application.js
export default Serializer.extend({
  keyForAttribute(attr) {
    return underscore(attr);
  }
});

レスポンスは以下のようになります。

{
  author: {
    first_name: 'Link',
    last_name: 'The WoodElf'
  }
}

keyForCollection(modelName: any): any

プライマリコレクションをシリアライズする際のキーをカスタマイズするために使用します。デフォルトでは、keyForModelの戻り値の複数形になります。

例えば、デフォルトでは以下のリクエストのようになります。

GET /blogPosts

{
  blogPosts: [
    {
      id: 1,
      title: 'Lorem ipsum'
    },
    ...
  ]
}

APIがキーにハイフンを使用する場合、keyForCollectionをオーバーライドできます。

// serializers/application.js
export default Serializer.extend({
  keyForCollection(modelName) {
    return this._container.inflector.pluralize(dasherize(modelName));
  }
});

レスポンスは以下のようになります。

{
  'blog-posts': [
    {
      id: 1,
      title: 'Lorem ipsum'
    },
    ...
  ]
}

keyForEmbeddedRelationship(attributeName: any): any

keyForRelationshipと同様ですが、埋め込み関係の場合です。

keyForForeignKey(relationshipName: any): any

keyForRelationshipIdsと同様ですが、belongsTo関係の場合です。

例えば、blogPostをシリアライズしていて、1つのauthorをサイドロードする場合、blogPostのJSONにはauthorIdキーが含まれます。

{
  blogPost: {
    id: 1,
    authorId: 1
  },
  author: ...
}

keyForForeignKeyをオーバーライドして、このキーのフォーマットを変更します。

// serializers/application.js
export default Serializer.extend({
  keyForForeignKey(relationshipName) {
    return underscore(relationshipName) + '_id';
  }
});

レスポンスは以下のようになります。

{
  blogPost: {
    id: 1,
    author_id: 1
  },
  author: ...
}

keyForModel(modelName: any): any

modelName modelNameのプライマリモデルをシリアライズする際のキーをカスタマイズするために使用します。例えば、デフォルトのSerializerは以下のようになります。

GET /blogPosts/1

{
  blogPost: {
    id: 1,
    title: 'Lorem ipsum'
  }
}

APIがハイフン付きキーを使用する場合、keyForModelをオーバーライドできます。

// serializers/application.js
export default Serializer.extend({
  keyForModel(modelName) {
    return hyphenate(modelName);
  }
});

レスポンスは以下のようになります。

{
  'blog-post': {
    id: 1,
    title: 'Lorem ipsum'
  }
}

keyForPolymorphicForeignKeyId(relationshipName: String): String

ポリモーフィックなリレーションシップは、タイプとIDのペアで表現されます。

以下のモデルの場合

Model.extend({
  commentable: belongsTo({ polymorphic: true })
});

デフォルトのSerializerは以下のようになります。

{
  comment: {
    id: 1,
    commentableType: 'post',
    commentableId: '1'
  }
}

このフックは、idフィールド(上記の例ではcommentableId)のシリアライズ方法を制御します。デフォルトでは、リレーションシップをキャメルケースに変換し、Idをサフィックスとして追加します。

keyForPolymorphicForeignKeyType(relationshipName: String): String

ポリモーフィックなリレーションシップは、タイプとIDのペアで表現されます。

以下のモデルの場合

Model.extend({
  commentable: belongsTo({ polymorphic: true })
});

デフォルトのSerializerは以下のようになります。

{
  comment: {
    id: 1,
    commentableType: 'post',
    commentableId: '1'
  }
}

このフックは、typeフィールド(上記の例ではcommentableType)のシリアライズ方法を制御します。デフォルトでは、リレーションシップをキャメルケースに変換し、Typeをサフィックスとして追加します。

keyForRelationship(modelName: any): any

このモデルに関連するコレクションのキーのフォーマットを変更するために使用します。modelNameは、リレーションシップの名前付きパラメーターです。

例えば、多くのblogPostsをサイドロードするauthorをシリアライズする場合、デフォルトのレスポンスは以下のようになります。

{
  author: {...},
  blogPosts: [...]
}

keyForRelationshipをオーバーライドして、このキーのフォーマットを変更します。

// serializers/application.js
export default Serializer.extend({
  keyForRelationship(modelName) {
    return underscore(modelName);
  }
});

レスポンスは以下のようになります。

{
  author: {...},
  blog_posts: [...]
}

keyForRelationshipIds(modelName: any): any

このモデルのJSON表現におけるhasMany関係のIDのキーのフォーマットを変更するために使用します。

例えば、多くのblogPostsをサイドロードするauthorをシリアライズする場合、デフォルトではauthorのJSONにはblogPostIdsキーが含まれます。

{
  author: {
    id: 1,
    blogPostIds: [1, 2, 3]
  },
  blogPosts: [...]
}

keyForRelationshipIdsをオーバーライドして、このキーのフォーマットを変更します。

// serializers/application.js
export default Serializer.extend({
  keyForRelationshipIds(relationship) {
    return underscore(relationship) + '_ids';
  }
});

レスポンスは以下のようになります。

{
  author: {
    id: 1,
    blog_post_ids: [1, 2, 3]
  },
  blogPosts: [...]
}

normalize(json: any): any

このメソッドは、POSTおよびPUTショートハンドで使用されます。これらのショートハンドは、適切なリソースの作成または更新方法を知るために、リクエストの一部として有効なJSON:APIドキュメントを期待します。normalizeメソッドを使用すると、リクエストボディをJSON:APIドキュメントに変換できるため、それ以外では使用できないショートハンドを利用できます。

JSON:APIを既に使用している場合は、このメソッドはnoopになります。POSTおよびPUTリクエストと共に送信されたリクエストペイロードは、既に正しい形式になっているためです。

例として、含まれているActiveModelSerializerのnormalizeメソッドを参照してください。

serialize(primaryResource: any, request: any): Object

独自の カスタムシリアライズ関数を実装するには、このメソッドをオーバーライドします。responseは、ルートハンドラーから返されたものであり、requestはPretenderのリクエストオブジェクトです。

プレーンなJavaScriptオブジェクトまたは配列を返し、MirageはこれをアプリのXHRリクエストへのレスポンスデータとして使用します。

このメソッドをオーバーライドし、superを呼び出して、Mirageが応答する前にデータを操作することもできます。これは、メタデータの追加や、Mirageの他の抽象化にうまく適合しない一括処理に最適な場所です。

serialize(object, request) {
  // This is how to call super, as Mirage borrows [Backbone's implementation of extend](http://backbonejs.org/#Model-extend)
  let json = Serializer.prototype.serialize.apply(this, arguments);

  // Add metadata, sort parts of the response, etc.

  return json;
}