Skip to content
🌀 Add media types supported serialization using your favourite serializer
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.


Build Status: master Gem Version MIT license

Add media types supported serialization using your favourite serializer


Add this line to your application's Gemfile:

gem 'media_types-serialization'

And then execute:

$ bundle

Or install it yourself as:

$ gem install media_types-serialization

If you have not done this before, and you're using rails, install the necessary parts using:

rails g media_types:serialization:api_viewer

This will:

  • Add the default html_wrapper layout which is an API Viewer used as fallback or the .api_viewer format
  • Add the default template_controller which allows the API Viewer to post templated links
  • Add the route for these templated link forms
  • Add an initializer that registers the media renderer and api_viewer media type


In order to use media type serialization you only need to do 2 things:


Add a serializer that can serialize a certain media type. The to_hash function will be called explicitly in your controller, so you can always use your own, favourite serializer here to do the hefty work. This gem does provide some easy tools, usually enough to do most serialization.

class Book < ApplicationRecord
  class Serializer < MediaTypes::Serialization::Base
    serializes_media_type MyNamespace::MediaTypes::Book
    def fields
      if current_media_type.create?
        return %i[name author]
     %i[name author updated_at views]

    def to_hash
      extract(serializable, fields).tap do |result|
        result[:_links] = extract_links unless current_media_type.create?

    alias to_h to_hash
    def extract_self
      # A serializer gets the controller as context
      { href: context.api_book_url(serializable) }

    def extract_links(view:)
        'self': extract_self,
        'signatures': { href: context.api_book_signatures_url(serializable) }

By default, The passed in MediaType gets converted into a constructable (via to_constructable) and invoked with the current view (e.g. create, index, collection or ). This means that by default it will be able to serialize the latest version you MediaType is reporting. The best way to supply your media type is via the media_types gem.

Multiple suffixes, one serializer

By default, the media renderer will automatically detect and inject the following:

  • suffix +json if you define to_json
  • suffix +xml if you define to_xml
  • type text/html if you define to_html

If you do not define these methods, only the default suffix / type will be used, accepts_html for the text/html content-type.

If you don't define to_html, but try to make a serializer output html, it will be rendered in the layout at: serializers/wrapper/html_wrapper.html.erb (or any other templating extension).

Migrations (versions)

If the serializer can serialize multiple versions, you can supply them through additional_versions: [2, 3]. A way to handle this is via backward migrations, meaning you'll migrate from the current version back to an older version.

class Book < ApplicationRecord
  class BasicSerializer < MediaTypes::Serialization::Base
    # Maybe it's currently at version 2, so tell the base that this also serializes version 1
    # You can also use a range to_a: (1...4).to_a
    serializes_media_type MyNamespace::MediaTypes::Book, additional_versions: [1]
    def to_hash
      # This enables migrations right when it's being serialized
      migrate do
        extract(serializable, fields).tap do |result|
          result[:_links] = extract_links unless current_media_type.create?
    # This defines migrations. You can use classes, commands or anything else to execute this code
    # but inline migrations work fine if you don't have a lot of them. 
    backward_migrations do
      # This is called if the version requested is 1 _or_ lower. This means you can compose your migrations. The 
      # migrations with a _lower_ version than the requested version are NOT executed.
      version 1 do |result|
        result.tap do |r|
          if r.key?(:views)
            r[:views_count] = r.delete(:views)


In your base controller, or wherever you'd like, include the MediaTypes::Serialization concern. In the controller that uses the serialization, you need to explicitly accept it if you want to use the built-in lookups.

require 'media_types/serialization'
require 'media_types/serialization/renderer/register'

class ApiController < ActionController::API
  include MediaTypes::Serialization

class BookController < ApiController

  accept_serialization(Book::BasicSerializer, accept_html: false, only: %i[show])
  accept_html(Book::CoverHtmlSerializer, only: %i[show])
  def show 
    # If you do NOT pass in the content_type, it will re-use the current content_type of the response if set or
    # use the default content type of the serializer. This is fine if you only output one Content-Type in the
    # action, but not if you are relying on content-negotiation. 
    render media: serialize_media(@book), content_type: request.format.to_s

If you have normalized your resources (e.g. into @resource), you can add a render_media method to your BaseController and render resources like so:

class ApiController < ActionController::API
  def render_media(**opts)
    render media: serialize_media(@resource), content_type: request.format.to_s, **opts

And then call render_media whenever you're ready to render.

HTML output

You can define HTML outputs for example by creating a serializer that accepts text/html. At this moment, there may only be one (1) active text/html serializer for each action; a single controller can have multiple registered, but never for the same preconditions in before_action (because how else would it know which one to pick?).

Use the render method to generate your HTML:

class Book::CoverHtmlSerializer < MediaTypes::Serialization::Base
  # Tell the serializer that this accepts HTML, but this is also signaled by `to_html`
  def to_html
      assigns: {
        title: extract_title,
        image: resolve_file_url(covers.first&.version_url('small')),
        description: extract_description,
        language_links: language_links,
      layout: false
  # Naturally you have to define extract_title, etc etc 

You can change the default wrapper / to_html implementation by setting:

::MediaTypes::Serialization.html_wrapper_layout = '/path/to/wrapper/layout'

API viewer

There is a special media type exposed by this gem at ::MediaTypes::Serialization::MEDIA_TYPE_API_VIEWER. If you're using rails you'll want to register it. You can do so manually, or by requireing:

require 'media_types/serialization/media_type/register'

If you do so, the .api_viewer format becomes available for all actions that call into render media:.

You can change the default wrapper implementation by setting:

::MediaTypes::Serialization.api_viewer_layout = '/path/to/wrapper/layout'

Wrapping output

By convention, index views are wrapped in _index: [items], collection views are wrapped in _embedded: [items] and create / no views are wrapped in [ROOT_KEY]: item. This is currently only enabled for to_json serialization but planned for xml as well.

This behaviour can not be turned of as of writing. However, you may overwrite this behaviour via:

  • self.root_key(view:): to define the root key for a specific view
  • self.wrap(serializer, view: nil): to define the wrapper for a specific view and/or serializer. For example, if you never want to wrap anything, you could define:
    def self.wrap(serializer, view: nil)

Link header

You can use to_link_header to generate a header value for the Link header.

entries = @last_media_serializer.to_link_header
if entries.present?
  response.header[HEADER_LINK] = entries

If you want the link header to be different from the _links, you can implement header_links(view:) next to extract_links(view:). This will be called by the to_link_header function.


If you only have json/xml/structured data responses and you want to use media_types-validation in conjunction with this gem, you can create a concern or add the following two functions to your base controller:

def render_media(resource = @resource, **opts)
  serializer = serialize_media(resource)
  render media: serializer, content_type: request.format.to_s, **opts

def validate_media(serializer = @last_media_serializer)
  media_type = serializer.current_media_type
  return true unless media_type && response_body
  validate_json_with_media_type(serializer.to_hash, media_type: media_type)

As long as the serializer has a to_json or to_hash, this will work -- but also means that the data will always be validate as if it were json. This covers most use cases.



After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to


Bug reports and pull requests are welcome on GitHub at XPBytes/media_types-serialization.

You can’t perform that action at this time.