{"id":13507329,"url":"https://github.com/stavro/remodel","last_synced_at":"2025-04-05T16:10:41.707Z","repository":{"id":57543110,"uuid":"30941834","full_name":"stavro/remodel","owner":"stavro","description":":necktie: An Elixir presenter package used to transform map structures.  \"ActiveModel::Serializer for Elixir\"","archived":false,"fork":false,"pushed_at":"2019-04-03T14:46:38.000Z","size":13,"stargazers_count":141,"open_issues_count":4,"forks_count":17,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-29T15:11:21.427Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/stavro.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2015-02-17T22:44:47.000Z","updated_at":"2024-07-15T19:45:21.000Z","dependencies_parsed_at":"2022-09-26T18:31:28.781Z","dependency_job_id":null,"html_url":"https://github.com/stavro/remodel","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/stavro%2Fremodel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stavro%2Fremodel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stavro%2Fremodel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stavro%2Fremodel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stavro","download_url":"https://codeload.github.com/stavro/remodel/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247361695,"owners_count":20926643,"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":[],"created_at":"2024-08-01T02:00:31.278Z","updated_at":"2025-04-05T16:10:41.663Z","avatar_url":"https://github.com/stavro.png","language":"Elixir","funding_links":[],"categories":["Algorithms and Data structures"],"sub_categories":[],"readme":"# Remodel #\n\n[![Build Status](https://semaphoreci.com/api/v1/projects/8c5cf51e-a3e8-47a6-8b91-632114de4fba/480819/badge.svg)](https://semaphoreci.com/stavro/remodel)\n\nRemodel is an Elixir _presenter_ package used to transform data structures.  This is especially useful when a desired representation doesn't match the schema defined within the database.\n\nIn particular, Remodel enables:\n\n * Renaming or aliasing attributes to change the name from the model\n * Arbitrary attributes based on combining data in an object\n * Inclusion of attributes only if a certain conditions have been met\n * Addition of root nodes on arrays or instances\n * Easy conversion to a list representation (eg, for CSVs)\n\n## Installation ##\n\nInstall Remodel as a hex dependency in your `.mix` file:\n\n```elixir\n  defp deps do\n    [{:remodel, \"~\u003e 0.0.4\"}]\n  end\n```\n\nand run `mix deps.get` to install the package.\n\n## Overview ##\n\nYou can use Remodel to prepare an Elixir Map (or anything that identifies as a Map, such as a Struct or an Ecto.Model instance) to be serialized through a JSON, XML, or CSV serializer.\n\nWith Remodel, the data is derived from maps (ORM-agnostic) and the representation of the API output is described by attributes and methods using a simple Elixir DSL. This allows you to keep your data separated from the structure you wish to output.\n\nOnce you have installed Remodel (explained above), you can construct a Remodel schema and then render the model\nfrom your Plug or Phoenix applications from the controller (or route) very easily. Using [Phoenix](http://phoenixframework.org) and [Ecto](https://github.com/elixir-lang/ecto) as an example, assuming you have a `Post` model filled with blog posts, you can render an API representation by creating a route:\n\n```elixir\n# web/controllers/post_controller.ex\ndef index(conn, params) do\n  posts = Repo.all(from p in Post,\n                   select: p,\n                   order_by: [asc: p.id],\n                   preload: :author)\n\n  json(conn, PostSerializer.to_map(posts))\nend\n```\n\nThen we can create the following Remodel schema to express the desired API output of `posts`:\n\n```elixir\n# web/serializers/post_serializer.ex\ndefmodule PostSerializer do\n  use Remodel\n\n  attributes [:id, :title, :author_name]\n\n  def author_name(record) do\n    \"#{record.author.first_name} #{record.author.last_name}\"\n  end\nend\n```\n\nWhich would output the following JSON when visiting the appropriate action:\n\n```js\n[\n  {\n    \"id\" : 1,\n    \"title\": \"Introducing Remodel\",\n    \"author_name\" : \"Sean Stavropoulos\"\n  },\n  {\n    \"id\" : 2,\n    ...\n  }\n]\n```\n\n### Attributes ###\n\nBasic usage to define a few simple attributes for the response:\n\n```elixir\nattributes [:id, :foo, :bar]\n```\n\nor use with aliased attributes:\n\n```elixir\n# Take the value of model attribute `foo` and name the node `bar`\nattribute :foo, as: :bar\n# =\u003e %{bar: 5}\n```\n\nor show attributes only if a condition is true:\n\n```elixir\n# atom representing a method in the serializer\nattribute :foo, if: :published\n\ndef published(record) do\n  !is_nil(record.published_at)\nend\n```\n\n### Root Nodes ###\n\nSome JSON configurations will include a single root node named after the object's type or an abstractable name for the object depending on context.  Remodel can accomodate these JSON types on an individual, or a default basis.\n\nExample usage of root nodes:\n\n```elixir\ndefmodule UserSerializer do\n  use Remodel\n  @array_root :users\n\n  attribute :id\nend\n\n[%{id: 1}, %{id: 2}] |\u003e UserSerializer.to_map #=\u003e  %{users: [%{id: 1}, %{id: 2}]}\n[%{id: 1}, %{id: 2}] |\u003e UserSerializer.to_map(array_root: :superusers) #=\u003e  %{superusers: [%{id: 1}, %{id: 2}]}\n```\n\nExample usage of instance root nodes:\n\n```elixir\ndefmodule UserSerializer do\n  use Remodel\n  @instance_root :user\n\n  attribute :id\nend\n\n[%{id: 1}, %{id: 2}] |\u003e UserSerializer.to_map #=\u003e  %{[%{user: %{id: 1}}, %{user: %{id: 2}}]}\n[%{id: 1}, %{id: 2}] |\u003e UserSerializer.to_map(instance_root: :user) #=\u003e  %{[%{user: %{id: 1}}, %{user: %{id: 2}}]}\n```\n\n### List Generation ###\n\nRemodel includes two formatters to transform map structures.  The first, as visible in the above examples, converts an input map to an output map.  Remodel also includes a transformation useful for working with list-based tools, such as most CSV generators.\n\n```elixir\ndefmodule UserSerializer do\n  use Remodel\n  attributes [:id, :full_name]\n\n  def full_name(record) do\n    \"#{record.first_name} #{record.last_name}\"\n  end\nend\n\n# Save all users from the database to a CSV\nRepo.all(from u in User, select: %{id: u.id, first_name: u.first_name, last_name: u.last_name})\n|\u003e UserSerializer.to_list(headers: true)  # =\u003e [[:id, :full_name], [1, \"Joe Armstrong\"], ...]\n|\u003e CSVLixir.write\n|\u003e File.write(\"foo.csv\")\n:ok\n```\n\n### Scope ###\n\nSome serialization configurations may depend on data outside of the serialized resource (for example, utilizing information about the currently authenticated user).  Remodel allows passing in any scope data type which can be referenced throughout the serialization process.\n\nHere's an example to only display a particular attribute if a user is requesting their own serialized user object:\n\n```elixir\ndefmodule UserSerializer do\n  use Remodel\n\n  attribute :id\n  attribute :email, if: :personal_info_visible?\n\n  def personal_info_visible?(record, scope) do\n    scope.id == record.id\n  end\nend\n\n# Assume a current_user variable has been set to the authenticated user\ncurrent_user = Repo.get(User, 1)\n\nRepo.one(from u in User, where: u.id == 1, select: u, limit: 1) |\u003e UserSerializer.to_map(scope: current_user)\n#=\u003e %{id: 1, email: \"email@domain.com\"}\n\nRepo.one(from u in User, where: u.id == 2, select: u, limit: 1) |\u003e UserSerializer.to_map(scope: current_user)\n#=\u003e %{id: 2}\n```\n\nAll attribute and conditional functions must accept either one argument (the record), or two arguments (the record, and any given scope)\n### Meta ###\n\nOften when you have pagination with your data you use a meta field for showing additional information (current_page, total_pages, etc).\n`meta` will only be included if you have a Serializer that supports `root`\n\n```elixir\ndefmodule UserSerializer do\n  use Remodel\n  @array_root :users\n\n  attribute :id\nend\n\n[%{id: 1}, %{id: 2}] |\u003e UserSerializer.to_map(meta: %{ page: 1 } #=\u003e  %{users: [%{id: 1}, %{id: 2}], \"meta\" =\u003e %{ page: 1}}\n```\n## Roadmap ##\n\nRemodel is a brand new project, and I am hopeful that there is a place in the Elixir ecosystem for such a tool.  In no particular order of importance, some features I would like to add are:\n\n * Typespecs and Documentation\n * DRY up test suite\n * Anonymous functions for `if` clauses: (eg: `attribute :foo, if: fn(record) -\u003e record.bar end`)\n * Benchmarking and performance optimizations\n * Handling of Ecto Associations\n\n## Inspirations ##\n\nThere are a few excellent libraries that helped inspire Remodel and they are listed below:\n\n * [Active Model Serializers](https://github.com/rails-api/active_model_serializers)\n * [RABL](https://github.com/nesquena/rabl)\n\n## License\n\nCopyright 2015 Sean Stavropoulos\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstavro%2Fremodel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstavro%2Fremodel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstavro%2Fremodel/lists"}