# 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](https://github.com/rails-api/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" :sparkles:.
## Installation
Install the gem and add to your application's Gemfile by executing:
```bash
bundle add transmutation
```
or manually add the following to your Gemfile:
```ruby
gem "transmutation"
```
If bundler is not being used to manage dependencies, install the gem by executing:
```bash
gem install transmutation
```
## Usage
### Basic Usage
- Define a serializer class that inherits from `Transmutation::Serializer` and define the attributes to be serialized.
```ruby
class UserSerializer < Transmutation::Serializer
attributes :id, :name, :email
end
```
- Serialize an object using the serializer class.
```ruby
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: "john@example.com")
UserSerializer.new(user).to_json # => "{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}"
```
As long as your object responds to the attributes defined in the serializer, it can be serialized.
Struct
```ruby
User = Struct.new(:id, :name, :email)
```
Class
```ruby
class User
attr_reader :id, :name, :email
def initialize(id:, name:, email:)
@id = id
@name = name
@email = email
end
end
```
ActiveRecord
```ruby
# == 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.
```ruby
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.
```ruby
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.
```ruby
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.
```ruby
render json: user, namespace: "V1" # => Api::V1::V1::UserSerializer
```
To prevent caller namespaces from being appended to the provided namespace, prefix the namespace with `::`.
```ruby
render json: user, namespace: "::V1" # => V1::UserSerializer
```
The `namespace` key is forwarded to the `#serialize` method.
```ruby
render json: user, namespace: "V1" # => serialize(user, namespace: "V1")
```
- `serializer`: The serializer class to use.
```ruby
render json: user, serializer: "SuperUserSerializer" # => Api::V1::SuperUserSerializer
```
To prevent all namespaces from being appended to the serializer class, prefix the serializer class with `::`.
```ruby
render json: user, serializer: "::SuperUserSerializer" # => SuperUserSerializer
```
The `serializer` key is forwarded to the `#serialize` method.
```ruby
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
```ruby
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](https://github.com/spellbook-technology/transmutation/blob/main/CODE_OF_CONDUCT.md).
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
## 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](https://github.com/spellbook-technology/transmutation/blob/main/CODE_OF_CONDUCT.md).