{"id":13546060,"url":"https://github.com/trailblazer/roar","last_synced_at":"2025-12-14T23:56:13.495Z","repository":{"id":1273484,"uuid":"1212717","full_name":"trailblazer/roar","owner":"trailblazer","description":"Parse and render REST API documents using representers.","archived":false,"fork":false,"pushed_at":"2023-01-17T15:54:37.000Z","size":1092,"stargazers_count":1846,"open_issues_count":18,"forks_count":137,"subscribers_count":46,"default_branch":"master","last_synced_at":"2025-12-01T06:20:49.216Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://www.trailblazer.to/gems/roar","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/trailblazer.png","metadata":{"files":{"readme":"README.markdown","changelog":"CHANGES.markdown","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2011-01-01T18:22:16.000Z","updated_at":"2025-11-08T23:50:30.000Z","dependencies_parsed_at":"2023-02-10T10:31:23.628Z","dependency_job_id":null,"html_url":"https://github.com/trailblazer/roar","commit_stats":null,"previous_names":["apotonick/roar"],"tags_count":49,"template":false,"template_full_name":null,"purl":"pkg:github/trailblazer/roar","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailblazer%2Froar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailblazer%2Froar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailblazer%2Froar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailblazer%2Froar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/trailblazer","download_url":"https://codeload.github.com/trailblazer/roar/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailblazer%2Froar/sbom","scorecard":{"id":896618,"data":{"date":"2025-08-11","repo":{"name":"github.com/trailblazer/roar","commit":"365b28be85b61c43181f2868cb8f452f5945b4cf"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":2,"reason":"Found 6/21 approved changesets -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Warn: no topLevel permission defined: .github/workflows/ci_jruby.yml:1","Warn: no topLevel permission defined: .github/workflows/ci_legacy.yml:1","Warn: no topLevel permission defined: .github/workflows/ci_truffleruby.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/trailblazer/roar/ci.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/trailblazer/roar/ci.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci_jruby.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/trailblazer/roar/ci_jruby.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci_jruby.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/trailblazer/roar/ci_jruby.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci_legacy.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/trailblazer/roar/ci_legacy.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci_legacy.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/trailblazer/roar/ci_legacy.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci_truffleruby.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/trailblazer/roar/ci_truffleruby.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci_truffleruby.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/trailblazer/roar/ci_truffleruby.yml/master?enable=pin","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   4 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 15 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-24T13:57:29.632Z","repository_id":1273484,"created_at":"2025-08-24T13:57:29.633Z","updated_at":"2025-08-24T13:57:29.633Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27618405,"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","status":"online","status_checked_at":"2025-12-09T02:00:09.185Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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-01T12:00:30.859Z","updated_at":"2025-12-14T23:56:13.447Z","avatar_url":"https://github.com/trailblazer.png","language":"Ruby","readme":"# Roar\n\n_Resource-Oriented Architectures in Ruby._\n\n[![Gitter Chat](https://badges.gitter.im/trailblazer/chat.svg)](https://gitter.im/trailblazer/chat)\n[![TRB Newsletter](https://img.shields.io/badge/TRB-newsletter-lightgrey.svg)](http://trailblazer.to/newsletter/)\n[![Build Status](https://github.com/trailblazer/roar/actions/workflows/ci.yml/badge.svg)](https://github.com/trailblazer/roar/actions)\n[![Gem Version](https://badge.fury.io/rb/roar.svg)](http://badge.fury.io/rb/roar)\n\n## Table of Contents\n\n  * [Introduction](#introduction)\n  * [Representable](#representable)\n  * [Installation](#installation)\n    * [Dependencies](#dependencies)\n  * [Defining Representers](#defining-representers)\n  * [Rendering](#rendering)\n  * [Parsing](#parsing)\n  * [Module Representers](#module-representers)\n  * [Collections](#collections)\n  * [Nesting](#nesting)\n  * [Inline Representer](#inline-representer)\n  * [Syncing Objects](#syncing-objects)\n  * [Coercion](#coercion)\n  * [More Features](#more-features)\n  * [Hypermedia](#hypermedia)\n  * [Passing Options](#passing-options)\n  * [Specify Decorator](#specify-decorator)\n  * [Consuming Hypermedia](#consuming-hypermedia)\n  * [Media Formats](#media-formats)\n  * [HAL\\-JSON](#hal-json)\n    * [Hypermedia](#hypermedia-1)\n    * [Nesting](#nesting-1)\n  * [JSON API](#json-api)\n  * [Client\\-Side Support](#client-side-support)\n  * [HTTP Support](#http-support)\n    * [HTTPS](#https)\n    * [Basic Authentication](#basic-authentication)\n    * [Client SSL certificates](#client-ssl-certificates)\n    * [Request customization](#request-customization)\n    * [Error handling](#error-handling)\n  * [XML](#xml)\n  * [Support](#support)\n  * [License](#license)\n\n## Introduction\n\nRoar is a framework for parsing and rendering REST documents. Nothing more.\n\nRepresenters let you define your API document structure and semantics. They allow both rendering representations from your models _and_ parsing documents to update your Ruby objects. The bi-directional nature of representers make them interesting for both server and client usage.\n\nRoar comes with built-in JSON, JSON-HAL and XML support. JSON API support is available via the [JSON API](https://github.com/trailblazer/roar-jsonapi) gem. Its highly modular architecture provides features like coercion, hypermedia, HTTP transport, client caching and more.\n\nRoar is completely framework-agnostic and loves being used in web kits like Rails, Hanami, Sinatra, Roda, etc. If you use Rails, consider [roar-rails](https://github.com/apotonick/roar-rails) for an enjoyable integration.\n\n## Representable\n\nRoar is just a thin layer on top of the [representable](https://github.com/trailblazer/representable) gem. While Roar gives you a DSL and behaviour for creating hypermedia APIs, representable implements all the mapping functionality.\n\nIf in need for a feature, make sure to check the [representable API docs](https://github.com/trailblazer/representable) first.\n\n## Installation\n\nThe roar gem runs with all Ruby versions \u003e= 1.9.3.\n\n```ruby\ngem 'roar'\n```\n\nTo use roar with Ruby versions \u003c 2.2.0, add a version pin to your Gemfile:\n\n```ruby\ngem 'sinatra', '~\u003e 1.4'\n```\n\n### Dependencies\n\nRoar does not bundle dependencies for JSON and XML.\n\nIf you want to use JSON, add the following to your `Gemfile`:\n\n```ruby\ngem 'multi_json'\n```\n\nIf you want to use XML, add the following to your `Gemfile`:\n\n```ruby\ngem 'nokogiri'\n```\n\n\n## Defining Representers\n\nLet's see how representers work. They're fun to use.\n\n```ruby\nrequire 'roar/decorator'\nrequire 'roar/json'\n\nclass SongRepresenter \u003c Roar::Decorator\n  include Roar::JSON\n\n  property :title\nend\n```\n\nAPI documents are defined using a decorator class. You can define plain attributes using the `::property` method.\n\nNow let's assume we'd have `Song` which is an `ActiveRecord` class. Please note that Roar is not limited to ActiveRecord. In fact, it doesn't really care whether it's representing ActiveRecord, `Sequel::Model` or just an OpenStruct instance.\n\n```ruby\nclass Song \u003c ActiveRecord::Base\nend\n```\n\n## Rendering\n\nTo render a document, you apply the representer to your model.\n\n```ruby\nsong = Song.new(title: \"Medicine Balls\")\n\nSongRepresenter.new(song).to_json #=\u003e {\"title\":\"Medicine Balls\"}\n```\n\nHere, the `song` objects gets wrapped (or \"decorated\") by the decorator. It is treated as immutable - Roar won't mix in any behaviour.\n\n## Parsing\n\nThe cool thing about representers is: they can be used for rendering and parsing. See how easy updating your model from a document is.\n\n```ruby\nsong = Song.new(title: \"Medicine Balls\")\n\nSongRepresenter.new(song).from_json('{\"title\":\"Linoleum\"}')\nsong.title #=\u003e Linoleum\n```\n\nUnknown attributes in the parsed document are simply ignored, making half-baked solutions like `strong_parameters` redundant.\n\n\n## Module Representers\n\n**Module Representers are deprecated in Roar 1.1 and will be removed in Roar 2.0.**\n\nIn place of inheriting from `Roar::Decorator`, you can also extend a singleton object with a representer module. Decorators and module representers actually have identical features. You can parse, render, nest, go nuts with both of them.\n\n\n```ruby\nsong = Song.new(title: \"Fate\")\nsong.extend(SongRepresenter)\n\nsong.to_json #=\u003e {\"title\":\"Fate\"}\n```\n\nHere, the representer is injected into the actual model and gives us a new `#to_json` method.\n\nThis also works both ways.\n\n```ruby\nsong = Song.new\nsong.extend(SongRepresenter)\n\nsong.from_json('{\"title\":\"Fate\"}')\nsong #=\u003e {\"title\":\"Fate\"}\n```\n\nIt's worth noting though that many people dislike `#extend` due to well-known performance issues and object pollution. As such this approach is no longer recommended. In this README we'll use decorators to illustrate this library.\n\n\n## Collections\n\nRoar (or rather representable) also allows mapping collections in documents.\n\n```ruby\nclass SongRepresenter \u003c Roar::Decorator\n  include Roar::JSON\n\n  property :title\n  collection :composers\nend\n```\n\nWhere `::property` knows how to handle plain attributes, `::collection` does lists.\n\n```ruby\nsong = Song.new(title: \"Roxanne\", composers: [\"Sting\", \"Stu Copeland\"])\n\nSongRepresenter.new(song).to_json #=\u003e {\"title\":\"Roxanne\",\"composers\":[\"Sting\",\"Stu Copeland\"]}\n```\n\nAnd, yes, this also works for parsing: `from_json` will create and populate the array of the `composers` attribute.\n\n\n## Nesting\n\nNow what if we need to tackle with collections of `Song`s? We need to implement an `Album` class.\n\n```ruby\nclass Album \u003c ActiveRecord::Base\n  has_many :songs\nend\n```\n\nAnother representer to represent.\n\n```ruby\nclass AlbumRepresenter \u003c Roar::Decorator\n  include Roar::JSON\n\n  property :title\n  collection :songs, extend: SongRepresenter, class: Song\nend\n```\n\nBoth `::property` and `::collection` accept options for nesting representers into representers.\n\nThe `extend:` option tells Roar which representer to use for the nested objects (here, the array items of the `album.songs` field). When parsing a document `class:` defines the nested object type.\n\nConsider the following object setup.\n\n```ruby\nalbum = Album.new(title: \"True North\")\nalbum.songs \u003c\u003c Song.new(title: \"The Island\")\nalbum.songs \u003c\u003c Song.new(title: \"Changing Tide\")\n```\n\nYou apply the `AlbumRepresenter` and you get a nested document.\n\n```ruby\nAlbumRepresenter.new(album).to_json #=\u003e {\"title\":\"True North\",\"songs\":[{\"title\":\"The Island\"},{\"title\":\"Changing Tide\"}]}\n```\n\nThis works vice-versa.\n\n```ruby\nalbum = Album.new\n\nAlbumRepresenter.new(album).from_json('{\"title\":\"Indestructible\",\"songs\":[{\"title\":\"Tropical London\"},{\"title\":\"Roadblock\"}]}')\n\nputs album.songs[1] #=\u003e #\u003cSong title=\"Roadblock\"\u003e\n```\n\nThe nesting of two representers can map composed object as you find them in many many APIs.\n\nIn case you're after virtual nesting, where a nested block in your document still maps to the same outer object, [check out the `::nested` method](https://github.com/trailblazer/representable#document-nesting).\n\n## Inline Representer\n\nSometimes you don't wanna create two separate representers - although it makes them reusable across your app. Use inline representers if you're not intending this.\n\n```ruby\nclass AlbumRepresenter \u003c Roar::Decorator\n  include Roar::JSON\n\n  property :title\n\n  collection :songs, class: Song do\n    property :title\n  end\nend\n```\n\nThis will give you the same rendering and parsing behaviour as in the previous example with just one module.\n\n\n## Syncing Objects\n\nUsually, when parsing, nested objects are created from scratch. If you want nested objects to be updated instead of being newly created, use `parse_strategy:`.\n\n```ruby\nclass AlbumRepresenter \u003c Roar::Decorator\n  include Roar::JSON\n\n  property :title\n\n  collection :songs, extend: SongRepresenter, parse_strategy: :sync\nend\n```\n\nThis will advise Roar to update existing `songs`.\n\n```ruby\nalbum.songs[0].object_id #=\u003e 81431220\n\nAlbumRepresenter.new(album).from_json('{\"title\":\"True North\",\"songs\":[{\"title\":\"Secret Society\"},{\"title\":\"Changing Tide\"}]}')\n\nalbum.songs[0].title #=\u003e Secret Society\nalbum.songs[0].object_id #=\u003e 81431220\n```\nRoar didn't create a new `Song` instance but updated its attributes, only.\n\nWe're currently [working on](https://github.com/trailblazer/roar/issues/85) better strategies to easily implement `POST` and `PUT` semantics in your APIs without having to worry about the nitty-gritties.\n\n\n## Coercion\n\nRoar provides coercion with the [dry-types](https://dry-rb.org/gems/dry-types/) gem.\n\n```ruby\nrequire 'roar/coercion'\nrequire 'roar/json'\n\nclass SongRepresenter \u003c Roar::Decorator\n  include Roar::JSON\n  include Roar::Coercion\n\n  property :title\n  property :released_at, type: Types::DateTime\nend\n```\n\nThe `:type` option allows to set a dry-types-compatible type.\n\n```ruby\nsong = Song.new\n\nSongRepresenter.new(song).from_json('{\"released_at\":\"1981/03/31\"}')\n\nsong.released_at #=\u003e 1981-03-31T00:00:00+00:00\n```\n\n\n## More Features\n\nRoar/representable gives you many more mapping features like renaming attributes, wrapping, passing options, etc. See the [representable documentation](http://trailblazer.to/gems/representable/3.0/api.html) for a detailed explanation.\n\n\n## Hypermedia\n\nRoar comes with built-in support for embedding and processing hypermedia in your documents.\n\n```ruby\nclass SongRepresenter \u003c Roar::Decorator\n  include Roar::JSON\n  include Roar::Hypermedia\n\n  property :title\n\n  link :self do\n    \"http://songs/#{title}\"\n  end\nend\n```\n\nThe `Hypermedia` feature allows declaring links using the `::link` method. In the block, you have access to the represented model. When using representer modules, the block is executed in the model's context.\n\nHowever, when using decorators, the context is the decorator instance, allowing you to access additional data. Use `represented` to retrieve model data.\n\n```ruby\nclass SongRepresenter \u003c Roar::Decorator\n  # ..\n  link :self do\n    \"http://songs/#{represented.title}\"\n  end\nend\n```\n\nThis will render links into your representation.\n\n```ruby\nSongRepresenter.new(song).to_json #=\u003e {\"title\":\"Roxanne\",\"links\":[{\"rel\":\"self\",\"href\":\"http://songs/Roxanne\"}]}\n```\n\nPer default, links are pushed into the hash using the `links` key. Link blocks are executed in represented context, allowing you to call any instance method of your model (here, we call `#title`).\n\nAlso, note that [roar-rails](https://github.com/apotonick/roar-rails) allows using URL helpers in link blocks.\n\n\n## Passing Options\n\nSometimes you need more data in the link block. Data that's not available from the represented model.\n\n```ruby\nclass SongRepresenter \u003c Roar::Decorator\n  include Roar::JSON\n\n  property :title\n\n  link :self do |opts|\n    \"http://#{opts[:base_url]}songs/#{title}\"\n  end\nend\n```\n\nPass this data to the rendering method.\n\n```ruby\nrepresenter = SongRepresenter.new(song)\nrepresenter.to_json(base_url: \"localhost:3001/\")\n```\n\nAny options passed to `#to_json` will be available as block arguments in the link blocks.\n\n\n## Specify Decorator\n\nIf you have a property that is a separate class or model, you can specify a decorator for that property. Suppose there is a separate `Artist` model for an album. When the album is eagerly loaded, the artist model could be represented along with it.\n\n```ruby\nclass ArtistRepresenter \u003c Roar::Decorator\n  property :name\nend\n\nclass AlbumRepresenter \u003c Roar::Decorator\n  # ..\n  property :artist, decorator: ArtistRepresenter\nend\n```\n\n## Consuming Hypermedia\n\nSince we defined hypermedia attributes in the representer we can also consume this hypermedia when we parse documents.\n\n```ruby\nrepresenter.from_json('{\"title\":\"Roxanne\",\"links\":[{\"rel\":\"self\",\"href\":\"http://songs/Roxanne\"}]}')\n\nrepresenter.links[:self].href #=\u003e \"http://songs/Roxanne\"\n```\n\nReading link attributes works by using `#links[]` on the consuming instance.\n\nThis allows an easy way to discover hypermedia and build navigational logic on top.\n\n\n## Media Formats\n\nWhile Roar comes with a built-in hypermedia format, there's official media types that are widely recognized. Roar currently supports HAL and JSON API.\n\nSimply by including a module you make your representer understand the media type. This makes it easy to change formats during evaluation.\n\n## HAL-JSON\n\nThe [HAL](http://stateless.co/hal_specification.html) format is a simple media type that defines embedded resources and hypermedia.\n\n```ruby\nrequire 'roar/json/hal'\n\nclass SongRepresenter \u003c Roar::Decorator\n  include Roar::JSON::HAL\n\n  property :title\n\n  link :self do\n    \"http://songs/#{title}\"\n  end\nend\n```\n\nDocumentation for HAL can be found in the [API docs](http://rdoc.info/github/trailblazer/roar/Roar/JSON/HAL).\n\nMake sure you [understand the different contexts](#hypermedia) for links when using decorators.\n\n### Hypermedia\n\nIncluding the `Roar::JSON::HAL` module adds some more DSL methods to your module. It still allows using `::link` but treats them slightly different.\n\n```ruby\nrepresenter.to_json\n#=\u003e {\"title\":\"Roxanne\",\"_links\":{\"self\":{\"href\":\"http://songs/Roxanne\"}}}\n```\n\nAccording to the HAL specification, links are now key with their `rel` attribute under the `_links` key.\n\nParsing works like-wise: Roar will use the same setters as before but it knows how to read HAL.\n\n### Nesting\n\nNested, or embedded, resources can be defined using the `:embedded` option.\n\n```ruby\nclass AlbumRepresenter \u003c Roar::Decorator\n  include Roar::JSON::HAL\n\n  property :title\n\n  collection :songs, class: Song, embedded: true do\n    property :title\n  end\nend\n```\n\nTo embed a resource, you can use an inline representer or use `:extend` to specify the representer name.\n\n```ruby\nAlbumRepresenter.new(album).to_json\n\n#=\u003e {\"title\":\"True North\",\"_embedded\":{\"songs\":[{\"title\":\"The Island\"},{\"title\":\"Changing Tide\"}]}}\n```\n\nHAL keys nested resources under the `_embedded` key and then by their type.\n\nAll HAL features in Roar are discussed in the [API docs](http://rdoc.info/github/trailblazer/roar/Roar/JSON/HAL), including [array links](https://github.com/trailblazer/roar/blob/master/lib/roar/json/hal.rb#L196).\n\n## JSON API\n\nRoar also supports [JSON API](http://jsonapi.org/) via the [Roar JSON API gem](https://github.com/trailblazer/roar-jsonapi).\n\n## Client-Side Support\n\nBeing a bi-directional mapper that does rendering _and_ parsing, Roar representers are perfectly suitable for use in clients, too. In many projects, representers are shared as gems between server and client.\n\nConsider the following shared representer.\n\n```ruby\nclass SongRepresenter \u003c Roar::Decorator\n  include Roar::JSON\n  include Roar::Hypermedia\n\n  property :title\n  property :id\n\n  link :self do\n    \"http://songs/#{title}\"\n  end\nend\n```\n\nIn a client where you don't have access to the database it is common to use `OpenStruct` classes as domain objects.\n\n```ruby\nrequire 'roar/client'\nrequire 'roar/json'\n\nclass Song \u003c OpenStruct\n  include Roar::JSON\n  include SongRepresenter\n  include Roar::Client\nend\n```\n\n## HTTP Support\n\nThe `Client` module mixes all necessary methods into the client class, e.g. it provides HTTP support\n\n```ruby\nsong = Song.new(title: \"Roxanne\")\nsong.post(uri: \"http://localhost:4567/songs\", as: \"application/json\")\n\nsong.id #=\u003e 42\n```\n\nWhat happens here?\n\n* You're responsible for initializing the client object with attributes. This can happen with in the constructor or using accessors.\n* `post` will use the included `SongRepresenter` to compile the document using `#to_json`.\n* The document gets `POST`ed to the passed URL.\n* If a document is returned, it is deserialized and the client's attributes are updated.\n\nThis is a very simple but efficient mechanism for working with representations in a client application.\n\nRoar works with all HTTP request types, check out `GET`.\n\n```ruby\nsong = Client::Song.new\nsong.get(uri: \"http://localhost:4567/songs/1\", as: \"application/json\")\n\nsong.title #=\u003e \"Roxanne\"\nsong.links[:self].href #=\u003e http://localhost/songs/1\n```\n\nAs `GET` is not supposed to send any data, you can use `#get` on an empty object to populate it with the server data.\n\n### HTTPS\n\nRoar supports SSL connections - they are automatically detected via the protocol.\n\n```ruby\nsong.get(uri: \"https://localhost:4567/songs/1\")\n```\n\n### Basic Authentication\n\nThe HTTP verbs allow you to specify credentials for HTTP basic auth.\n\n```ruby\nsong.get(uri: \"http://localhost:4567/songs/1\", basic_auth: [\"username\", \"secret_password\"])\n\n```\n\n### Client SSL certificates\n\n(Only currently supported with Net:Http)\n\n```ruby\nsong.get(uri: \"http://localhost:4567/songs/1\", pem_file: \"/path/to/client/cert.pem\", ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER)\n\n```\n\nNote: ssl_verify_mode is not required and will default to ```OpenSSL::SSL::VERIFY_PEER)```\n\n\n\n### Request customization\n\nAll verbs yield the request object before the request is sent, allowing to modify it. It is a `Net::HTTP::Request` instance (unless you use Faraday).\n\n ```ruby\nsong.get(uri: \"http://localhost:4567/songs/1\") do |req|\n  req.add_field(\"Cookie\", \"Yumyum\")\nend\n```\n\n### Error handling\n\nIn case of a non-2xx response status, `#get` and friends raise a `Roar::Transport::Error` exception. The original response can be accessed as follows.\n\n```ruby\n  song.get(uri: \"http://localhost/songs1\") # not-existing.\nrescue Roar::Transport::Error =\u003e exception\n  exception.response.code #=\u003e 404\n```\n\n## XML\n\nRoar also comes with XML support.\n\n```ruby\nclass SongRepresenter \u003c Roar::Decorator\n  include Roar::XML\n  include Roar::Hypermedia\n\n  property :title\n  property :id\n\n  link :self do\n    \"http://songs/#{title}\"\n  end\nend\n```\n\nInclude the `Roar::XML` engine and get bi-directional XML for your objects.\n\n```ruby\nsong = Song.new(title: \"Roxanne\", id: 42)\n\nSongRepresenter.new(song).to_xml\n```\n\nNote that you now use `#to_xml` and `#from_xml`.\n\n```xml\n\u003csong\u003e\n  \u003ctitle\u003eRoxanne\u003c/title\u003e\n  \u003cid\u003e42\u003c/id\u003e\n  \u003clink rel=\"self\" href=\"http://songs/Roxanne\"/\u003e\n\u003c/song\u003e\n```\n\nPlease consult the [representable XML documentation](https://github.com/trailblazer/representable/#more-on-xml) for all its great features.\n\n\n## Support\n\nQuestions? Need help? Free 1st Level Support on irc.freenode.org#roar !\nWe also have a [mailing list](https://groups.google.com/forum/?fromgroups#!forum/roar-talk), yiha!\n\n## License\n\nRoar is released under the [MIT License](http://www.opensource.org/licenses/MIT).\n","funding_links":[],"categories":["`API Frameworks`","Ruby","API Frameworks","Serializers"],"sub_categories":["`Ruby`","Ruby"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrailblazer%2Froar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftrailblazer%2Froar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrailblazer%2Froar/lists"}