Skip to content

spellbook-technology/transmutation

Repository files navigation

Transmutation

Transmutation is a Ruby gem that provides a simple way to serialize Ruby objects into JSON.

It also adds an opinionated way to automatically find and use serializer classes based on the object's class name and the caller's namespace - it takes inspiration from the Active Model Serializers gem, but strips away adapters.

It aims to be a performant and elegant solution for serializing Ruby objects into JSON, with a touch of opinionated "magic" ✨.

Installation

Install the gem and add to your application's Gemfile by executing:

bundle add transmutation

or manually add the following to your Gemfile:

gem "transmutation"

If bundler is not being used to manage dependencies, install the gem by executing:

gem install transmutation

Usage

Basic Usage

  • Define a serializer class that inherits from Transmutation::Serializer and define the attributes to be serialized.

    class UserSerializer < Transmutation::Serializer
      attributes :id, :name, :email
    end
  • Serialize an object using the serializer class.

    class User
      attr_reader :id, :name, :email
    
      def initialize(id:, name:, email:)
        @id = id
        @name = name
        @email = email
      end
    end
    
    user = User.new(id: 1, name: "John Doe", email: "[email protected]")
    
    UserSerializer.new(user).to_json # => "{\"id\":1,\"name\":\"John Doe\",\"email\":\"[email protected]\"}"

    As long as your object responds to the attributes defined in the serializer, it can be serialized.

    Struct
    User = Struct.new(:id, :name, :email)
    Class
    class User
      attr_reader :id, :name, :email
    
      def initialize(id:, name:, email:)
        @id = id
        @name = name
        @email = email
      end
    end
    ActiveRecord
    # == Schema Information
    #
    # Table name: users
    #
    #  id    :bigint
    #  name  :string
    #  email :string
    class User < ApplicationRecord
    end

The #serialize method

When you include the Transmutation::Serialization module in your class, you can use the #serialize method to serialize an object.

It will attempt to find a serializer class based on the object's class name along with the caller's namespace.

include Transmutation::Serialization

serialize(User.new) # => UserSerializer.new(User.new)

If no serializer class is found, it will return the object as is.

With Ruby on Rails

When then Transmutation::Serialization module is included in a Rails controller, it also extends your render calls.

class Api::V1::UsersController < ApplicationController
  include Transmutation::Serialization

  def show
    user = User.find(params[:id])

    render json: user
  end
end

This will attempt to bubble up the controller namespaces to find a defined serializer class:

  • Api::V1::UserSerializer
  • Api::UserSerializer
  • UserSerializer

This calls the #serialize method under the hood.

If no serializer class is found, it will fall back to the default behavior of rendering the object as JSON.

You can disable this behaviour by passing serialize: false to the render method.

render json: user, serialize: false # => user.to_json

Configuration

You can override the serialization lookup by passing the following options:

  • namespace: The namespace to use when looking up the serializer class.

    render json: user, namespace: "V1" # => Api::V1::V1::UserSerializer

    To prevent caller namespaces from being appended to the provided namespace, prefix the namespace with ::.

    render json: user, namespace: "::V1" # => V1::UserSerializer

    The namespace key is forwarded to the #serialize method.

    render json: user, namespace: "V1" # => serialize(user, namespace: "V1")
  • serializer: The serializer class to use.

    render json: user, serializer: "SuperUserSerializer" # => Api::V1::SuperUserSerializer

    To prevent all namespaces from being appended to the serializer class, prefix the serializer class with ::.

    render json: user, serializer: "::SuperUserSerializer" # => SuperUserSerializer

    The serializer key is forwarded to the #serialize method.

    render json: user, serializer: "SuperUserSerializer" # => serialize(user, serializer: "SuperUserSerializer")

Opinionated Architecture

If you follow the pattern outlined below, you can take full advantage of the automatic serializer lookup.

File Structure

.
└── app/
    ├── controllers/
    │   └── api/
    │       ├── v1/
    │       │   └── users_controller.rb
    │       └── v2
    │           └── users_controller.rb
    ├── models/
    │   └── user.rb
    └── serializers/
        └── api/
            ├── v1/
            │   └── user_serializer.rb
            ├── v2/
            │   └── user_serializer.rb
            └── user_serializer.rb

Serializers

class Api::UserSerializer < Transmutation::Serializer
  attributes :id, :name, :email
end

class Api::V1::UserSerializer < Api::UserSerializer
  attributes :phone # Added in V1
end

class Api::V2::UserSerializer < Api::UserSerializer
  attributes :avatar # Added in V2
end

To remove attributes, it is recommended to redefine all attributes and start anew. This acts as a reset and makes serializer inheritance much easier to follow.

Development

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

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/spellbook-technology/transmutation. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Transmutation project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.