{"id":13879241,"url":"https://github.com/vasilakisfil/SimpleAMS","last_synced_at":"2025-07-16T15:31:50.627Z","repository":{"id":54182715,"uuid":"67253444","full_name":"vasilakisfil/SimpleAMS","owner":"vasilakisfil","description":"Fast modern plain Ruby serializers using zero dependencies","archived":false,"fork":false,"pushed_at":"2021-11-01T23:06:00.000Z","size":320,"stargazers_count":235,"open_issues_count":2,"forks_count":9,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-07-14T14:21:28.371Z","etag":null,"topics":["rails","ruby","serializer"],"latest_commit_sha":null,"homepage":"","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/vasilakisfil.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":"2016-09-02T20:40:40.000Z","updated_at":"2025-05-13T04:10:39.000Z","dependencies_parsed_at":"2022-08-13T08:31:35.345Z","dependency_job_id":null,"html_url":"https://github.com/vasilakisfil/SimpleAMS","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/vasilakisfil/SimpleAMS","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vasilakisfil%2FSimpleAMS","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vasilakisfil%2FSimpleAMS/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vasilakisfil%2FSimpleAMS/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vasilakisfil%2FSimpleAMS/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vasilakisfil","download_url":"https://codeload.github.com/vasilakisfil/SimpleAMS/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vasilakisfil%2FSimpleAMS/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265521441,"owners_count":23781504,"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":["rails","ruby","serializer"],"created_at":"2024-08-06T08:02:14.825Z","updated_at":"2025-07-16T15:31:50.353Z","avatar_url":"https://github.com/vasilakisfil.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/vasilakisfil/SimpleAMS.svg?branch=master)](https://travis-ci.org/vasilakisfil/SimpleAMS)\n\n# SimpleAMS\n\u003e \"Simple things should be simple and complex things should be possible.\" Alan Kay.\n\nIf we want to interact with modern APIs we should start building modern, flexible libraries\nthat help developers to build such APIs. Modern Ruby serializers, as I always wanted them to be.\n\nYou can find the core ideas, the reasoning behind the architecture, use cases\nand examples [here](https://vasilakisfil.social/blog/2020/01/20/modern-ruby-serializers/).\n\n## Table of contents\n\n1. [Installation](#installation)\n2. [Usage](#usage)\n    * [Simple case](#simple-case)\n        - [Rendering a resource](#rendering-a-resource)\n        - [Rendering a collection](#rendering-a-collection)\n    * [Serializer DSL](#serializer-dsl)\n        - [fields directive](#fields-directive)\n        - [Relations (has_many/has_one/belongs_to)](#relations-has_manyhas_onebelongs_to)\n            * [relations are recursive](#relations-are-recursive)\n            * [embedded content (again recursive)](#embedded-content-again-recursive)\n            * [relation name/type](#relation-nametype)\n        - [value-hashmap type of directives](#value-hashmap-type-of-directives)\n            * [adapter](#adapter)\n            * [primary_id](#primary_id)\n            * [type](#type)\n        - [name-value-hashmap type of directives](#name-value-hashmap-type-of-directives)\n            * [link](#link)\n            * [meta](#meta)\n            * [form](#form)\n            * [generic](#generic)\n            * [group of link/meta/form/generic](#group-of-linksmetasformsgenerics)\n        - [collection directive](#collection-directive)\n    * [Rendering DSL](#rendering-dsl)\n        - [includes vs relations](#includes-vs-relations)\n        - [Rendering collections](#rendering-collections)\n        - [Rendering options with values](#rendering-options-with-values)\n        - [Exposing methods inside the serializer, like helpers](#exposing-methods-inside-the-serializer-like-helpers)\n    * [Extended DSL show off](#extended-dsl-show-off)\n3. [Development](#development)\n4. [Contributing](#contributing)\n\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'simple_ams'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install simple_ams\n\n## Usage\nThe gem's interface has been inspired by ActiveModel Serializers 0.9.2,\n0.10.stable, jsonapi-rb and Ember Data.\nHowever, it has been built for POROs, **has zero dependencies** and does not\nrelate to Rails in any case other than some nostalgia for the (advanced at that\ntime) pre-0.10 ActiveModel Serialiers.\n\nYou can find the core ideas, the reasoning behind the architecture, use cases and examples [here](https://vasilakisfil.social/blog/2020/01/20/modern-ruby-serializers/).\n\n### Simple case\nYou will rarely need all the advanced options. Usually you will have something like that:\n\n```ruby\nclass UserSerializer\n  include SimpleAMS::DSL\n\n  #specify the adapter we want to use\n  adapter SimpleAMS::Adapters::JSONAPI\n\n  #specify the attributes we want to serialize from the given object\n  attributes :id, :name, :email, :created_at, :role\n\n  #specify the type of the resource\n  type :user\n  #specify the name of the collection\n  collection :users\n\n  #specify a relation. Here microposts serves as both a name of the collection\n  #and the name of the method used to retrieve the values of the collection\n  #from the given object\n  has_many :microposts\nend\n```\n\n\n#### Rendering a resource\nThen you can just feed your serializer with data:\n\n```ruby\nSimpleAMS::Renderer.new(user).to_json\n```\n`to_json` first calls `as_json`, which creates a ruby Hash and then `to_json` is called\non top of that hash.\n\nIf you want to filter the available options (defined by the serializer) when you\ninstantiate the serializer, `Renderer` accepts an options hash. In there you can\nthrow pretty much the same DSL:\n\n```ruby\nSimpleAMS::Renderer.new(user, {\n  serializer: UserSerializer, fields: [:id, :name, :email], includes: []\n}).to_json\n```\n\nHere we say that we only want 3 specific fields, and no relations at all.\n\n\n#### Rendering a collection\nRendering a collection is pretty similar, meaning that it reuses the same serializer\nclass, and accepts the same runtime options. The only difference is that you need\nto call a different class.\n\n```ruby\nSimpleAMS::Renderer::Collection.new(users, {\n  serializer: UserSerializer, fields: [:id, :email, :name], includes: []\n}).to_json\n```\n\n### Serializer DSL\nThe serializer is a very robust, yet simple, with a hash-based internal\nrepresentation.\n\n#### fields directive\nFields specify the attributes that the serializer will hold.\nThe values of each attribute is taken by the to-be serialized object,\nunless the serializer has a method of the same name.\n\n```ruby\nfields :id, :name, :email, :created_at, :role\n```\n\n\nUsing `attributes` is also valid, it’s just an [alias](https://github.com/vasilakisfil/SimpleAMS/blob/master/lib/simple_ams/dsl.rb#L130-L137) after all:\n\n```ruby\nattributes :id, :name, :email, :created_at, :role\n```\n\n\nOf course, any field can be overridden by defining a method of the same name\ninside the serializer.\nIn there, you can have access to a method called object which holds the actual\nresource to be serialized:\n\n```ruby\ndef name\n  \"#{object.first_name} #{object.last_name}\"\nend\n```\n\n\n#### Relations (has_many/has_one/belongs_to)\nThese directives allows you to append relations in a resource.\n`has_one` is just an alias of `belongs_to` since there is no real difference in\nAPIs (although internally and in adapters, SimpleAMS knows if you specified the\nrelation using`belongs_to` or `has_one`, making it future proof in case API specs\ndecide to support each one in a different way).\n\n```ruby\nhas_many :microposts\n```\n\nAgain, it can be overridden by defining a method of the same name:\n\n```ruby\ndef microposts\n  Post.where(user_id: object.id).order(:created_at, :desc).limit(10)\nend\n```\n\n##### relations are recursive\nThe relations directives can take the same options as the rendering.\n\n```ruby\n#overriding the serializer\nhas_many :microposts, serializer: CustomPostsSerializer\n#overriding the serializer and fields that should be included\nhas_many :microposts, serializer: CustomPostsSerializer, fields: [:content]\n#overriding the serializer, fields and relations that should be included\nhas_many :microposts, serializer: CustomPostsSerializer, fields: [:content],\n  includes: []\n#overriding the serializer, fields, relations and links\nhas_many :microposts, serializer: CustomPostsSerializer, fields: [:content],\n  includes: [], links: [:self]\n```\n\n\nWhen overriding from the relations directives (or when rendering in general) you\nare able to override any directive defined in the serializer to acquire a subset\n**but never a superset**.\n\n##### embedded content (again recursive)\nSometimes, an annoying spec might define parts of a relation in the main body,\nwhile parts of the relation somewhere else. For instance, JSON:API does that by\nhaving some links in the main body and the rest in the included section.\nThat’s also possible if you pass a block in the relation directive:\n\n```ruby\nhas_many :microposts, serializer: MicropostsSerializer, fields: [:content] do\n  #these goes to a class named `Embedded`, attached to the relation\n  link :self, -\u003e(obj){ \"/api/v1/users/#{obj.id}/relationships/microposts\" }\n  link :related, -\u003e(obj){ [\"/api/v1/users/1\", rel: :user] }\nend\n```\n\nInside that block, you can pass any parameter the original DSL supports and will\nbe stored in an Embedded class under MicropostsSerializer.\nBtw SimpleAMS is smart enough (one of the very few cases that acts like that) to\nfigure out that if a lambda returns something that’s not an array, then this must\nbe the value, while options are just empty.\n\n##### relation name/type\nSometimes, we want to detach the relation’s name from the type. In the previous\nexample `microposts` is the relation name (whatever that means), while the type\nis defined by the `MicropostsSerializer`, unless we override it, which can be\ndone either in the relation serializer itself, or when we use the relation from\nthe parent serializer:\n\n```ruby\nhas_many :microposts, serializer: MicropostsSerializer, fields: [:content], type: :feed do\n  link :self, -\u003e(obj){ \"/api/v1/users/#{obj.id}/relationships/microposts\" }\n  link :related, -\u003e(obj){ [\"/api/v1/users/1\", rel: :user] }\nend\n```\n\nInternally SimpleAMS, differentiates type from name, and usually type is\nsomething that’s semantically stronger (like a relation type) than name.\nYou can even inject the name of the relation using the name option:\n\n```ruby\nhas_many :microposts, serializer: MicropostsSerializer, fields: [:content], type: :feed, name: :posts do\n  link :self, -\u003e(obj){ \"/api/v1/users/#{obj.id}/relationships/microposts\" }\n  link :related, -\u003e(obj){ [\"/api/v1/users/1\", rel: :user] }\nend\n```\n\nAs I said, the name, which is usually the name of the attribute that includes\nthe relation in the JSON format, doesn’t really have any semantic meaning in\nmost specs. At least I haven’t seen any spec to depend on the root attribute\nname of the relation. Instead it’s the type that’s important, because type is\nwhat the [web linking RFC defines](https://tools.ietf.org/html/rfc8288#section-2).\n\n#### value-hashmap type of directives\nThese are directives like adapter. They take a value, and optionally a hashmap,\nwhich are options to be passed down straight to the adapter, hence they are adapter specific.\nSuch options are `primary_id`, `type` and `adapter`\n\nFor instance, for adapter it could be:\n```ruby\nadapter SimpleAMS::Adapters::JSONAPI, root: true\n```\n\nOf course, since we are talking about Ruby here, it would be a huge restriction\nto not allow dynamic value/hashmap combination. Basically any such directive\ncan accept a lambda (generally anything that responds to `call`) and should\nreturn an array where the first part is the value and (optionally) the second part is the\noptions. There is an argument that is passed down to the function/lambda, and\nthat’s the actual object. For instance, to support polymorphic resources you\ncan have the type dynamic:\n\n```ruby\ntype -\u003e(obj, s){ obj.employee? ? [:employee, {}] : [:user, {}]}\n```\n\nOne of the very few times that SimpleAMS acts smart is inside the lambda, that\nif you have only a value (not an Array), it will take that as the value, while\nthe options will be taken by the second argument, after the lambda. So the\nabove is equivalent with:\n\n```ruby\ntype -\u003e(obj, s){ obj.employee? ? :employee : :user}, {}\n```\n\nNote: you shouldn't use that in case of adapter, as that's the definition of UB :P\n\n\n\u003cdetails\u003e\n\u003csummary id=\"adapter\"\u003eadapter\u003c/summary\u003e\n\nSpecifies the adapter to be used. The `adapter` method is the only one that does\nnot support lambda as it's value, as that would be the definition of undefined\nbehavior. If you want to support polymorphic collections, you should use the `type`\ninstead in combination with the `serializer`.\n\n```ruby\n#without options\nadapter SimpleAMS::Adapters::JSONAPI\n#with adapter-specific options\nadapter SimpleAMS::Adapters::JSONAPI, {root: false}\n```\n\nNote that you can even specify your own adapter. Usually you will want to inherit\nfrom an existing adapter (like `SimpleAMS::Adapters::AMS`), but that's not a\nrequirement. All you need is to duck type to 2 methods:\n* `initialize(document, options = {})` be able to accept 2 arguments when your adapter\n  is instantiated. The first one is a document, while the second one is the adapter-specific\n  options (like the `{root: false}`.\n* `as_json` method returns that returns the hash representation of the serialized result\n\nThe conversion of a Hash into raw JSON string is out of the scope of this library.\nBut you will probably want to use the fastest implementation possible like [oj](https://github.com/ohler55/oj).\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary id=\"primary_id\"\u003eprimary_id\u003c/summary\u003e\n\nSpecifies the `primary_id` to be used. There are many API specs that handle the\nidentifier of a resource in a different way than the rest of the attributes.\nJSON:API is one of those.\n\n```ruby\n#without options\nprimary_id :id\n#with adapter-specific options\nadapter :id, {external: true}\n#dynamic\nadapter -\u003e(obj, s) { [obj.class.primary_key, {}]}\n```\n\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary id=\"type\"\u003etype\u003c/summary\u003e\n\nSpecifies the `type` to be used. There are many API specs that handle the\ntype of a resource in a different way than the rest of the attributes.\nJSON:API is one of those.\n\n```ruby\n#without options\ntype :user\n#with adapter-specific options\ntype :user, {polymorphic: false}\n#dynamic\ntype -\u003e(obj, s){ obj.employee? ? [:employee, {}] : [:user, {}]}\n```\n\n\u003c/details\u003e\n\n\n\n#### name-value-hashmap type of directives\nThese are similar to the above, only that they also have an actual value, which\nis converted to a representation through the adapter.\nSuch options are `link`, `meta`, `form` and the most generic directive `generic`.\n\nFor instance, think about a links. According to RFC [8288](https://tools.ietf.org/html/rfc8288#section-2), a link has\n\n* a link context,\n* a link relation type,\n* a link target, and\n* optionally, target attributes\n\nNow, if we wanted to translate that to our serializers, a link could look like:\n```ruby\nlink :feed, '/api/v1/me/feed', {style: :compact}\n```\n\nHere obviously the link context is the serializer itself, the link relation is\nthe feed, and the value is `/api/v1/me/feed`. Now you might say, feed should be\nthe name of the link which is different from the relation type.\nThe relation type could be `microposts`.\nAnd actually, that’s the case for [JSONAPI v1.1](https://jsonapi.org/format/1.1/).\nIn that case, the feed should be treated barely as a name (whatever that means)\nand relation type will be put inside the link options like:\n\n```ruby\nlink :feed, '/api/v1/me/feed', {rel: :microposts, style: :compact}\n```\n\nNote however that this needs to be supported by the adapter you are using.\n\nSimilar to the case of value-hash directives, it is possible to have dynamic\nvalue and options:\n\n```ruby\n#values can be dynamic through lambdas\n#lambdas take arguments the object to be serialized and the instantiated serializer\nlink :feed, -\u003e(obj, s) { [s.api_v1_user_feed_path(user_id: obj.id), {rel: :feed} }\n#if the value inside the lambda is single (no array), the options will be taken from\n#the second argument, after the lambda. So the above is equivelent to:\nlink :feed, -\u003e(obj, s) { s.api_v1_user_feed_path(user_id: obj.id) }, rel: :feed\n```\n\n\u003cdetails\u003e\n\u003csummary id=\"link\"\u003elink\u003c/summary\u003e\n\nSpecifies a link to be used. There are many API specs that handle the links of a\nresource in a special way (JSON:API is one of those).\nYou can specify multiple links, as long as each link name is unique.\n\n```ruby\n#specifying a link with without options\nlink :feed, \"/api/v1/feed\"\n#specifying a link with options\nlink :feed, \"/api/v1/feed\", {rel: :feed, compact: true}\n#values can be dynamic through lambdas\n#lambdas take arguments the object to be serialized and the instantiated serializer\nlink :feed, -\u003e(obj, s) { [s.api_v1_user_feed_path(user_id: obj.id), {rel: :feed, compact: true}] }\n#if the value inside the lambda is single (no array), the options will be taken from\n#the second argument, after the lambda. So the above is equivelent to:\nlink :feed, -\u003e(obj, s) { s.api_v1_user_feed_path(user_id: obj.id) }, rel: :feed, compact: true\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary id=\"meta\"\u003emeta\u003c/summary\u003e\n\nSpecifies a meta to be used. There are many API specs that handle the metas of a\nresource in a special way (JSON:API is one of those).\nYou can specify multiple metas, as long as each link name is unique.\n\n```ruby\n#specifying a meta with without options\nmeta :total_count, 1\n#specifying a meta with options\nmeta :total_count, 1, {compact: true}\n#values can be dynamic through lambdas\n#lambdas take arguments the object to be serialized and the instantiated serializer\n#in this case an object is apparently a collection/array\nmeta :total_count, -\u003e(obj, s) { [obj.count, {compact: true}] }\n#if the value inside the lambda is single (no array), the options will be taken from\n#the second argument, after the lambda. So the above is equivelent to:\nmeta :total_count, -\u003e(obj, s) { obj.count }, {compact: true}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary id=\"form\"\u003eform\u003c/summary\u003e\n\nSpecifies a form to be used. Unfortunately, there are very few API specs that\nhandle forms (the [Ion hypermedia type](https://ionspec.org) is one of those).\nYou can specify multiple forms, as long as each link name is unique.\n\n```ruby\n#specifying a form with without options\nform :upload, {method: :get, url: \"/api/v1/submit\"}\n#specifying a form with options\nform :upload, {method: :get, url: \"/api/v1/submit\"}, compact: true\n#values can be dynamic through lambdas\n#lambdas take arguments the object to be serialized and the instantiated serializer\nform :upload, -\u003e(obj, s) { [obj.class.upload_form_options, {compact: true}] }\n#if the value inside the lambda is single (no array), the options will be taken from\n#the second argument, after the lambda. So the above is equivelent to:\nform :upload, -\u003e(obj, s) { obj.class.upload_form_options }, {compact: true}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary id=\"generic\"\u003egeneric\u003c/summary\u003e\n\nSpecifies a generic to be used. A generic is just a placeholder for extensions\nthat are unknown to SimpleAMS (but maybe they make a lot of sense to you ^_^)\n\n```ruby\n#specifying a generic with without options\ngeneric :pagination, :extended\n#specifying a form with options\ngeneric :pagination, :extended, compact: false\n#values can be dynamic through lambdas\n#lambdas take arguments the object to be serialized and the instantiated serializer\ngeneric :pagination, -\u003e(obj, s) { [obj.class.pagination_type, {compact: false}] }\n#if the value inside the lambda is single (no array), the options will be taken from\n#the second argument, after the lambda. So the above is equivelent to:\ngeneric :pagination, -\u003e(obj, s) { obj.class.pagination_type }, {compact: false}\n```\n\n\u003c/details\u003e\n\n##### group of links/metas/forms/generics\n\nEach of the aforementioned options comes with a plural form as well.\nFor instance, if we want to specify multiple `links` at the same time:\n\n```ruby\nlinks {\n  self: ['/api/v1/me', {rel: :user}]\n  feed: ['/api/v1/me/feed', {rel: :feed}]\n}\n```\n\nor if we want to specify multiple `metas`:\n```ruby\nmetas {\n  total_count: -\u003e(obj){ obj.count }\n  pages: -\u003e(obj){ obj.pages_count }\n}\n```\n\nSame goes for `forms` and `generics`.\n\n#### collection directive\nSimpleAMS has a unique ability to allow you specify different options when you are\nrendering a collection. In its most simple use case it specified the plural name\nof the resource, used when rendering a collection:\n\n```ruby\ncollection :users\n```\n\nIt’s needed, if your adapter serializes the collection using a root element.\nBut it can do much more than that: it allows you to define directives on the\ncollection level. For instance, if you want to have a link that should be\napplied **only** to the collection level and not to each resource of the collection,\nthen you need to define it inside the collection’s block:\n\n```ruby\ncollection :users do\n  link :self, \"/api/v1/users\"\nend\n```\n\nOr if we also want to have the total count of the collection, that should go in\nthere actually:\n\n```ruby\ncollection :users do\n  link :self, \"/api/v1/users\"\n  meta :count, -\u003e(collection, s) { collection.count }\nend\n```\n\nAgain, inside that block you can define using the regular DSL, whatever you\nwould define in the resource level. It’s just yet another level of recursion\nsince, the same things that I show you here can be applied in the collection\nlevel inside the block. For instance, in theory (and if the adapter supports\nit), you can specify relations that apply only to the collection level:\n\n```ruby\nclass UserSerializer\n  include SimpleAMS::DSL\n\n  adapter SimpleAMS::Adapters::JSONAPI\n\n  attributes :id, :name, :email, :created_at, :role\n\n  type :user\n  collection :users do\n    link :self, \"/api/v1/users\"\n    meta :count, -\u003e(collection, s) { collection.count }\n\n    has_one :s3_uploader #whatever that means :P\n  end\n\n  has_many :microposts\nend\n```\n\n### Rendering DSL\nWhen rendering a resource, it should be straightforward:\n\n```ruby\nSimpleAMS::Renderer.new(user, { serializer: UserSerializer }).to_json\n```\n\nAll you need is to specify a serializer. In the example above, the resulted\nresource is a reflection of what is defined inside the serializer.\nHowever, the serializer acts as a filtering mechanism, meaning that you can\noverride anything the serializer defines, given that the result creates a\n**subset and not a superset** (any superset options will be ignored).\n\nFor instance, you can override the type during rendering:\n\n```ruby\nSimpleAMS::Renderer.new(user, {\n  serializer: UserSerializer, type: :person\n}).to_json\n```\nor you can override the relations, and specify that you don’t want to include\nany relation defined in the serializer:\n\n```ruby\nSimpleAMS::Renderer.new(user, {\n  serializer: UserSerializer, includes: []\n}).to_json\n```\n\nor specify exactly what fields you want:\n\n```ruby\nSimpleAMS::Renderer.new(user, {\n  serializer: UserSerializer, fields: [:id, :email, :name, :created_at]\n}).to_json\n```\n\nor even specify the links subset that you want:\n\n```ruby\nSimpleAMS::Renderer.new(user, {\n  serializer: UserSerializer, fields: [:id, :email, :name, :created_at],\n  links: [:self, :comments, :posts]\n}).to_json\n```\n\nand the list goes on.. basically the rendering DSL is identical\n\n#### includes vs relations\nThere might be some confusion between `includes` and `relations`, so to clear things up:\n* `includes`: specifies which relations you want to include, out of the available relations.\n* `relations`: specifies the available relations, so it's not just an array of\n  symbols, but rather full relation objects which are generated through the dsl.\n  The raw representation of relations is an array of objects where each object\n  is `[relation_type, name, options, embedded_options]`. Here\n   `relation_type` is the type of the relation (`has_many`, `belongs_to` etc),\n   `name` is the name of the relation (like users), `options`, any relation options,\n   and `embedded_options` relevant to embedded options.\n\nSo when rendering, if you don't want any relations at all, the correct way is to\nspecify `includes: []`. In practice you can use `relations: []` as well, but that\nwill mean that the serializer has no relations at all (takes precedence over\n`includes`). But that's not the correct way to do it. For instance, thing about\nanother scenario: you want to specify only one relation, the `feed` relation.\nWith `includes` you would have `includes: [:feed]`. With relations, you would\nhave to specify the relation at runtime (\n`relations: [[:has_one, :feed, {serializer: FeedSerializer, fields: [:id, :content]}, {}]]`\n) and then also specify that you only want that: `includes: [:feed]`.\n\nIn general, there is no reason why you should use `relations` at rendering time,\ninstead you should leave that to the serializer, and only specify the `includes`.\n\nBtw you might have noticed `includes` only as a rendering option, but SimpleAMS\nDSL is used all over the place, and actually it's a serializer option as well\n(just that it's not very useful ^_^).\n\n\n#### Rendering collections\nRendering a collection is similar, only that you need to call\n`SimpleAMS::Renderer::Collection` instead of just `SimpleAMS::Renderer`:\n\n```ruby\nSimpleAMS::Renderer::Collection.new(users, {\n  serializer: UserSerializer, fields: [:id, :email, :name, :created_at],\n  links: [:self, :comments, :posts]\n}).to_json\n```\n\nNote that even with collection, by default everything goes to the resource.\nIf you need to specify options for the collection itself, you need to use the\ncollection key. For instance, having some metas inside the collection:\n\n```ruby\nSimpleAMS::Renderer::Collection.new(users, {\n  serializer: UserSerializer, fields: [:id, :email, :name, :created_at],\n  links: [:self, :comments, :posts],\n  collection: {\n    metas: [:total_count]\n  }\n}).to_json\n```\n\n#### Rendering options with values\nIf you want to specify the actual values when rendering the resource, rather\nthan taking into account the serializer, you can inject a hashmap:\n\n```ruby\nSimpleAMS::Renderer::Collection.new(users, {\n  serializer: UserSerializer, fields: [:id, :email, :name, :created_at],\n  links: [:self, :comments, :posts],\n  collection: {\n    metas: {\n      total_count: users.count,\n  }\n}).to_json\n```\n\nOf course, you can also pass a lambda there, but not sure what’s the point since\nthe lambda parameter is the resource that you already try to render so it’s not\ngoing to give you anything more (and will be slower actually).\n\n#### Exposing methods inside the serializer, like helpers\nWhen rendering you can expose a couple of objects in the serializer:\n\n```ruby\nSimpleAMS::Renderer::Collection.new(users, {\n  serializer: UserSerializer, fields: [:id, :email, :name, :created_at],\n  #exposing helpers that will be available inside the serializer\n  expose: {\n    #a class\n    current_user: User.first\n    #or a module\n    helpers: CommonHelpers\n  },\n}).to_json\n```\n\nThe expose attribute is also available through DSL, although usually that’s not\nvery useful. Just wanted to mentions that there is actually parity on everything,\nsince everything has been built on the same building blocks :)\n\n### Extended DSL show off\nHere is an extended example of the DSL. It's not a real use case of course, but\nshows what's possible with SimpleAMS and its powerful DSL.\n\n```ruby\n{\n  #the primary id of the record(s), used mostly by the underlying adapter (like JSONAPI)\n  primary_id: :id,\n  #the type of the record, used mostly by the underlying adapter (like JSONAPI)\n  type: :user,\n  #which relations should be included\n  includes: [:posts, videos: [:comments]],\n  #which fields for each relation should be included\n  fields: [:id, :name, posts: [:id, :text], videos: [:id, :title, comments: [:id, :text]]] #overrides includes when association is specified\n  relations: [\n    [:belongs_to, :company, {\n        serializer: CompanySerializer,\n        fields: Company.column_names.map(\u0026:to_sym)\n      }\n    ],\n    [:has_many, :followers, {\n        serializer: UserSerializer,\n        fields: User.column_names.map(\u0026:to_sym)\n      }\n    ],\n  ]\n  #the serializer that should be used\n  #makes sense to use it when initializing the Renderer\n  serializer: UserSerializer,\n  #can also be a lambda, in case of polymorphic records, ideal for ArrayRenderer\n  serializer: -\u003e(obj, s){ obj.employee? ? EmployeeSerializer : UserSerializer }\n  #specifying the underlying adapter. This cannot be a lambda in case of ArrayRenderer,\n  #but can take some useful options that are passed down straight to the adapter class.\n  adapter: SimpleAMS::Adapters::AMS, root: true\n  #the links data\n  links: {\n    #can be a simple string\n    root: '/api/v1'\n    #a string with some options (relation and target attributes as defined by RFC8288\n    #however, you can also pass adapter-specific attributes\n    posts: \"/api/v1/posts/\", rel: :posts,\n    #it can also be a lambda that takes the resource to be rendered as a param\n    #when the lambda is called, it should return the array structure above\n    self: -\u003e(obj, s) { [\"/api/v1/users/#{obj.id}\", rel: :user] }\n  },\n  #the meta data, same as the links data (available in adapters even for single records)\n  metas: {\n    type: -\u003e(obj, s){ obj.employee? ? :employee : :user}\n    #meta can take arbitrary options as well\n    authorization: :oauth, type: :bearer_token\n  },\n  #the form data, same as the links/metas data (available in adapters even for single records)\n  forms: {\n    update: -\u003e(obj, s){ User::UpdateForm.for(obj)}\n    follow: -\u003e(obj, s){ User::FollowForm.for(obj)}\n  },\n  #collection parameters, used only in ArrayRenderer\n  collection: {\n    links: {\n      root: '/api/v1'\n    },\n    metas: {\n      pages: -\u003e(obj, s) { [obj.pages, collection: true]},\n      current_page: -\u003e(obj, s) { [obj.current_page, collection: true] },\n      previous_page: -\u003e(obj, s) { [obj.previous_page, collection: true] },\n      next_page: -\u003e(obj, s) { [obj.next_page, collection: true] },\n      max_per_page: 50,\n    },\n    #creating a resource goes in the collection route (users/), hence inside collection options ;)\n    forms: {\n      create: -\u003e(obj){ User::CreateForm.for(obj)}\n    },\n  }\n  #exposing helpers that will be available inside the seriralizer\n  expose: {\n    #a class\n    current_user: User.first\n    #or a module\n    helpers: CommonHelpers\n  },\n}\n```\n\n## Development\n\nAfter 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.\n\nTo 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 [rubygems.org](https://rubygems.org).\n\n## Contributing\nBut reports are very welcome at https://github.com/vasilakisfil/SimpleAMS. Please add as much info as you can (serializer and Renderer input)\nso that we can easily track down the bug.\n\nPull requests are also very welcome on GitHub at https://github.com/vasilakisfil/SimpleAMS.\nHowever, to keep the code's sanity (AMS I am looking to you), **I will be very picky** on the code style and design,\nto match (my) existing code characteristics.\nBecause at the end of the day, it's gonna be me who will maintain this thing.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvasilakisfil%2FSimpleAMS","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvasilakisfil%2FSimpleAMS","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvasilakisfil%2FSimpleAMS/lists"}