{"id":13879776,"url":"https://github.com/FidMe/active_mappers","last_synced_at":"2025-07-16T15:33:05.185Z","repository":{"id":56391963,"uuid":"153070150","full_name":"FidMe/active_mappers","owner":"FidMe","description":"The cleanest way to declare your Rails API view layer. Period.","archived":false,"fork":false,"pushed_at":"2024-03-25T14:54:13.000Z","size":77,"stargazers_count":13,"open_issues_count":4,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-15T04:55:38.956Z","etag":null,"topics":["json","mappers","rails","ruby","serializer"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FidMe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-10-15T07:24:39.000Z","updated_at":"2024-01-26T11:24:18.000Z","dependencies_parsed_at":"2023-02-12T05:02:51.906Z","dependency_job_id":null,"html_url":"https://github.com/FidMe/active_mappers","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FidMe%2Factive_mappers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FidMe%2Factive_mappers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FidMe%2Factive_mappers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FidMe%2Factive_mappers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FidMe","download_url":"https://codeload.github.com/FidMe/active_mappers/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226143895,"owners_count":17580245,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["json","mappers","rails","ruby","serializer"],"created_at":"2024-08-06T08:02:32.733Z","updated_at":"2024-11-24T08:31:47.406Z","avatar_url":"https://github.com/FidMe.png","language":"Ruby","readme":"# ActiveMappers\n\n[![Build Status](https://travis-ci.org/FidMe/active_mappers.svg?branch=master)](https://travis-ci.org/FidMe/active_mappers)\n[![Gem Version](https://badge.fury.io/rb/active_mappers.svg)](https://badge.fury.io/rb/active_mappers)\n\nIf you have ever done Rails API development, you must have considered using a layer to abstract and centralize your JSON objects construction.\n\nThere are multiple solutions out on the market, here is a quick overview of each :\n\n| Solution                 | Pros                                                                               | Cons                                                 |\n| ------------------------ | ---------------------------------------------------------------------------------- | ---------------------------------------------------- |\n| JBuilder                 | Simple, easy, integrates with the default View layer                               | Very slow, dedicated to JSON                         |\n| Active Model Serializers | Simple, easy to declare                                                            | Can be hard to customize, slow, project is abandoned |\n| fast_json_api            | As simple as AMS, fast                                                             | Hard to customize, JSONAPI standard is required      |\n| ActiveMappers            | Blazing fast, Easy to declare/customize, works with any format output (JSON, Hash) | Limited number of options (for now)                  |\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'active_mappers'\n```\n\nExecute\n\n```bash\n$ bundle install\n```\n\nThen, depending on your usage you may want to create an `app/mappers` folder in your Rails application.\n\nYou will put all your mappers inside of it.\n\n## Basic usage\n\n```ruby\nUserMapper.with(user)\n# =\u003e\n# {\n#   user: {\n#     id: '123',\n#     email: 'mvilleneuve@snapp.fr',\n#     profile: {\n#       first_name: 'Michael',\n#       last_name: 'Villeneuve',\n#     }\n#   }\n# }\n```\n\n## Setup (optional)\n\nYou may want to customize some parts of ActiveMappers behavior.\n\nIf you want to, create an initializer in your project :\n\n```ruby\n# config/initializers/active_mappers.rb\nActiveMappers::Setup.configure do |config|\n  config.camelcase_keys = false\n  config.ignored_namespaces = [:admin, :back_office]\nend\n```\n\nHere is the list of configurable options\n\n| Option                  | Type      | Default   | Description                                                        |\n| ----------------------- | --------- | --------- | ------------------------------------------------------------------ |\n| `camelcase_keys`        | `boolean` | `true`    | Should keys name be camelcase. Fallback to snake when set to false |\n| `ignored_namespaces`    | `Array`   | `[]`      | Namespaces to ignore when generating json root key name            |\n| `root_keys_transformer` | `Proc`    | See below | Custom way to change a mapper class name into a JSON root key      |\n\n### Root Keys Transformer\n\nA root key transform is used to transform the mapper class name into a JSON root key name.\n\nFor example this mapper class :\n\n```ruby\nclass User::ProfileInformationMapper \u003c ActiveMappers::Base\nend\n```\n\nWill automatically resolve to the following json :\n\n```json\n{\n  \"user/profileInformation\": {}\n}\n```\n\nTo customize this behavior you can do the following :\n\n```ruby\nconfig.root_keys_transformer = proc do |key|\n  # Return any key transform based on the key which is the class name of your mapper\n\n  # The below line transforms User::ProfileInformationMapper to user/profile_informations\n  key.gsub('Mapper', '').tableize\nend\n```\n\n## API\n\n### Declaring your attributes\n\nMost basic usage, just declare the attributes you want to display.\n\n```ruby\nclass UserMapper \u003c ActiveMappers::Base # You must extend ActiveMappers::Base in order to use the DSL\n  attributes :id, :email\nend\n```\n\n### Delegating your attributes\n\nSay you have a model with the following structure :\n\n```ruby\n{\n  email: 'mvilleneuve@snapp.fr',\n  profile: {\n    first_name: 'Michael',\n  }\n}\n```\n\nAnd you want to generate this structure :\n\n```ruby\n{\n  email: 'mvilleneuve@snapp.fr',\n  first_name: 'Michael',\n  last_name: 'Villeneuve',\n}\n```\n\nTo implement this, you must use the `delegate` feature :\n\n```ruby\nclass UserMapper \u003c ActiveMappers::Base\n  delegate :first_name, :last_name, to: :profile\nend\n```\n\n### Declaring relationship\n\nYou can declare any type of relationship (`has_one`, `belongs_to`, `has_many`, etc) and the mapper that matches it will automatically be fetched and used.\n\nFor example if a `User` has a `belongs_to` relationship with an `Account` you can write :\n\n```ruby\nclass UserMapper \u003c ActiveMappers::Base\n  attributes :email\n  relation :account # Will automatically resolve to AccountMapper\nend\n\nclass AccountMapper \u003c ActiveMappers::Base\n  attributes :first_name, :last_name\nend\n```\n\nIt will generate something like\n\n```ruby\n{\n  user: {\n    email: 'mvilleneuve@snapp.fr',\n    account: {\n      first_name: 'Michael',\n      last_name: 'Villeneuve'\n    }\n  }\n}\n```\n\nIt also works with namespaced resources.\n\nIf you need you can specify more options :\n\n```ruby\nclass UserMapper \u003c ActiveMappers::Base\n  relation :account, AccountMapper, scope: :admin\nend\n```\n\n### Declaring polymorphic relationships\n\nConsider the following polymorphic relation :\n\n```ruby\nclass Post\n  belongs_to :author, polymorphic: true\nend\n\nclass AdminUser\n  has_many :posts, class_name: 'Post', as: :author\nend\n\nclass NormalUser\n  has_many :posts, class_name: 'Post', as: :author\nend\n```\n\nIn order to use the `author` polymorphic attribute in your `PostMapper` you need to declare the following :\n\n```ruby\nclass PostMapper \u003c ActiveMappers::Base\n  polymorphic :author\nend\n```\n\nAnd of course, you must implement the associated mappers :\n\n```ruby\nclass AdminUserMapper\n  attributes :id, :name\nend\n\nclass NormalUserMapper\n  attributes :id, :name\nend\n```\n\nThen, based of the `XXX_type` column, the mapper will automatically resolve to either `AdminUserMapper` or `NormalUserMapper`\n\n### Rendering a collection of different classes\n\nSay you want to render many resources with a single Mapper\n\n```ruby\ncollection = Bird.all + Fish.all + Insect.all\n\nrender json: AnimalMapper.with(collection)\n\nclass AnimalMapper \u003c ActiveMappers::Base\n  acts_as_polymorphic\nend\n\nclass BirdMapper \u003c ActiveMappers::Base\n  attributes :name, :wings_count\nend\n\nclass FishMapper \u003c ActiveMappers::Base\n  attributes :name, :fins_count\nend\n\nclass InsectMapper \u003c ActiveMappers::Base\n  attributes :name, :has_venom\nend\n```\n\nWill generate the following :\n\n```ruby\n{\n  animals: [\n    { name: 'Michael', wings_count: 2 },\n    { name: 'Emeric', fins_count: 1 },\n    { name: 'Arthur', has_venom: true },\n  ]\n}\n```\n\nAgain, just like the above polymorphic declaration, the mapper will automatically resolve to the corresponding one.\n\n### Custom Attributes\n\nIf you need to implement custom attributes you can always use the `each` statement.\n\n```ruby\nclass UserMapper \u003c ActiveMappers::Base\n  attributes :email, :id\n\n  each do |user|\n    {\n      custom_attribute: \"Hi, I'm a custom attribute\",\n      another_custom_attribute: Time.now\n    }\n  end\nend\n```\n\nWill generate the following:\n\n```ruby\n{\n  user: {\n    id: '12345',\n    email: 'mvilleneuve@snapp.fr',\n    custom_attribute: \"Hi, I'm a custom attribute\",\n    another_custom_attribute: \"2018-09-26 17:49:59 +0200\"\n  }\n}\n```\n\nYou can declare any number of `each` in a single mapper.\nActually, `each` is used to implement every above features.\n\n### Context\n\nIn most cases declaring your mapper with your resource should be enough. However, sometimes you may need to give your mapper a context in order to allow it to resolve a method or something else.\n\nAt fidme we found a use case where we need to send an instance of a user along with a mapper in order to call a model method with user as params.\n\nTo solve this use case we use the context option, for instance :\n\n```ruby\nclass RewardMapper \u003c ActiveMappers::Base\n  each do |reward, context|\n    { user_reward: reward.of_user(context) }\n  end\nend\n\nRewardMapper.with(reward, context: user)\n```\n\n### Scope\n\nActiveMappers does not yet support inheritance. However we provide an even better alternative named `scope`.\n\nWhenever you feel the need to declare more or less attributes based on who called the mapper, you may want to consider using scope.\n\nA very usual use case would be to have a different way to map a resource depending on wether you are an administrator or not.\nInstead of declaring a whole new mapper just to add/remove attributes, you can do the following :\n\n```ruby\nclass UserMapper \u003c ActiveMappers::Base\n  attributes :pseudo\n\n  scope :admin\n    attributes :id\n  end\n\n  scope :owner\n    attributes :email\n  end\nend\n\n# This declaration gives you 3 ways to call the mapper\n\n# By an administrator\nUserMapper.with(User.first, scope: :admin)\n# =\u003e { pseudo: 'michael33', id: '1234' }\n\n# By anyone\nUserMapper.with(User.first)\n# =\u003e { pseudo: 'michael33' }\n\n# Or by the corresponding user that will gain access to personal informations\nUserMapper.with(User.first, scope: :owner)\n# =\u003e { pseudo: 'michael33', email: 'mvilleneuve@fidme.com' }\n```\n\n## Using a mapper\n\nEven though there are many ways to declare a mapper, there is only one way to use it\n\n```ruby\nUserMapper.with(User.first)\n\n# Or use it with a collection\nUserMapper.with(User.all)\n```\n\nIn a Rails controller :\n\n```ruby\ndef index\n  render json: UserMapper.with(User.all)\nend\n```\n\n### JSON Root\n\nYou can choose to use ActiveMappers with or without a JSON root.\n\nBy default, root will be enabled, meaning a UserMapper, will generate a JSON prefixed by :\n\n```ruby\n{\n  user: {}\n}\n```\n\n### Custom Root\n\nIf you want to customize the root name, you can use\n\n```ruby\nUserMapper.with(user, root: :hello)\n```\n\nwhich will generate :\n\n```ruby\n{\n  hello: {}\n}\n```\n\n### Rootless\n\nIf you do not want to set any root, use :\n\n```ruby\nUserMapper.with(user, rootless: true)\n```\n\n## Adding your own features to Active Mapper DSL\n\nIf you want to add specific features to the DSL you can reopen `::ActiveMappers::Base` class and add your own methods.\nThe most convenient way to do that is in your Active Mapper initializer following this pattern:\n\n```ruby\nActiveMappers::Setup.configure do |config|\n  ...\nend\n\nmodule ActiveMappers\n  class Base\n    include Rails.application.routes.url_helpers\n\n    def self.my_capitalize_dsl_feature(*params)\n      each do |resource|                              #your mapped resource(s)\n        h = {}\n        params.each do |param|\n          h[param] = resource.try(param)\u0026.capitalize  #your treatment\n        end\n        h                                             #the returned hash will be merged to the mapper result.\n      end\n    end\n  end\nend\n```\n\nand then:\n\n```ruby\nclass UserMapper \u003c ActiveMappers::Base\n  my_capitalize_dsl_feature :civility\nend\n```\n\n## Anything is missing ?\n\nFile an issue\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFidMe%2Factive_mappers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FFidMe%2Factive_mappers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFidMe%2Factive_mappers/lists"}