[
  {
    "path": ".gitignore",
    "content": "pkg\n.bundle\ncoverage\nGemfile.lock\n*~\n.yardoc\ndoc\nFORMATS.html\nscratch\n.ruby-version\n.ruby-gemset\nJarfile.lock\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "AllCops:\n  Exclude:\n    - 'pkg/**/*'\n    - 'vendor/**/*'\n    - 'bench/**/*'\n\nLint/AmbiguousRegexpLiteral:\n  Enabled: false\n\nLint/AmbiguousOperator:\n  Enabled: false\n\nLint/AssignmentInCondition:\n  Enabled: false\n\nMetrics/AbcSize:\n  Enabled: false\n\nMetrics/ClassLength:\n  Enabled: false\n\nMetrics/CyclomaticComplexity:\n  Enabled: false\n\n# FIXME: lower by fixing the biggest offenders\nMetrics/LineLength:\n  Max: 184\n\nMetrics/MethodLength:\n  Enabled: false\n\nMetrics/PerceivedComplexity:\n  Enabled: false\n\n# def_delegators’ first symbol is the target, the rest are calls\nStyle/AlignParameters:\n  Enabled: false\n\n# FIXME: should this be normalised to `EnforcedStyle: semantic`?\nStyle/BlockDelimiters:\n  Enabled: false\n\n# including parametrised modules looks much better without this alignment\nStyle/ClosingParenthesisIndentation:\n  Enabled: false\n\nStyle/ConstantName:\n  Exclude:\n    - yaks/lib/yaks/breaking_changes.rb\n\nStyle/Documentation:\n  Enabled: false\n\nStyle/EmptyLineBetweenDefs:\n  AllowAdjacentOneLineDefs: true\n\nStyle/FileName:\n  Exclude:\n    - yaks-html/lib/yaks-html.rb\n    - yaks-sinatra/lib/yaks-sinatra.rb\n    - yaks-transit/lib/yaks-transit.rb\n\n# including parametrised modules looks much better without this indentation\nStyle/FirstParameterIndentation:\n  Enabled: false\n\nStyle/FormatString:\n  EnforcedStyle: percent\n\nStyle/GlobalVars:\n  Exclude:\n    - bench/bench.rb\n    - bench/bench_1000.rb\n\nStyle/HashSyntax:\n  Exclude:\n    - Rakefile\n\n# some arrays need deeper indenting for readability\nStyle/IndentArray:\n  Enabled: false\n\nStyle/IndentationWidth:\n  Exclude:\n    - yaks/lib/yaks/breaking_changes.rb\n\n# the codebase uses -> consistently\nStyle/Lambda:\n  Enabled: false\n\n# FIXME: figure out why fixing this blows tests up\nStyle/MethodCallParentheses:\n  Enabled: false\n\nStyle/ModuleFunction:\n  Exclude:\n    - yaks/lib/yaks/util.rb\n\nStyle/MultilineBlockChain:\n  Enabled: false\n\nStyle/PercentLiteralDelimiters:\n  Exclude:\n    - yaks/lib/yaks/breaking_changes.rb\n    - yaks/spec/unit/yaks/config_spec.rb\n  PreferredDelimiters:\n    '%i': '[]'\n    '%w': '[]'\n    '%W': '[]'\n\nStyle/PerlBackrefs:\n  Enabled: false\n\nStyle/Semicolon:\n  AllowAsExpressionSeparator: true\n\nStyle/SignalException:\n  EnforcedStyle: only_raise\n\nStyle/SpaceBeforeSemicolon:\n  Enabled: false\n\n# FIXME: this should be enforced to either space or no_space\nStyle/SpaceBeforeBlockBraces:\n  Enabled: false\n\n# FIXME: this should be enforced to either space or no_space\nStyle/SpaceInsideBlockBraces:\n  Enabled: false\n\n# FIXME: make a call whether to fix this one or not\nStyle/SpaceInsideBrackets:\n  Enabled: false\n\nStyle/SpaceInsideHashLiteralBraces:\n  EnforcedStyle: no_space\n\nStyle/SpaceInsideStringInterpolation:\n  Enabled: false\n\n# FIXME: enforce either single_quotes (fewer fixes) or double_quotes\nStyle/StringLiterals:\n  Enabled: false\n\nStyle/UnneededPercentQ:\n  Exclude:\n    - yaks/lib/yaks/breaking_changes.rb\n\nStyle/TrailingComma:\n  Enabled: false\n\n# Allow redundant braces in foo.bar({\"qux\" => \"quz\"}), when writing\n# e.g. JSON tests this can be more explicit and clear\nStyle/BracesAroundHashParameters:\n  Exclude:\n    - yaks/spec/**/*\n\nStyle/SpaceInsideStringInterpolation:\n  Enabled: false\n\nStyle/LambdaCall:\n  Enabled: false\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: ruby\n\nrvm:\n  - 2.2\n  - 2.3.4\n  - 2.4.2\n  - jruby\n  - jruby-head\n  - ruby-head\n\ncache: bundler\n\nsudo: false\n\nscript: bundle exec rake $TASK\n\nenv:\n  - TASK=yaks:rspec\n  - TASK=yaks-html:rspec\n  - TASK=yaks-sinatra:rspec\n\nmatrix:\n  allow_failures:\n    - rvm: ruby-head\n    - rvm: jruby-head\n    - rvm: rbx\n    - env: TASK=mutant\n  fast_finish: true\n  include:\n    - rvm: rbx\n      env: TASK=\"yaks:rspec --trace\"\n    - rvm: rbx\n      env: TASK=\"yaks-html:rspec --trace\"\n    - rvm: rbx\n      env: TASK=\"yaks-sinatra:rspec --trace\"\n    - rvm: 2.2\n      env: TASK=ataru\n    - rvm: 2.2\n      env: TASK=mutant\n"
  },
  {
    "path": "ADDING_FORMATS.md",
    "content": "# Adding Extra Output Formats to Yaks\n\nIndividual output formats are each handled by a dedicated `Yaks::Serializer` class. These take a `Yaks::Resource` as input, and turn it into the requested output format.\n\nA `Yaks::Resource` is created by \"mapping\" domain models by a `Yaks::Mapper`. In a `Yaks::Mapper` subclass a DSL is available to specify how to extract different types of information, for example attributes or links, and store them in a generalized way in a `Resource`.\n\nDifferent formats have different features. Simple formats might just represent attributes, links, and subresources, other formats have queries, forms, or RDF identifiers. If a format represents data of a different nature, then the first step is to decide on a good and straightforward syntax to specify how to derive this data. This can then be stored in a `Yaks::Resource`, and formats that support it can use it, other formats can ignore it.\n\nThis is already the case, JSON-API ignores links for example.\n\nSo adding an output format is generally straightforward, as long as the information that the output format supports is already available in `Yaks::Resource`. In that case adding a `Yaks::Serializer::YourFormat` is all that is needed.\n\nIf the format has features that are not yet available then syntax needs to be added for those features. The guiding idea there is to try and find more than one format with the given feature, to make sure the intermediate abstraction is general and not tied to the specifics and vocabulary of a single format.\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "### master\n[all changes](http://github.com/plexus/yaks/compare/v0.13.0...master)\n\n### v0.13.0 / 2017-11-13\n[all changes](http://github.com/plexus/yaks/compare/v0.12.0...v0.13.0)\n\n* Maintenance release to upgrade dependencies\n\n### v0.12.0 / 2016-03-28\n[all changes](http://github.com/plexus/yaks/compare/v0.11.0...v0.12.0)\n\n* `attribute` can now take an `if` parameter, just like links and forms\n\n### v0.11.0 / 2015-07-09\n[all changes](http://github.com/plexus/yaks/compare/v0.10.0...v0.11.0)\n\n* Updated JSON-API to match 1.0 format. ([Yohan Robert](https://github.com/groyoh) and [Janko Marohnić](https://github.com/janko-m))\n* Added Yaks::Behaviour::OptionalIncludes, to support JSON-API style\n  optional associations ([Janko Marohnić](https://github.com/janko-m))\n* Renamed Sinatra::Yaks to Yaks::Sinatra ([Matt Patterson](https://github.com/fidothe))\n* Correctly handle 'charset' in Yaks::Sinatra ([Matt Patterson](https://github.com/fidothe))\n* Fix rendering of checkboxes in yaks-html\n* Add integration for testing through RSpec/Capybara/Rack::Test\n\n### v0.10.0 / 2015-05-19\n[all changes](http://github.com/plexus/yaks/compare/v0.9.0...v0.10.0)\n\n* Updated JSON-API Reader to handle collections\n\n* Further changes to bring JSONAPI formatting more in line with 1.0\n  format\n\n  - Changed `linked` to `included`\n  - Change format of `links` to include 'linkages'\n  - `included` no longer contains duplicates\n  - Render top level collection links\n  - Don't include \"rel\" in links output\n\n* yaks-html: Make nested resources expand/collapse, various small\n  improvements\n\n* yaks-html: render \"rel\" attributes, making the HTML output more suitable\n  for use in integration tests\n\n* In mapper/form declarations: make methods that take a lambda to also\n  accept a block\n\n* Instead of representing form fieldset legends as form fields, give\n  them their own class\n\n* Introduce `Yaks::Form#fields_flat`, an enumerator to traverse all\n  input fields in a form linearly, e.g. for validation. Will skip over\n  legend elements\n\n* Introduce `Yaks::Form#map_fields`, a way to map over fields and fieldsets\n  recursively, yielding a new nested object\n\n* Bug fix: `json_serializer` configuration method not working as intended\n\n* Improved mapper lookup to deal with model inside namespace\n\n* Introduce `mapper_for`, a configuration option for setting up one-off\n  mapper derivation rules\n\n* Reify Form::Legend, making it easier to handle form objects with field sets\n\n* Improve test coverage. We are now at 97.30% mutation coverage\n\n* Improve documentation. Code examples in the README are now verified with\n  Ataru\n\n* Make code warning-free\n\n\n### v0.9.0 / 2015-03-17\n\nMake dynamic form fields respect the order in which they were declared\nin the form relative to other form fields.\n\nSome changes to bring JSONAPI formatting more in line with 1.0 format\n\n - Top level key must be named 'data' rather than the resource type\n - The resource name myst be included in a 'type' attribute\n\nStarted a Reader for JSONAPI, which can build a resource from JSONAPI input.\n\nAdd if: options to Form::Field, Form::Fieldset, and Form::Field\noption, just as on links, associations, and forms.\n\nAllow form field details to be expressed in a block, and allow\nConfigurable \"setters\" to take a block instead of a direct argument.\n\n``` ruby\ntext :first_name,\n  label: 'views.checkout.first_name',\n  required: true,\n  value: ->{ customer_attribute(:first_name) }\n```\n\nbecomes\n\n```\ntext :first_name do\n  required true\n  label 'views.checkout.first_name'\n  value { customer_attribute(:first_name) }\nend\n```\n\nThis makes the DSL more consistent, since e.g. `label` could already\nbe set in this way, but not `value` or `required`.\n\nPrevent `:if` on a form field to be rendered as a form element\nattribute.\n\n### v0.8.3 / 2015-03-09\n\nThe default policy for resolving mappers will now look up superclass\nnames of the object being serialized, so you can define a single\nmapper to handle a class hierarchy.\n\n### v0.8.2 / 2015-03-02\n\nVarious improvements to the HTML formatter\n\n- use the form name as a title if there's no title\n- remove the link styling on rels to indicate they are purely\n  identifiers\n- link IANA registered rels (indicated by using a symbol) to the IANA\n  list\n- style the hierarchy in a cleaner way by using a gray left border\n  rather than complete boxes\n- Add a header that shows the current request method/path\n- Add a footer that shows the yaks version\n- show the name/value of hidden form fields\n- get rid of the all the border-radius, try a new color scheme\n\n### v0.8.1 / 2015-02-20\n\nAdd `disabled` as a possible attribute of a select option, so you can\nrender form select controls with disabled options.\n\n### v0.8.0 / 2015-02-18\n\nAllow to use procs for dynamic values in \"option\" form elements (as\nused inside a \"select\"). This makes the form API more consistent.\n\nAdd an `:if` option to links, to only render them upon a certain\ncondition.\n\nAdd an `:if` option to forms, and a corresponding `condition` method\n(it's tricky to have a method called `if`), to only render them upon a\ncertain condition.\n\nAdd an `:if` option to associtions, to only render them upon a certain\ncondition.\n\n### 0.8.0.beta2 / 2015-01-14\n\nIn form select fields, allow the attributes of options to be generated\ndynamically by passing procs, in line with other form related\nattributes\n\n### 0.8.0.beta1 / 2015-01-09\n\nImproved form support, HTML form rendering, CJ support.\n\n### 0.8.0.alpha / 2014-12-17\n\nImproved Collection+JSON support, dynamically generated form fields.\n\n#### Collection+JSON\n\nCarles Jove i Buxeda has done some great work to improve support for Collection+JSON, GET forms are now rendered as CJ queries.\n\n#### Dynamic Form Fields\n\nA new introduction are \"dynamic\" form fields. Up to now it was hard to generate forms based on the object being serialized. Now it's possible to add dynamic sections to a Form definition. These will be evaluated at map-time, they receive the object being mapped, and inside the syntax for defining form fields can be used.\n\n```\nform :checkout do\n  text :name\n  text :lastname\n\n  dynamic do |object|\n    object.shipping_options.each do |shipping|\n      radio shipping.type_name, title: shipping.description\n    end\n  end\nend\n```\n\n#### Fieldset and Legend\n\nSupport for the fieldset element type has been added, which works as you would expect\n\n```\nform :foo do\n  fieldset do\n    legend \"Hello\"\n    text :field_1\n  end\nend\n```\n\n#### Remove links\n\nA link defined in a mapper can be removed in a derived mapper. This is useful when you have a base mapper defining for example 'self' or 'profile' links, but for some derived mappers you don't want these in the output.\n\n```\nclass BaseMapper\n  link :self, \"/api/{mapper_name}/{id}\"\nend\n\nclass FooMapper < BaseMapper\n  link :self, remove: true\nend\n```\n\n#### Deprecations\n\nInternally there the DSL/Config mechanisms have been made more consistent. Yaks::Config is now immutable, much like Yaks::Mapper::Config. Attributes-based classes no long have arity-based hybrid getter/setters. Instead use `with(attr: val)` to set a value.\n\nBecause of this work, two methods on Yaks::Config are considered deprecated. You will get a warning when using the old name.\n\n* json_serializer, use serializer(:json, &...)\n* namespace, use mapper_namespace\n\n#### Experimental read/write support\n\nSome work has happened on read/write support, but this is not considered stable yet.\n\n### 0.7.7 / 2014-12-02\n\nGeneral extension and improvements to form handling.\n\nAdd top level links in Collection+JSON (Carles Jove i Buxeda)\n\nThe mapper DSL method \"control\" has been renamed to \"form\". There is a\ndeprecated alias available.\n\nAdd Yaks::Resource#find_form for querying a resource for an embedded\nform by name.\n\nIntroduce yaks.map() so you can only call the mapping step without\nrunning the whole pipeline.\n\n### 0.7.6 / 2014-11-18\n\nMuch expanded form support, simplified link DSL, pretty-print objects\nto Ruby code.\n\nBreaking change: using a symbol instead of link template no longer\nworks, use a lambda.\n\n    link :foo, :bar\n\nBecomes\n\n    link :foo, ->{ bar }\n\nStrictly speaking the equivalent version would be `link :foo, ->{\nload_attribute(:bar) }`. Depending on if `bar` is implemented on the\nmapper or is an attribute of the object, this would simplify to `link\n:foo, ->{ bar }` or `link :foo, ->{ object.bar }` respectively.\n\nThe form control DSL has been expanded, instead of `field type:\n'text'` and similar there are now aliases, e.g. `text :name, value:\n'foo'`.\n\nAll attributes on the form control itself, and on fields, now\noptionally take a lambda (any `#to_proc`-able) for dynamic\ncontent. e.g.\n\n    control :add_product do\n      method 'POST'\n      action ->{ '/cart/#{cart.id}/line_items' }\n      hidden :product_id, value: -> { product.id }\n      number :quantity, value: 0\n    end\n\nAs with lambdas used for links, in case of a zero-arity lambda these\nevaluate with `self` being the mapper. If the lambda takes an argument\nthe argument will be the mapper, and the lambda is evaluated as a\nclosure.\n\nThe `href` attribute of a control has been renamed `action`, in line\nwith the attribute name in HTML. An alias is available but will output\na deprecation warning.\n\nThe Yaks::Resource#pp method has been lifted into Attributes so it's\navailable on most immutable Yaks objects. It has also been adapted to\nproduce, in most cases, output that is valid Ruby code.\n\n### 0.7.5 / 2014-11-17\n\nAdd the :replace option to link specifications. When used on a link\nwhen another link of the same rel was specified previously, then the\ncurrent link will replace the one (and any other) that was specified\nearlier.\n\nUse case:\n\n    class BaseMapper < Yaks::Mapper\n      link :self, '/api/{mapper_name}/{id}'\n    end\n\n    class CartMapper < BaseMapper\n      link :self, '/api/cart', :replace => true\n    end\n\n\n### 0.7.4 / 2014-11-17\n\nFix a regression in around hooks introduced in 0.7.0.\n\nImprove pretty printing (Yaks::Resource#pp)\n\n### 0.7.3 / 2014-11-11\n\nyaks-sinatra: Allow passing extra Yaks options to the helper method\n\n### 0.7.2 / 2014-11-10\n\nAllow controls to use the same expansion mechanisms that are available\nin links, i.e. URI templates, symbol referring to a method. Added\nprocs to that list as well.\n\n### 0.7.1 / 2014-11-10\n\nBugfix in CollectionMapper.\n\n### 0.7.0 / 2014-11-10\n\n#### Introduces yaks-sinatra\n\nFor easier Sinatra integration. See the respective README for more info.\n\n#### Move the rel of subresource into a resource itself\n\nBefore the subresources in a Yaks::Resource were stored in a hash,\nkeyed by rel. Now the rel is stored as a property of the resource, and\nthe subresources are a simple array. This opens the door to formats\nthat support multiple rels on a resource, and simplifies things as a\npreparatory step towards bi-directional mapping.\n\nThis change is mostly transparent to the user, but when implementing\ncustom output formats or doing testing on the resulting Resource\ninstances, you might have to update your code.\n\n#### Pass the rack env to steps and hooks\n\nYaks is a pipeline where each step implements the `call`\nmethod. Before `call` always received one argument, the previous\ntransformation step's result. Now it receives the Rack env as a second\nargument.\n\nThis also applies to before/after/around hooks, although if they are\nspecified as ruby blocks then no change is needed, the second argument\nwill be ignored.\n\n#### Handle URI instances\n\nAfter formatting for a JSON output format (e.g. HAL), but before\nactually serializing to JSON, all data needs to be of a type that has\na JSON equivalent, or needs to be handled explicitly with a conversion\n(known as \"primitivizing\"). instances of `URI` have been added to this\nlist, they will automatically be represented as JSON strings.\n\n### 0.6.2 / 2014-11-05\n\nImprovements to yaks-html: render form controls, make output prettier.\n\n### 0.6.1 / 2014-10-30\n\nMake sure Resource, NullResource, and CollectionResource have\nidentical public APIs.\n\nCreate a base Yaks::Error class, and derived classes for specific\nerror categories. This should make it easier to handle errors\noriginating in Yaks. Note that not all code makes use of these yet, so\nyou might still get a StandardError in some cases.\n\n### 0.6.0 / 2014-10-30\n\nv0.6.0 saw some big internal overhaul to make things cleaner and more\nconsistent. It also introduced some new features.\n\n#### Form controls\n\nWe already had templated links which form a limited way of generating\nparameterized requests. Form controls are more like full HTML forms,\ne.g.\n\n``` ruby\nclass UserMapper < Yaks::Mapper\n  control :create do\n    href         \"/foo\"\n    method       \"POST\"\n    content_type \"application/x-www-form-urlencoded\"\n\n    field :first_name, label: \"First name\"\n    field :last_name,  label: \"Last name\"\n  end\nend\n```\n\nThese are also called actions in some formats. At the moment only one\nformat renders these, a new format called HALO which is en extension\nof HAL, loosely based on an example by Mike Kelly on how HAL could be\nextended for this purpose.\n\n#### Introduce a HTML output format\n\nProvided as a separate gem, `yaks-html` allows Yaks to generate a\nversion of your API that can be browsed from any web browser. This is\nstill very rough around the edges.\n\n### 0.5.0 / 2014-09-18\n\n* Yaks now serializes (returns a string), instead of returning a data\n  structure. This is a preparatory step for supporting non-JSON\n  formats. To get the old behavior back, do this\n\n``` ruby\nyaks = Yaks.new do\n  skip :serialize\nend\n```\n\n* The old `after` hook has been removed, instead there are now generic hooks for all steps: `before`, `after`, `around`, `skip`; `:map`, `:format`, `:primitivize`, `:serialize`.\n\n* By default Yaks uses `JSON.pretty_generate` as a JSON unparser. To use something else, for example `Oj.dump`, do this\n\n``` ruby\nyaks = Yaks.new do\n  json_serializer &Oj.method(:dump)\nend\n```\n\n* Mapping a non-empty collection will try to infer the type, and hence rel of the nested items, based on the first object in the collection. This is only relevant for formats like HAL that don't have a top-level collection representation, and only matters when mapping a collection at the top level, not when mapping a collection from an association.\n\n* Collection+JSON uses a link's \"title\" attribute to output a link's \"name\", to better correspond with other formats\n\n* When registering a custom format (Yaks::Format subclass), the signature has changed\n\n``` ruby\n# 0.4.3\nFormat.register self, :collection_json, 'application/vnd.collection+json'\n\n# 0.5.0\nregister :collection_json, :json, 'application/vnd.collection+json'\n```\n\n* `yaks.call` is now the preferred interface, rather than `yaks.serialize`, although there are no plans yet to remove the alias.\n\n* The result of a call to `Yaks.new` now responds to `to_proc`, so you can treat it as a Proc/Symbol, e.g. `some_method &yaks`\n\n* Improved YARD documentation\n\n* 100% mutation coverage :trumpet: :tada:\n\n### 0.4.3 / 2014-08-25\n\n* when specifying a rel_template, instead of allowing for {src} and {dest} fields, now a single {rel} field is expected, which corresponds more with typical usage.\n\n```ruby\nYaks.new do\n  rel_template 'http://my-api/docs/relationships/{rel}'\nend\n```\n\n* Yaks::Serializer has been renamed to Yaks::Format\n\n* Yaks::Mapper#{map_attributes,map_links,map_subresource} signature has changed, they now are responsible for adding themselves to a resource instance.\n\n```ruby\nclass FooMapper < Yaks::Mapper\n  def map_attributes(resource)\n    resource.update_attributes(:example => 'attribute')\n  end\nend\n```\n\n* Conditionally turn associations into links\n\n```ruby\nclass ShowMapper < Yaks::Mapper\n  has_many :events, href: '/show/{id}/events', link_if: ->{ events.count > 50 }\nend\n```\n\n* Reify `Yaks::Mapper::Attribute`\n\n* Remove `Yaks::Mapper#filter`, instead override `#attributes` or `#associations` to filter things out, for example:\n\n```ruby\nclass SongMapper\n  attributes :title, :duration, :lyrics\n  has_one :artist\n  has_one :album\n\n  def minimal?\n    env['HTTP_PREFER'] =~ /minimal/\n  end\n\n  def attributes\n    if minimal?\n      super.reject {|attr| attr.name.equal? :lyrics } # These are instances of Yaks::Mapper::Attribute\n    else\n      super\n    end\n  end\n\n  def associations\n    return [] if minimal?\n    super\n  end\nend\n```\n\n* Give Attribute, Link, Association a common interface : `add_to_resource(resource, mapper, context)`\n* Add persistent update methods to `Yaks::Resource`\n\n### v0.4.2 / 2014-06-24\n\n* JSON-API: render self links as href attributes\n* HAL: render has_one returning nil as null, not as {}\n* Keep track of the mapper stack, useful for figuring out if mapping the top level response or not, or for accessing parent\n* Change Serializer.new(resource, options).serialize to Serializer.new(options).call(resource) for cosistency of \"pipeline\" interface\n* Make Yaks::CollectionMapper#collection overridable for pagination\n* Don't render links from custom link methods (link :foo, :method_that_generates_url) that return nil\n\n### v0.4.1 / 2014-06-18\n\n* Change how env is passed to yaks.serialize to match docs\n* Fix JSON-API bug (#18 reported by Nicolas Blanco)\n* Don't pluralize has_one association names in JSON-API\n\n### v0.4.0  / 2014-06-17\n\n* Introduce after {} post-processing hook\n* Streamline interfaces and variable names, especially the use of `call`\n* Improve deriving mappers automatically, even with Rails style autoloading\n* Give CollectionResource a members_rel, for HAL-like formats with no top-level collection concept\n* Switch back to using `src` and `dest` as the rel-template keys, instead of `association_name`\n* deprecate `mapper_namespace` in favor of `namespace`\n\n### v0.4.0.rc1 / 2014-06-11\n\n* Introduce Yaks.new as the main public interface\n* Fix JsonApiSerializer and make it compliant with current spec\n* Remove Hamster dependency, Yaks new uses plain old Ruby arrays and hashes\n* Remove `RelRegistry` and `ProfileRegistry` in favor of a simpler explicit syntax + policy based fallback\n* Add more policy derivation hooks, plus make `DefaultPolicy` template for rel urls configurable\n* Optionally take a Rack env hash, pass it around so mappers can inspect it\n* Honor the HTTP Accept header if it is present in the rack env\n* Add map_to_primitive configuration option\n\n### v0.3.0 / 2014-05-15\n\n* Allow partial expansion of templates, expand certain fields, leave others as URI template in the result.\n\n### v0.2.0 / 2014-03-31\n\n* links can now take a simple for a template to compute a link just like an attribute\n\n### v0.1.0 / 2014-03-07\n\n### v0.0.0 / 2013-12-09\n"
  },
  {
    "path": "COOKBOOK.md",
    "content": "# Yaks Cookbook\n\n## Represent Date/Time objects as iso8601\n\n``` ruby\n$yaks = Yaks.new do\n  map_to_primitive Date, Time, DateTime, ActiveSupport::TimeWithZone, &:iso8601\nend\n```\n\n## Make Yaks' HTML format play nice with CSRF protection\n\nMinimum version when using Rack::Protection\n\n``` ruby\n$yaks = Yaks.new do\n  after :format, :add_csrf_token do |result, env|\n    next result unless result.is_a?(Hexp::Node) && env.key?('rack.session')\n\n    session = env['rack.session']\n    session[:csrf] ||= SecureRandom.hex(32)\n    token = session[:csrf]\n\n    result.replace 'form' do |form|\n    form.append(H[:input, type: :hidden, name: 'authenticity_token', value: token])\n    end\n  end\nend\n```\n\nVersion that covers all cases when using a Rack::Protection protected\nAPI mounted inside a Rails app.\n\n``` ruby\n$yaks = Yaks.new do\n  after :format, :add_csrf_token do |result, env|\n    next result unless result.is_a?(Hexp::Node) && env.key?('rack.session')\n\n    # Rails uses '_csrf_token' as a key. Rack::Protection uses\n    # :csrf, but will detect and use '_csrf_token' if :csrf is\n    # absent. This works fine as long as a call to Rails is made\n    # before a call to the API is made. When using the HTML\n    # rendering of the API on an empty session and afterwards\n    # switching to Rails though, the '_csrf_token' and :csrf\n    # values will differ, causing Rack::Protection to reject\n    # valid API calls. Hence this little dance to prevent that.\n\n    session = env['rack.session']\n    session[:csrf] ||= session['_csrf_token'] || SecureRandom.hex(32)\n    session['_csrf_token'] ||= session[:csrf]\n    token = session[:csrf]\n\n    result.replace 'form' do |form|\n    form.append(H[:input, type: :hidden, name: 'authenticity_token', value: token])\n    end\n  end\n```\n\n## Make Yaks' HTML format work with PUT/DELETE/etc.\n\nIf you're using `Rack::MethodOverride` or something similar, you could\ndrop this in your Yaks config to convert forms so they will work in a\nbrowser.\n\n``` ruby\nafter :format, :html_form_methods do |result, env|\n  next result unless result.is_a?(Hexp::Node)\n  result.replace('[method=\"PUT\"],[method=\"DELETE\"],[method=\"PATCH\"]') do |form|\n    form\n      .append(H[:input, type: \"hidden\", name: \"_method\", value: form[:method]])\n      .attr(\"method\", \"POST\")\n  end\nend\n```\n\n## Implement Pagination\n\nIn a hypermedia API the typical way to provide pagination is by adding\n\"previous\" and \"next\" links on a collection. You can do this by\nimplementing your own CollectionMapper\n\n```\nmodule Mappers\n  class CollectionMapper < Yaks::CollectionMapper\n    PAGE_SIZE = 50\n\n    link :previous, -> { previous_link }\n    link :next,     -> { next_link     }\n\n    def params\n      Rack::Request.new(env).params\n    end\n\n    def offset\n      params.fetch('offset') { 0 }.to_i\n    end\n\n    alias full_collection collection\n\n    def collection\n      # You can implement more efficient page slicing based on DB\n      # layer you're using\n      full_collection.drop(offset).take(PAGE_SIZE)\n    end\n\n    def count\n      full_collection.count\n    end\n\n    def previous_link\n      if offset > 0\n        URITemplate.new(\"#{env['PATH_INFO']}{?offset}\").expand(offset: [offset - PAGE_SIZE, 0].max)\n      end\n    end\n\n    def next_link\n      if offset + page_size < count\n        URITemplate.new(\"#{env['PATH_INFO']}{?offset}\").expand(offset: offset + PAGE_SIZE)\n      end\n    end\n  end\nend\n```\n\nYou can pass this mapper explicitly when calling yaks:\n`yaks.call(collection, mapper: MyCollectionMapper)`, or leverage the\ndefault policy which gives you several options for hooking into mapper\nresolution.\n\n* When implementing a `CollectionMapper` inside your configured mapper\n  namespace, or at the top level if no namespace is confgured, Yaks\n  will use that instead of its vanilla collection mapper\n\n* If you're serializing collections of a specific type, you can implement a specific mapper for that. E.g. if you want paging for hypothetical `DatabaseQuerySet`, you can implement a `DatabaseQuerySetMapper`\n\n* You can make a `PagedCollection` decorator class, and provide a `PagedCollectionMapper`. This is a great pattern because you can put more of the paging logic inside that object, and override it in subclasses, e.g. to date per month, offset, page, etc.\n"
  },
  {
    "path": "DEVELOPERS.md",
    "content": "# Yaks Dev Docs\n\nThis document is for when you want to hack on Yaks itself, or better\nunderstand its internals. To simply use it, consult the README.\n\n## Attribs\n\nYou'll find that most classes in Yaks include an instance of\n`Attribs`, for example\n\n``` ruby\nclass Yaks::Resource::Link\n  include Attribs.new(:rel, :uri, options: {})\nend\n```\n\nYou can think of this (as a starting point) as replacing\n`attr_reader`, by adding this line instances of `Link` will have\ngetter methods for `rel`, `uri`, and `options`. But that's really just\nscratching the surface.\n\n`Attribs` relies on Anima, so you get the same things as\nusing `include Anima.new`\n\n* a hash-based constructor\n* getters\n* equality checks\n* `to_h`\n\n``` ruby\nlink = Yaks::Resource::Link.new(rel: :self, uri: '/api/cart', options: {templated: false})\n\nlink.rel # => :self\nlink.to_h # => {:rel=>:self, :uri=>\"/api/cart\", :options=>{:templated=>false}}\n\nlink == Yaks::Resource::Link.new(link.to_h) # => true\n```\n\nThese last two are important because they make these objects behave\nlike \"value objects\". They are fully defined by their properties, not\nby their (object) identity.\n\nNote that there are no setters, these objects are immutable.\n\nThere are some other things that `Attribs` adds that make it\na pleasure to work with these objects.\n\n* default values\n* `with` method to create updates\n* `with_x` convenience methods\n* `pp` method for representing instances as valid Ruby code\n* `append_to` method\n* `to_h_compact` method\n\nYou can include default values for properties in `Attribs.new(...)`, for example the options of a `Link` default to `{}`.\n\n`with` (see\n[this discussion](https://gist.github.com/plexus/42c6c9c63212182ee440)\nabout why that name was chosen), will create a new object, with\ncertain properties replaced.\n\n``` ruby\nlink2 = link.with(uri: '/foo/bar')\n\nlink  # => #<Yaks::Resource::Link rel=:self uri=\"/api/cart\" options={:templated=>false}>\nlink2 # => #<Yaks::Resource::Link rel=:self uri=\"/foo/bar\" options={:templated=>false}>\n```\n\nFor each property `foo` there's also `with_foo`, so `x.with(foo: 'bar')` is the same as `x.with_foo('bar')`\n\n`pp` recursively turns nested `Attribs` based objects into nicely\nformat, valid Ruby code. This is great for debugging, and very helpful\nwhen writing test cases.\n\n<a id=\"mapper_config_example\"></a>\n\n``` ruby\nclass FooMapper < Yaks::Mapper\n  attributes :a, :b\n  link :self, '/api/foo'\n  has_many :baz\n  form :bar do\n    text :name\n    text :age\n  end\nend\n\nputs FooMapper.config.pp\n\n# -- output --\nYaks::Mapper::Config.new(\n  attributes: [\n    Yaks::Mapper::Attribute.new(name: :a),\n    Yaks::Mapper::Attribute.new(name: :b)\n  ],\n  links: [\n    Yaks::Mapper::Link.new(rel: :self, template: \"/api/foo\", options: {})\n  ],\n  associations: [\n    Yaks::Mapper::HasMany.new(name: :baz, collection_mapper: nil)\n  ],\n  forms: [\n    Yaks::Mapper::Form.new(\n      config: Yaks::Mapper::Form::Config.new(\n        name: :bar,\n        fields: [\n          Yaks::Mapper::Form::Field.new(name: :name, type: :text),\n          Yaks::Mapper::Form::Field.new(name: :age, type: :text)\n        ]\n      )\n    )\n  ]\n)\n```\n\nBecause of the common case where new objects need to be added to a\nlist, e.g. a new link, association, form, to the respective property,\nthere's a `append_to` convenience method for that.\n\n``` ruby\nconfig = Yaks::Mapper::Config.new\nconfig = config.append_to(:attributes, Yaks::Mapper::Attribute.new(name: :a))\nconfig = config.append_to(:attributes, Yaks::Mapper::Attribute.new(name: :b))\nputs config.pp\n\n# -- output --\nYaks::Mapper::Config.new(\n  attributes: [\n    Yaks::Mapper::Attribute.new(name: {:name=>:a}),\n    Yaks::Mapper::Attribute.new(name: {:name=>:b})\n  ]\n)\n```\n\nFinally `to_h_compact` is similar to `to_h`, but won't output values that are the same as the defaults. So it's the minimal hash for which `foo == foo.class.new(foo.to_h_compact)` holds true.\n\n## The Mapper DSL\n\nNow that we know that most objects in Yaks behave in a uniform way, we\ncan leverage that to create the Yaks mapper DSL.\n\nAs demonstrated in the [example above](#mapper_config_example), most\nmethods like `link`, `has_many`, or `fieldset` simply instantiate an\nobject of a certain type, and add it to a \"config\" object. For a form\n`text` input field, the config object is a `Form::Config`, held by the\nform instance. At the top-level where we have attributes, links, and\nassociations, this config object is an instance of\n`Yaks::Mapper::Config` held by the mapper subclass. When configuring\nYaks itself (through `Yaks.new do ...`), you are creating a\n`Yaks::Config`, etc.\n\nBecause the objects created by the DSL all use `Attribs`,\ntheir constructor takes a Hash. For the DSL we often prefer positional\narguments, however. E.g. `form :create` instead of `form name:\n:create`. To bridge this gap classes like `Form` implement a class\nmethod `create`, with the same signature as the DSL method.\n\nBecause all these classes implement `create`, we can now generate the\nDSL methods in a generic way. This is where `Yaks::Configurable` comes\nin.\n\n## Yaks::Configurable\n\nHere's how `Yaks::Mapper` starts\n\n``` ruby\nmodule Yaks\n  class Mapper\n    extend Configurable\n\n    def_add :link,      create: Link,      append_to: :links\n    def_add :has_one,   create: HasOne,    append_to: :associations\n    def_add :has_many,  create: HasMany,   append_to: :associations\n    def_add :attribute, create: Attribute, append_to: :attributes\n    def_add :form,      create: Form,      append_to: :forms\n\n    def_set :type\n\n    def_forward :attributes => :add_attributes\n    def_forward :append_to\n```\n\nThe `def_add` \"macro\"[[1](#macro_footnote)] provided by `Yaks::Configurable` will generate a\nmethod which\n\n* creates an instance of certain class by calling `KlassName.create(...)`\n* update `config` to a new config which has the instance appended to\n  `config.links`\n\nFor the case where a DSL method simply needs to overwrite a certain\nconfig attribute, use `def_set`.\n\nFor more involved cases you can implement methods on the Config object\nthat will \"update\" it in a specific way, returning the updated\ninstance (remember these are all immutable). In that case you generate\na DSL method which \"forwards\" to the config object, hence `def_forward`\n\n## Builder\n\nIn the case of `Yaks::Mapper`, the config object is stored on each\nmapper subclass. In other cases the configuration isn't class based\nthough, but instance based. For example, both a `Yaks::Form` and a\n`Yaks::Form::Fieldset` both have a `Yaks::Form::Config` instance as an\nattribute. Creating form fields will add them to this config.\n\nThe block passed to the `form` DSL method will be passed on to\n`Form.create`. Inside the block a very similar DSL is used as that on\na Mapper, but we don't have a class level evaluation context.\n\nInstead we create a `Yaks::Builder` and use the `Yaks::Configurable`\n\"macros\" to declare how the DSL in this context functions. Finally we\nask the builder to evaluate the block, updating the form's config.\n\n``` ruby\nmodule Yaks::Mapper::Form\n\n  ConfigBuilder = Builder.new(Config) do\n    def_set :action, :title, :method, :media_type\n    def_add :field, create: Field::Builder, append_to: :fields\n    def_add :fieldset, create: Fieldset, append_to: :fields\n    # ...\n  end\n\n  def self.create(*args, &block)\n    args, options = extract_options(args)\n    options[:name] = args.first if args.first.is_a? Symbol\n\n    config = Config.new(options)\n    config = ConfigBuilder.build(config, &block)\n\n    new(config: config)\n  end\n\n  # ...\nend\n```\n\nThe builder takes an initial config object, and then evaluates the\nblock, keeping track of the updated config as it evaluates DSL\nmethods. Finally you get the updated config object back. [[2](#state_monad_footnote)]\n\n\n### footnotes\n\n<a id=\"macro_footnote\">[1]</a> I strongly dislike calling all Ruby\nclass-level methods \"macros\", especially when they have little to\nnothing in common with \"real\" (i.e. syntax tranforming read-time\nfunctions) macros. In this case what they achieve is very similar to\nwhat you would do with a \"real\" macro, so I'm rolling with it, adding\nsarcastic \"quotes\" to express my self-loathing in doing so.\n\n<a id=\"state_monad_footnote\">[2]</a> You can think of the Builder as a\nstate monad. I'm sure that helps.\n"
  },
  {
    "path": "FORMATS.org",
    "content": "#+TITLE:Comparison of Hypermedia Message Formats\n#+AUTHOR: Arne Brasseur\n#+email: arne@arnebrasseur.net\n#+INFOJS_OPT: view:info toc:nil\n#+BABEL: :session *ruby* :cache yes :results output graphics :exports both :tangle yes\n\n* Form Controls / Actions\n\n| Format          | href                            | name/id  | title/caption | method    | media-type      | fields                           | schema      | string template | structured template |\n|-----------------+---------------------------------+----------+---------------+-----------+-----------------+----------------------------------+-------------+-----------------+---------------------|\n| HTML5           | action=\"\"                       |          |               | method=\"\" | enctype=\"\"      | yes                              |             |                 |                     |\n| halo            | \"href\"                          | json key |               | \"method\"  | \"content-type\"  |                                  | \"schema\"    | \"template\"      |                     |\n| siren           | \"href\"                          | \"name\"   | \"title\"       | \"method\"  | \"type\"          | \"fields\"                         |             |                 |                     |\n| mason           | \"href\"                          | json key | \"title\"       | \"method\"  | depends on type |                                  | \"schemaUrl\" |                 | \"template\"          |\n| Collection+JSON | \"href\" (query)/current resource |          | \"prompt\"      |           |                 |                                  |             |                 |                     |\n| Hydra           | current resource                | \"@type\"  |               | \"method\"  |                 | \"expects\": {\"supportedProperty\"} |             |                 |                     |\n\n| Format          | Name       | Title    | Type    | Value   |\n|-----------------+------------+----------+---------+---------|\n| Siren           | \"name\"     |          | \"type\"  | \"value\" |\n| Collection+JSON | \"name\"     | \"prompt\" |         | \"value\" |\n| Hydra           | \"property\" |          | \"range\" |         |\n\n** HTML\n\n   [[http://www.w3.org/TR/html5/forms.html][W3C: HTML5 Forms]]\n\n** halo+json\n\n   [[https://gist.github.com/mikekelly/893552][Gist: A sketch of application/halo+json and application/halo+xml]]\n\n   #+BEGIN_SRC json\n     {\n       \"_controls\": {\n         \"widgetate\": {\n           \"href\": \"/widget/{newID}\",\n           \"method\": \"PUT\",\n           \"content-type\": \"application/xml\",\n           \"schema\": null,\n           \"template\": \"<widget>\\\\n <name>{{name}}</name>\\\\n\\\\n <blobs>\\\\n {{#blobs}}\\\\n <blob>\\\\n {{#first}}\\\\n <first>true</first>\\\\n {{/first}}\\\\n <contents>{{contents}}</contents>\\\\n </blob>\\\\n {{/blobs}}\\\\n </blobs>\\\\n\\\\n {{#is_empty}}\\\\n <note>This is an empty widget</note>\\\\n {{/is_empty}}\\\\n</widget>\\\\n\"\n         }\n       }\n     }\n   #+END_SRC\n\n** Siren\n\n   [[https://github.com/kevinswiber/siren][Siren Home page]]\n\n\n   #+BEGIN_SRC json\n     {\n       \"actions\": [\n         {\n           \"name\": \"add-item\",\n           \"title\": \"Add Item\",\n           \"method\": \"POST\",\n           \"href\": \"http://api.x.io/orders/42/items\",\n           \"type\": \"application/x-www-form-urlencoded\",\n           \"fields\": [\n             { \"name\": \"orderNumber\", \"type\": \"hidden\", \"value\": \"42\" },\n             { \"name\": \"productCode\", \"type\": \"text\" },\n             { \"name\": \"quantity\", \"type\": \"number\" }\n           ]\n         }\n       ]\n     }\n   #+END_SRC\n\n** Mason\n\n   #+BEGIN_SRC json\n     {\n       \"@actions\": {\n         \"is:delete-issue\": {\n           \"type\": \"void\",\n           \"href\": \"...\",\n           \"method\": \"DELETE\",\n           \"title\": \"Delete issue\"\n         }\n       }\n     }\n   #+END_SRC\n\n   #+BEGIN_SRC json\n     {\n       \"@actions\": {\n         // JSON action with schema reference\n         \"is:project-create\": {\n           \"type\": \"json\",\n           \"href\": \"...\",\n           \"title\": \"Create new project\",\n           \"schemaUrl\": \"...\"\n         },\n         // JSON action with default template\n         \"is:update-project\": {\n           \"type\": \"json\",\n           \"href\": \"...\",\n           \"title\": \"Update project details\",\n           \"template\": {\n             \"Code\": \"SHOP\",\n             \"Title\": \"Webshop\",\n             \"Description\": \"All issues related to the webshop.\"\n           }\n         }\n       }\n     }\n   #+END_SRC\n\n\nhttps://github.com/JornWildt/Mason/blob/master/Documentation/Mason-draft-1.md#actions\n** Collection+JSON\n\n   CJ does not have a form-like representation. It does allow\n   resources to contain a \"template\", which is really a list of form\n   fields, and a client can use HTTP methods against the same endpoint\n   to perform CRUD operations. In addition CJ provides \"queries\" for\n   basic GET based operations.\n\n   #+BEGIN_SRC json\n     {\n       \"template\" :\n       {\n         \"data\" :\n         [\n           {\"prompt\" : STRING, \"name\" : STRING, \"value\" : VALUE},\n           {\"prompt\" : STRING, \"name\" : STRING, \"value\" : VALUE},\n           ...\n           {\"prompt\" : STRING, \"name\" : STRING, \"value\" : VALUE}\n         ]\n       }\n     }\n   #+END_SRC\n\n   #+BEGIN_SRC json\n     {\n       \"queries\" :\n       [\n         {\n           \"href\" : \"http://example.org/search\",\n           \"rel\" : \"search\",\n           \"prompt\" : \"Enter search string\",\n           \"data\" :\n           [\n             {\"name\" : \"search\", \"value\" : \"\"}\n           ]\n         }\n       ]\n     }\n   #+END_SRC\n** JSON-LD + Hydra\n\n   Example taken from [[http://sookocheff.com/posts/2014-03-11-on-choosing-a-hypermedia-format/][this blog post]]\n\n   JSON-LD itself does not have form like controls, only\n   linking. Hydra introduces an \"operation\" property for this purpose.\n\n   #+BEGIN_SRC json\n     {\n       \"@context\": [\n         \"http://www.w3.org/ns/hydra/core\",\n         {\n           \"@vocab\": \"https://schema.org/\",\n           \"image\": { \"@type\": \"@id\" },\n           \"friends\": { \"@type\": \"@id\" }\n         }\n       ],\n       \"@id\": \"https://api.example.com/player/1234567890/friends\",\n       \"operation\": {\n         \"@type\": \"BefriendAction\",\n         \"method\": \"POST\",\n         \"expects\": {\n           \"@id\": \"http://schema.org/Person\",\n           \"supportedProperty\": [\n             { \"property\": \"name\", \"range\": \"Text\" },\n             { \"property\": \"alternateName\", \"range\": \"Text\" },\n             { \"property\": \"image\", \"range\": \"URL\" }\n           ]\n         }\n       }\n     }\n   #+END_SRC\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\n\ngemspec path: 'yaks'\ngemspec path: 'yaks-html'\ngemspec path: 'yaks-sinatra'\n\n# Transit depends on Oj, which is not available for JRuby\nunless defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'\n  gemspec path: 'yaks-transit'\nend\n\nif RUBY_VERSION < '2'\n  gem 'mime-types', [ '>= 2.6.2', '< 3' ]\nend\n\n# gem 'mutant', github: 'mbj/mutant'\n# gem 'mutant-rspec', github: 'mbj/mutant'\n"
  },
  {
    "path": "IDENTIFIERS.md",
    "content": "# Identifiers\n\nIn Yaks, and Hypermedia message formats in general, a number of\ndifferent types of identifiers are used. Some are full URIs and\ncorrespond with well defined specs. Some are just short identifers\nthat are easy to program with.\n\nUnderstanding these types of identifiers is key to creating a unifying\nmodel of a \"Resource\" that can be shared across output formats. We\nwant to unify as much as possible across formats, without conflating\nthings that are really not the same.\n\nThis document reflects my current limited understanding of things,\nbased on possibly incorrect assumptions. Feedback is more than\nwelcome.\n\n## rels\n\nAs used in HTML and Atom, these identifiers say what the relationship\nis between a resource and another resource it links to. There is a\n[registry of names](http://www.iana.org/assignments/link-relations/link-relations.xhtml),\ne.g. self, next, profile, stylesheet. Custom rels need to be fully\nqualified URLs. Keep in mind that these are simply opaque identifiers,\nbut by using a known protocol like http they can be used to point at\ndocumentation.\n\nSome examples\n\n```\ncopyright\nstylesheet\nhttp://api.example.com/rel/author\nhttp://api.example.com/api-docs/relationships#comment\ncustom_scheme:foo\n/order\n```\n\nThe last example is a relative URL, which would have to be expanded against the source URL of the document it is mentioned in.\n\nIn Yaks both links and subresources are specified with their rel(ationship).\n\n```ruby\nclass PersonMapper < Yaks::Mapper\n  link :self, '/people/{id}'\n  link 'http://api.example.com/rels#friends', '/people/{id}/friends'\n\n  has_one :address, rel: 'http://api.example.com/rels#address'\nend\n```\n\nFor subresources the rel can be omitted, in which case it will be inferred based on the rel_template:\n\n```ruby\n$yaks = Yaks.new do\n  rel_template 'http://api.example.com/rels/{dest}'\nend\n```\n\nLinks and subresources are rendered keyed by rel in HAL and Collection+JSON. JSON-API renders `self` links as the `href` of a resource.\n\n## profiles\n\nA specific IANA registered rel type is profile.\n\n> Profile: Identifying that a resource representation conforms to a certain profile, without affecting the non-profile semantics of the resource representation.\n\nProfile basically adds a layer of semantics on top of the hypermedia message format (e.g. HAL, Collection+JSON), which in turns defines semantics on top of a serialization format (JSON, XML, EDN). Loosely speaking it could be seen as the \"type\" or \"class\". For example if you know the profile of a resource, you might know you can expect to find a \"name\", \"date_of_birth\", or \"post_body\" field.\n\n## \"type\"\n\nDespite the appealing rigor of having fully qualified URIs to identify things, sometimes you just want to call a person a `person`. In Yaks we call these short identifier the *type* for lack of a better word. In some cases, notably JSON-API, they are used literally in the output. More often they are used to derive full URIs based on a template.\n\nThe type of a mapper is inferred from its class name, but can be set explicitly as well.\n\n```ruby\nclass CatMapper < Yaks::Mapper\nend\n\n# type = \"cat\"\n```\n\n```ruby\nclass CatMapper < Yaks::Mapper\n  type 'feline'\nend\n\n# type => \"feline\"\n```\n\n## rdf class\n\nRDF (Resource Description Framework) is a set of specifications for use in \"semantic web\" applications. RDF is based on \"ontologies\" that precisely define a \"vocabulary\" of \"classes\" and \"predicates\". An example class identifier for all Merlot wines could be\n\n> http://www.w3.org/TR/2004/REC-owl-guide-20040210/wine#Merlot\n\n(source [wikipedia](http://en.wikipedia.org/wiki/Resource_Description_Framework))\n\nNot currently used by Yaks, but might become important when implementing support for JSON-LD or other RDF serialization formats.\n\n## CURIES\n\nCURIES are \"compact uris\". The HAL format uses this so it can have the rigor of fully specified rels, with the ease of use of short-name \"type\" identifiers. The mechanism is similar to how one specifies and uses XML namespaces.\n\nFrom the HAL spec:\n\n```json\n{\n    \"_links\": {\n        \"self\": { \"href\": \"/orders\" },\n        \"curies\": [{ \"name\": \"ea\", \"href\": \"http://example.com/docs/rels/{rel}\", \"templated\": true }],\n        \"next\": { \"href\": \"/orders?page=2\" },\n        \"ea:find\": {\n            \"href\": \"/orders{?id}\",\n            \"templated\": true\n        },\n        \"ea:admin\": [{\n            \"href\": \"/admins/2\",\n            \"title\": \"Fred\"\n        }, {\n            \"href\": \"/admins/5\",\n            \"title\": \"Kate\"\n        }]\n    }\n}\n```\n\nIn this case \"ea:find\" is just a shorthand for \"http://example.com/docs/rels/find\".\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2013-2014 Arne Brasseur\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Rakefile",
    "content": "require 'yaks'\nrequire 'yaks-html'\nrequire 'yaks-sinatra'\nrequire 'yaks-transit'\n\nrequire 'rspec/core/rake_task'\nrequire 'rubocop/rake_task'\nrequire 'rubygems/package_task'\nrequire 'yard'\n\ndef delegate_task(gem, task_name)\n  task task_name do\n    chdir gem.to_s do\n      sh \"rake\", task_name.to_s\n    end\n  end\nend\n\n[:yaks, :\"yaks-html\", :\"yaks-sinatra\"].each do |gem|\n  namespace gem do\n    desc 'Run rspec'\n    delegate_task gem, :rspec\n\n    desc 'Build gem'\n    delegate_task gem, :gem\n\n    desc 'Generate YARD docs'\n    delegate_task gem, :yard\n\n    desc 'push gem to rubygems'\n    task :push => \"#{gem}:gem\" do\n      sh \"gem push pkg/#{gem}-#{Yaks::VERSION}.gem\"\n    end\n  end\nend\n\ndesc \"Tag current release and push to Github\"\ntask :tag do\n  sh \"git tag v#{Yaks::VERSION}\"\n  sh \"git push --tags\"\nend\n\ndesc \"Tag, build, and push all gems to rubygems.org\"\ntask :push_all => [\n       :tag,\n       \"yaks:gem\",\n       \"yaks-html:gem\",\n       \"yaks-sinatra:gem\",\n       \"yaks:push\",\n       \"yaks-html:push\",\n       \"yaks-sinatra:push\"\n     ]\ntask :push => :push_all\n\ndesc \"Run all the tests\"\ntask :rspec => [\"yaks:rspec\", \"yaks-html:rspec\", \"yaks-sinatra:rspec\"]\n\ndesc 'Run mutation tests'\ndelegate_task :yaks, :mutant\n\ndesc \"Start a console\"\ntask :console do\n  require 'irb'\n  require 'irb/completion'\n  ARGV.clear\n  IRB.start\nend\n\ntask :ataru do\n  require \"ataru\"\n  Dir.chdir(\"yaks\")\n  Ataru::CLI::Application.start([\"check\", \"README.md\"])\nend\n\nRuboCop::RakeTask.new do |task|\n  task.options << '--display-cop-names'\nend\n\ntask :default => [:rspec, :rubocop]\n"
  },
  {
    "path": "bench/bench.rb",
    "content": "\nrequire 'benchmark/ips'\nrequire 'yaks'\n\nrequire_relative '../spec/acceptance/models'\nrequire_relative '../spec/fixture_helpers'\n\nBenchmark.ips do |x|\n  $yaks = Yaks.new\n\n  input = FixtureHelpers.load_yaml_fixture 'confucius'\n\n  x.report \"Simple HAL mapping\" do\n    $yaks.serialize(input)\n  end\nend\n"
  },
  {
    "path": "bench/bench_1000.rb",
    "content": "#!/usr/bin/env ruby\n\nrequire 'English'\nrequire 'benchmark/ips'\nrequire 'ruby-prof'\nrequire 'yaks'\n\nSIZE = 20\n$timestamp = Time.now.utc.iso8601.gsub('-', '').gsub(':', '')\n$yaks = Yaks.new\n\nFlatModel = Struct.new(:field1, :field2)\nDeepModel = Struct.new(:field, :next)\n\nflat = SIZE.times.map do |i|\n  FlatModel.new(i, 'x' * (i % 50))\nend\n\ndeep = nil\nSIZE.times do |i|\n  deep = DeepModel.new(i, deep)\nend\n\nclass FlatMapper < Yaks::Mapper\n  attributes :field1, :field2\n  link :self, '/model/{field1}'\nend\n\nclass DeepMapper < Yaks::Mapper\n  attributes :field\n  link :self, '/model/{field}'\n  has_one :next, mapper: DeepMapper\nend\n\ndef profile!(name)\n  RubyProf.start\n  yield\n  results = RubyProf.stop\n  File.open \"/tmp/#{name}-#{$timestamp}.out.#{$PROCESS_ID}\", 'w' do |file|\n    RubyProf::CallTreePrinter.new(results).print(file)\n  end\nend\n\ndo_flat = ->(format) { -> { $yaks.serialize(flat, item_mapper: FlatMapper, format: format) } }\ndo_deep = ->(format) { -> { $yaks.serialize(deep, mapper: DeepMapper, format: format) } }\n\n10.times { do_flat[:hal][] }\n10.times { do_deep[:hal][] }\n\nprofile!('flat', &do_flat.(:hal))\nprofile!('deep', &do_deep.(:hal))\nexit\n\nBenchmark.ips(10) do |job|\n  Yaks::Format.names.each do |format|\n    job.report \"#{format} ; #{SIZE} objects in a list ; no nesting\", &do_flat.(format)\n    job.report \"#{format} ; #{SIZE} objects nested\", &do_deep.(format)\n  end\nend\n"
  },
  {
    "path": "code_of_conduct.md",
    "content": "# Contributor Code of Conduct\n\nAs contributors and maintainers of this project, we pledge to respect\nall people who contribute through reporting issues, posting feature\nrequests, updating documentation, submitting pull requests or patches,\nand other activities.\n\nWe are committed to making participation in this project a\nharassment-free experience for everyone, regardless of level of\nexperience, gender, gender identity and expression, sexual\norientation, disability, personal appearance, body size, race, age, or\nreligion.\n\nExamples of unacceptable behavior by participants include the use of\nsexual language or imagery, derogatory comments or personal attacks,\ntrolling, public or private harassment, insults, or other\nunprofessional conduct.\n\nProject maintainers have the right and responsibility to remove, edit,\nor reject comments, commits, code, wiki edits, issues, and other\ncontributions that are not aligned to this Code of Conduct. Project\nmaintainers who do not follow the Code of Conduct may be removed from\nthe project team.\n\nInstances of abusive, harassing, or otherwise unacceptable behavior\nmay be reported by opening an issue or contacting one or more of the\nproject maintainers.\n\nThis Code of Conduct is adapted from the\n[Contributor Covenant](http:contributor-covenant.org), version 1.0.0,\navailable at\n[http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)"
  },
  {
    "path": "notes.org",
    "content": "0.4\n\n* DONE Get rid of profile/rel registry, use policy instead\n* DONE pass around policy explicitly instead of through options\n* DONE introduce name/type separate from profile\n** DONE mapper\n** DONE Resource\n* DONE allow setting rel types directly on associations, with fallback to policy\n* DONE switch to hash-based init of Resource to make it more extensible\n* DONE add Resource#type\n* DONE not 100% happy yet about nameing of mapper#mapper_name / config#name. Maybe use `type` across the board?\n* DONE Fix JsonAPISerializer\n* top-level automatic links, e.g. for self and profile\n* make HAL plural/singular links configurable from the Yaks.new\n* make primitivize configuration instance based, not global\n* Have JsonApi add self links as href: attributes\n* Move examples to acceptance tests\n* Select mapper based on content type\n* move to 100% mutcov\n\npre 0.5\n\n* CURIES/namespaces\n\nTicketsolve::Api::Yaks = ::Yaks.new do\n  policy do\n    def derive...\n  end\n\n  hal_options.plural_link '...'\n\n  primitivize Date, Time do |o|\n    o.iso8601\n  end\n\n  rel_template \"http://literature.example.com/rel/#{association_name}\"\n\n  link :self, \"http://api.com/{key}/{id}\"\n  link :profile, \"http://api.com/profile/{key}\"\n\n  # and/or\n  derive_rel_from_association do |mapper, association|\n    \"http://literature.example.com/rel/#{association.name}\"\n  end\nend\n\n\n\n* DONE 59 lib/yaks/mapper.rb\n* DONE 92 lib/yaks/mapper/link.rb\n* DONE 37 lib/yaks/mapper/association.rb\n* DONE 3 lib/yaks/version.rb\n* DONE 13 lib/yaks/mapper/has_many.rb\n* DONE 9 lib/yaks/mapper/has_one.rb\n* DONE 79 lib/yaks/config.rb\n*   79 lib/yaks/mapper/config.rb\n*   73 lib/yaks.rb\n* DONE 72 lib/yaks/util.rb\n* DONE 65 lib/yaks/collection_resource.rb\n*   59 lib/yaks/json_api_serializer.rb\n*   59 lib/yaks/hal_serializer.rb\n*   43 lib/yaks/primitivize.rb\n*   37 lib/yaks/mapper/class_methods.rb\n* DONE 33 lib/yaks/collection_mapper.rb\n*   28 lib/yaks/null_resource.rb\n* DONE 27 lib/yaks/resource.rb\n*   25 lib/yaks/resource/link.rb\n* DONE 23 lib/yaks/fp.rb\n*   22 lib/yaks/serializer.rb\n*   15 lib/yaks/shared_options.rb\n*   15 lib/yaks/default_policy.rb\n*   10 lib/yaks/mapper/map_links.rb\n\nhttp://www.dragoart.com/tuts/4344/1/1/how-to-draw-a-yak.htm\nhttps://www.google.com/search?q=yak+head&num=20&source=lnms&tbm=isch&sa=X&ei=uVoAVbyWLcTiO-v0gYgF&ved=0CAcQ_AUoAQ&biw=1758&bih=923&dpr=1.09#imgdii=_&imgrc=CtgMobmQThXw_M%253A%3BzyYGcwgq1IAU_M%3Bhttps%253A%252F%252Fcallaocafeandmarket.files.wordpress.com%252F2014%252F02%252Fyak2.jpg%3Bhttps%253A%252F%252Fcallaocafeandmarket.wordpress.com%252Fweather%252F%3B563%3B539\nhttps://www.google.com/search?q=hair+over+eyes&num=20&source=lnms&tbm=isch&sa=X&ei=p2sBVajOOIKvPdDjgPgL&ved=0CAcQ_AUoAQ&biw=1758&bih=923&dpr=1.09#imgdii=_&imgrc=la9hTGBvOniv9M%253A%3B_nu4vLbBOXzIeM%3Bhttp%253A%252F%252Fwww.rampantscotland.com%252Fdiary%252Fgraphics%252Fhighland_cow_baldernock_x3481d.jpg%3Bhttp%253A%252F%252Fwww.rampantscotland.com%252Fdiary%252Fdiary_photos_july11b.htm%3B514%3B496\nhttps://www.google.com/search?q=hair+over+eyes&num=20&source=lnms&tbm=isch&sa=X&ei=p2sBVajOOIKvPdDjgPgL&ved=0CAcQ_AUoAQ&biw=1758&bih=923&dpr=1.09#imgdii=_&imgrc=RmzHL5NICZcanM%253A%3BwMr__Up6S7s_9M%3Bhttp%253A%252F%252Fwww.peakdistrictonline.co.uk%252Fimages%252Fwildlife%252Fanimals%252FHighland_Cattle_In_The_Peak_District_5.jpg%3Bhttp%253A%252F%252Fwww.peakdistrictonline.co.uk%252Ffarm-animals-highland-cattle-c101118.html%3B620%3B412\nhttps://www.google.com/search?biw=1758&bih=923&tbm=isch&sa=1&q=yak+horns&oq=yak+horns&gs_l=img.3..0j0i24.118958.120138.0.120293.9.8.0.1.1.0.142.713.6j2.8.0.msedr...0...1c.1.62.img..0.9.701.rHB0v54VT9k#imgdii=_&imgrc=c8D4U_x1gob0YM%253A%3BzvdhmtL1g8r_UM%3Bhttp%253A%252F%252F4.bp.blogspot.com%252F_2Sr5OicZPHQ%252FTFT2H5ViKWI%252FAAAAAAAAAFs%252FfYlG-en9mFk%252Fs1600%252Fyak%252Band%252BDri.jpg%3Bhttp%253A%252F%252Fthewodakpa.blogspot.com%252F2010%252F07%252Ftibetan-yak_29.html%3B1080%3B672\nhttps://www.google.com/search?biw=1758&bih=923&tbm=isch&sa=1&q=yak+horns&oq=yak+horns&gs_l=img.3..0j0i24.118958.120138.0.120293.9.8.0.1.1.0.142.713.6j2.8.0.msedr...0...1c.1.62.img..0.9.701.rHB0v54VT9k#imgdii=_&imgrc=_N83z2DYkcc7DM%253A%3BDV8K7BLZksSGrM%3Bhttp%253A%252F%252Fthumbs.dreamstime.com%252Fz%252Fyak-stuffed-animal-like-bull-23182676.jpg%3Bhttp%253A%252F%252Fwww.dreamstime.com%252Froyalty-free-stock-image-yak-stuffed-animal-like-bull-image23182676%3B1300%3B957\n\n\n* JSON-LD / RDF\n** Useful protocols used in RDF.rb\n\n   - to_rdf, to_uri\n   - RDF::URI pname, qname\n   - RDF::Vocabulary::Term\n\nhttp://ruby-rdf.github.io/\nhttps://github.com/ruby-rdf/json-ld/\nhttp://schema.org/MusicEvent\nhttps://developers.google.com/structured-data/events/venues\nhttp://json-ld.org/playground/index.html\n\n\n    http://www.iana.org/assignments/relation/\n    http://microformats.org/wiki/rel- and http://microformats.org/profile/\n"
  },
  {
    "path": "shared/rake_tasks.rb",
    "content": "require 'yaks'\nrequire 'yaks-html'\nrequire 'rubygems/package_task'\nrequire 'rspec/core/rake_task'\nrequire 'yard'\n\ndef mutant_task(_gem)\n  require 'mutant'\n  task :mutant do\n    pattern  = ENV.fetch('PATTERN', 'Yaks*')\n    opts     = ENV.fetch('MUTANT_OPTS', '').split(' ')\n    requires = %w[-ryaks -ryaks/behaviour/optional_includes]\n    args     = %w[-Ilib --use rspec --score 100] + requires + opts + [pattern]\n    result   = Mutant::CLI.run(args)\n    raise unless result == Mutant::CLI::EXIT_SUCCESS\n  end\nend\n\ndef gem_tasks(gem)\n  Gem::PackageTask.new(Gem::Specification.load(\"#{gem}.gemspec\")) do |task|\n    task.package_dir = '../pkg'\n  end\n\n  mutant_task(gem) if RUBY_ENGINE == 'ruby' && RUBY_VERSION >= \"2.1.0\"\n\n  RSpec::Core::RakeTask.new(:rspec) do |t, _task_args|\n    t.rspec_opts = \"-Ispec\"\n    t.pattern = \"spec\"\n  end\n\n  YARD::Rake::YardocTask.new do |t|\n    t.files   = [\"lib/**/*.rb\" \"**/*.md\"]\n    t.options = %w[--output-dir ../doc]\n  end\nend\n"
  },
  {
    "path": "shared/rspec_config.rb",
    "content": "require 'rspec/its'\nrequire 'bogus/rspec'\nrequire 'timeout'\n\n# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration\n\nRSpec.configure do |rspec|\n  # Set the FULLSTACK environment variable to prevent RSpec from\n  # filtering stack traces. This can be useful to debug errors that\n  # happen inside third party libraries\n  rspec.backtrace_exclusion_patterns = [] if ENV['FULLSTACK']\n\n  # Limits the available syntax to the non-monkey patched syntax that is\n  # recommended. For more details, see:\n  #   - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax\n  #   - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/\n  #   - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching\n  rspec.disable_monkey_patching!\n\n  # Make sure we stay up to date\n  rspec.raise_errors_for_deprecations!\n\n  rspec.expect_with :rspec do |expectations|\n    # This option will default to `true` in RSpec 4. It makes the `description`\n    # and `failure_message` of custom matchers include text for helper methods\n    # defined using `chain`, e.g.:\n    #     be_bigger_than(2).and_smaller_than(4).description\n    #     # => \"be bigger than 2 and smaller than 4\"\n    # ...rather than:\n    #     # => \"be bigger than 2\"\n    expectations.include_chain_clauses_in_custom_matcher_descriptions = true\n  end\n\n  # This is configured for us by including bogus/rspec. We do not include rspec-mocks.\n  # rspec.mock_with :bogus\n\n  # Mutated code can lead to infinite loops. Consider tests that run\n  # too long as having failed\n  if defined?(Mutant)\n    rspec.around(:each) do |example|\n      Timeout.timeout(1, &example)\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/.rspec",
    "content": "-r spec_helper"
  },
  {
    "path": "yaks/README.md",
    "content": "[![Gem Version](https://badge.fury.io/rb/yaks.png)][gem]\n[![Build Status](https://secure.travis-ci.org/plexus/yaks.png?branch=master)][travis]\n[![Code Climate](https://codeclimate.com/github/plexus/yaks.png)][codeclimate]\n[![Gitter](https://badges.gitter.im/Join Chat.svg)][gitter]\n\n[gem]: https://rubygems.org/gems/yaks\n[travis]: https://travis-ci.org/plexus/yaks\n[codeclimate]: https://codeclimate.com/github/plexus/yaks\n[gitter]: https://gitter.im/plexus/yaks?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge\n\n# Yaks\n\n<img align=\"left\" src=\"https://raw.githubusercontent.com/plexus/yaks/master/graphics/logo_small.png\">\n\nThe library that understands hypermedia.\n\n**If you use Yaks please help out by filling out the [Yaks Users Survey](https://docs.google.com/forms/d/1sZB03Vf32igmNmJ7RP8mo8H4VZHcVIpSrUSbvx2xD8s/viewform)**\n\nYaks takes your data and transforms it into hypermedia formats such as\nHAL, JSON-API, or HTML. It allows you to build APIs that are\ndiscoverable and browsable. It is built from the ground up around\nlinked resources, a concept central to the architecture of the web.\n\nYaks consists of a resource representation that is independent of any\noutput type. A Yaks mapper transforms an object into a resource, which\ncan then be serialized into whichever output format the client\nrequested. These formats are presently supported:\n\n* HAL\n* JSON API\n* Collection+JSON\n* HTML\n* HALO\n* Transit\n\n## Table of Contents\n\n- [State of Development](#user-content-state-of-development)\n- [Concepts](#user-content-concepts)\n- [Mappers](#user-content-mappers)\n  - [Attributes](#user-content-attributes)\n  - [Forms](#user-content-forms)\n    - [Filtering](#user-content-filtering)\n  - [Links](#user-content-links)\n  - [Associations](#user-content-associations)\n  - [Behaviours](#user-content-behaviours)\n- [Calling Yaks](#user-content-calling-yaks)\n  - [Rack env](#user-content-rack-env)\n- [Namespace](#user-content-namespace)\n- [Custom attribute/link/subresource handling](#user-content-custom-attributelinksubresource-handling)\n- [Resources, Formatters, Serializers](#user-content-resources-formatters-serializers)\n- [Formats](#user-content-formats)\n  - [HAL](#user-content-hal)\n  - [HTML](#user-content-html)\n  - [JSON-API](#user-content-json-api)\n  - [Collection+JSON](#user-content-collection-json)\n  - [Transit](#user-content-transit)\n- [Hooks](#user-content-hooks)\n- [Policy over Configuration](#user-content-policy-over-configuration)\n  - [derive_mapper_from_object](#user-content-derive_mapper_from_object)\n  - [derive_mapper_from_association](#user-content-derive_mapper_from_association)\n  - [derive_rel_from_association](#user-content-derive_rel_from_association)\n- [Primitivizer](#user-content-primitivizer)\n- [Integration](#user-content-integration)\n- [Real World Usage](#user-content-real-world-usage)\n- [Demo](#user-content-demo)\n- [Cookbook](#user-content-cookbook)\n- [Standards Based](#user-content-standards-based)\n- [How to contribute](#user-content-how-to-contribute)\n- [License](#user-content-license)\n\n## Packages\n\n- [yaks-sinatra](yaks-sinatra/README.md)\n- [yaks-html](yaks-html/README.md)\n- [yaks-transit](yaks-transit/README.md)\n\n## State of Development\n\nRecent focus has been on stabilizing the core classes, improving\nformat support, and increasing test (mutation) coverage. We are\ncommitted to a stable public API and semantic version. On the 0.x line\nthe minor version is bumped when non-backwards compatible changes are\nintroduced. After 1.x regular semver conventions will be used.\n\n## Concepts\n\nYaks is a processing pipeline, you create and configure the pipeline,\nthen feed data through it.\n\n``` ruby\nyaks = Yaks.new do\n  default_format :hal\n  rel_template 'http://api.example.com/rels/{rel}'\n  format_options(:hal, plural_links: [:copyright])\n  mapper_namespace ::MyAPI\n  json_serializer do |data|\n    JSON.dump(data)\n  end\nend\n\nyaks.call(product)\n```\n\nYaks performs this serialization in three steps\n\n* It *maps* your data to a `Yaks::Resource`\n* It *formats* the resource to a syntax tree representation\n* It *serializes* to get the final output\n\nFor JSON types, the \"syntax tree\" is just a combination of Ruby primitives, nested arrays and hashes with strings, numbers, booleans, nils.\n\nA Resource is an abstraction shared by all output formats. It can contain key-value attributes, RFC5988 style links, and embedded sub-resources.\n\nTo build an API you create a \"mapper\" for each type of object you want to represent. Yaks takes care of the rest.\n\nFor all configuration options see [Yaks::Config::DSL](http://rdoc.info/gems/yaks/frames/Yaks/Config/DSL).\n\nSee also the [API Docs on rdoc.info](http://rdoc.info/gems/yaks/frames/file/README.md)\n\n## Mappers\n\nSay your app has a `Post` object for blog posts. To serve posts over your API, define a `PostMapper`\n\n```ruby\nclass PostMapper < Yaks::Mapper\n  link :self, '/api/posts/{id}'\n\n  attributes :id, :title\n\n  has_one :author\n  has_many :comments\nend\n```\n\nConfigure a Yaks instance and start serializing!\n\n```ruby\nyaks = Yaks.new\nyaks.call(post)\n```\n\nor a bit more elaborate\n\n```ruby\nyaks = Yaks.new do\n  default_format :json_api\n  rel_template 'http://api.example.com/rels/{rel}'\n  format_options(:hal, plural_links: [:copyright])\nend\n\nyaks.call(post, mapper: ::PostMapper, format: :hal)\n```\n\n### Attributes\n\nUse the `attribute` or `attributes` DSL methods to specify which attributes of your model you want to expose, as in the example above. You can override the `load_attribute` method to change how attributes are fetched from the model.\n\nFor example, if you are representing data that is stored in a Hash, you could do\n\n```ruby\nclass PostHashMapper < Yaks::Mapper\n  attributes :id, :body\n\n  # @param name [Symbol]\n  def load_attribute(name)\n    object[name]\n  end\nend\n```\nThe `attribute` method may also take a block that will be called with the context of the mapper instance. The default implementation will use the block if provided, otherwise it will first try to find a matching method for an attribute on the mapper itself, and will then fall back to calling the actual model. So you can add extra 'virtual' attributes like so :\n\n```ruby\nclass CommentMapper < Yaks::Mapper\n  attributes :body, :date\n  attribute :id do\n    \"Id-#{object.id}\"\n  end\n\n  def date\n    object.created_at.strftime(\"at %I:%M%p\")\n  end\nend\n```\n\n### Forms\n\nMapper can contain form defintions, for formats that support them. The\nform DSL mimics the HTML5 field and attribute names.\n\n```ruby\nclass PostMapper < Yaks::Mapper\n  attributes :id, :body, :date\n\n  form :add_comment do\n    action '/api/comments'\n    method 'POST'\n    media_type 'application/json'\n\n    text :body\n    hidden :post_id, value: -> { object.id }\n  end\nend\n```\n\nTODO: add more info on form element types, attributes, conditional\nrendering of forms, dynamic form sections, ...\n\n\n#### Filtering\n\nYou can override `#attributes`, or `#associations`.\n\n```ruby\nclass SongMapper < Yaks::Mapper\n  attributes :title, :duration, :lyrics\n\n  has_one :artist\n  has_one :album\n\n  def minimal?\n    env['HTTP_PREFER'] =~ /minimal/\n  end\n\n  # @return Array<Yaks::Mapper::Attribute>\n  def attributes\n    return super.reject {|attr| attr.name.equal? :lyrics } if minimal?\n    super\n  end\n\n  # @return Array<Yaks::Mapper::Association>\n  def associations\n    return [] if minimal?\n    super\n  end\nend\n```\n\n### Links\n\nYou can specify link templates that will be expanded with model attributes. The link relation name should be a registered [IANA link relation](http://www.iana.org/assignments/link-relations/link-relations.xhtml) or a URL. The template syntax follows [RFC6570 URI templates](http://tools.ietf.org/html/rfc6570).\n\n```ruby\nclass FooMapper < Yaks::Mapper\n  link :self, '/api/foo/{id}'\n  link 'http://api.foo.com/rels/comments', '/api/foo/{id}/comments'\nend\n```\n\nTo prevent a link to be expanded, add `expand: false` as an option. Now the actual template will be rendered in the result, so clients can use it to generate links from.\n\nTo partially expand the template, pass an array with field names to expand. e.g.\n\n```ruby\nclass ProductMapper < Yaks::Mapper\n  link 'http://api.foo.com/rels/line_item', '/api/line_items?product_id={product_id}&quantity={quantity}', expand: [:product_id]\nend\n\n# \"_links\": {\n#    \"http://api.foo.com/rels/line_item\": {\n#      \"href\": \"/api/line_items?product_id=273&quantity={quantity}\",\n#      \"templated\": true\n#    }\n# }\n\n```\n\nYou can pass a proc instead of a template, in that case the proc will\nbe resolved in the context of the mapper. What this means is that, if\nthe proc takes no arguments, it will be evaluated with the mapper\ninstance as the value of `self`. If the proc does take an argument,\nthen it will receive the mapper instance, and will be evaluated as a\nclosure, i.e. with access to the scope in which it was defined.\n\n```ruby\nclass FooMapper < Yaks::Mapper\n  link 'http://api.foo.com/rels/go_home', -> { home_url }\n  # by default calls object.home_url\n\n  def home_url\n    object.setting('home_url')\n  end\nend\n```\n\n\nTo only include links based on certain conditions, add an `:if`\noption, passing it a block. The block will be resolved in the context\nof the mapper, as explained before.\n\nFor example, say you want to notify the consumer of your API that upon\nconfirming an order, the previously held cart is no longer valid, you\ncould use the IANA standard `invalidates` rel to communicate this.\n\n``` ruby\nclass OrderMapper < Yaks::Mapper\n  link :invalidates, '/api/cart', if: ->{ env['api.invalidate_cart'] }\nend\n```\n\n### Associations\n\nUse `has_one` for an association that returns a single object, or `has_many` for embedding a collection.\n\nOptions\n\n* `:mapper` : Use a specific for each instance, will be derived from the class name if omitted (see Policy vs Configuration)\n* `:collection_mapper` : For mapping the collection as a whole, this defaults to Yaks::CollectionMapper, but you can subclass it for example to add links or attributes on the collection itself\n* `:rel` : Set the relation (symbol or URI) this association has with the object. Will be derived from the association name and the configured rel_template if ommitted\n* `:if`: Only render the association if a condition holds\n* `:link_if`: Conditionally render the association as a link. A `:href` option is required\n\n```ruby\nclass ShowMapper < Yaks::Mapper\n  has_many :events, href: '/show/{id}/events', link_if: ->{ events.count > 50 }\nend\n```\n\n### Behaviours\n\nYaks provides mixins to change how your mappers work. These need to be\nrequired separately, they are not loaded by default.\n\n#### OptionalIncludes\n\nYou may choose to not render associations by default, but to only do\nso when the client explicitly asks for them. This can be done by\nincluding `Yaks::Behaviour::OptionalIncludes`.\n\nWhich associations to load is specified with the the `include` query\nparameter. You can use dots to load nested associated.\n\n```ruby\nrequire \"yaks/behaviour/optional_includes\"\n\nclass PostMapper < Yaks::Mapper\n  include Yaks::Behaviour::OptionalIncludes\n\n  has_one :author\n  has_many :comments\nend\n\nclass AuthorMapper < Yaks::Mapper\n  include Yaks::Behaviour::OptionalIncludes\n\n  has_one :profile\nend\n```\n\n```\nGET /post/42?include=comments,author.profile\n```\n\nNote that this will only work when Yaks has access to the Rack\nenvironment. When using an existing integration like `yaks-sinatra`\nthis will be handled for you.\n\nTo force an association to always be included, override its `if`\ncondition to always return true.\n\n```ruby\nrequire \"yaks/behaviour/optional_includes\"\n\nclass PostMapper < Yaks::Mapper\n  include Yaks::Behaviour::OptionalIncludes\n\n  has_one :author\n  has_many :comments, if: ->{ true }\nend\n```\n\n## Calling Yaks\n\nOnce you have a Yaks instance, you can call it with `call`\n(`serialize` also works but might be deprecated in the future.) Pass\nit the data to be serialized, plus options.\n\n* `:env` a Rack environment, see next section\n* `:format` the format to be used, e.g. `:json_api`. Note that if the Rack env contains an `Accept` header which resolves to a recognized format, then the header takes precedence\n* `:mapper` the mapper to be used. Will be inferred if omitted\n* `:item_mapper` When rendering a collection, the mapper to be used for each item in the collection. Will be inferred from the class of the first item in the collection if omitted.\n\n### Rack env\n\nWhen serializing, Yaks lets you pass in an `env` hash, which will be made available to all mappers.\n\n```ruby\nclass FooMapper < Yaks::Mapper\n  attributes :bar\n\n  def bar\n    if env['something']\n      #...\n    end\n  end\nend\n\nyaks = Yaks.new\nyaks.call(foo, env: my_env)\n```\n\nThe env hash will be available to all mappers, so you can use this to\npass around context. In particular context related to the current HTTP\nrequest, e.g. the current logged in user, which is why the recommended\nuse is to pass in the Rack environment.\n\nIf `env` contains a `HTTP_ACCEPT` key (Rack's way of representing the\n`Accept` header), Yaks will return the format that most closely\nmatches what was requested.\n\n<a id=\"namespace\"></a>\n\n## Namespace\n\nYaks by default will find your mappers for you if they follow the\nnaming convention of appending 'Mapper' to the model class name. This\n(and all other \"conventions\") can be easily redefined though, see the\n<a href=\"#policy\">policy</a> section. If you have your mappers inside a\nmodule, use `mapper_namespace`.\n\n```ruby\nmodule API\n  module Mappers\n    class PostMapper < Yaks::Mapper\n      #...\n    end\n  end\nend\n\nyaks = Yaks.new do\n  mapper_namespace API::Mappers\nend\n```\n\nIf your namespace contains a `CollectionMapper`, Yaks will use that\ninstead of `Yaks::CollectionMapper`, e.g.\n\n```ruby\nmodule API\n  module Mappers\n    class CollectionMapper < Yaks::CollectionMapper\n      link :profile, 'http://api.example.com/profiles/collection'\n    end\n  end\nend\n```\n\nYou can also have collection mappers based on the type of members the\ncollection holds, e.g.\n\n```ruby\nmodule API\n  module Mappers\n    class LineItemCollectionMapper < Yaks::CollectionMapper\n      link :profile, 'http://api.example.com/profiles/line_items'\n      attributes :total\n\n      def total\n        collection.inject(0) do |memo, line_item|\n          memo + line_item.price * line_item.quantity\n        end\n      end\n    end\n  end\nend\n```\n\nYaks will automatically detect and use this collection when\nserializing an array of `LineItem` objects. See <a\nhref=\"#derive_mapper_from_object\">derive_mapper_from_object</a> for\ndetails.\n\n\n## Custom attribute/link/subresource handling\n\nWhen inheriting from `Yaks::Mapper`, you can override\n`map_attributes`, `map_links` and `map_resources` to skip (or augment)\nabove methods, and instead implement your own custom mechanism. These\nmethods take a `Yaks::Resource` instance, and should return an updated\nresource. They should not alter the resource instance in-place. For\nexample\n\n```ruby\nclass ErrorMapper < Yaks::Mapper\n  link :profile, '/api/error'\n\n  def map_attributes(resource)\n    attrs = {\n      http_code: 500,\n      message: object.to_s,\n      type: object.class.name.underscore\n    }\n\n    case object\n    when AllocationException\n      attrs[:http_code] = 422\n    when ActiveRecord::RecordNotFound\n      attrs[:http_code] = 404\n      attrs[:type] = \"record_not_found\"\n    end\n\n    resource.update_attributes(attrs)\n  end\nend\n```\n\n## Resources, Formatters, Serializers\n\nYaks uses an intermediate \"Resource\" representation to support\nmultiple output formats. A mapper turns a domain model into a\n`Yaks::Resource`. A formatter (e.g. `Yaks::Format::Hal`) takes\nthe resource and outputs the structure of the target format.\n\nFinally a serializer will take this document structure and turn it\ninto a string. For JSON documents the intermediate format consists of\nRuby primitives like arrays and hashes. HTML/XML based formats on the\nother hand return a [Hexp::Node](https://github.com/plexus/hexp).\n\nFor JSON based format there's an extra step between `format` and\n`serialize` called `primitivize`, this way Ruby objects which don't\nhave an equivalent in the JSON spec, like `Symbol` or `Date`, can be\nturned into objects that are representable in JSON. See\n[Primitiver](#primitivizer).\n\n## Formats\n\nBelow follows a brief overview of formats that are available in\nYaks. The maturity of these formats varies, since we depend on people\nthat use a certain format actively to contribute. Implementing formats\nis in generally straightforward, and consists mostly of deciding how\nthe attributes, links, forms, of a `Yaks::Resource` should be\nrepresented. Depending on the format this might be a subject for\ndebate. We welcome these discussions, and if your opinion differs from\nwhat ends up in Yaks, it should be trivial to change these\nrepresentations for your use case.\n\n### HAL\n\nThis is the default. In HAL one decides when building an API which\nlinks can only be singular (e.g. self), and which are always\nrepresented as an array. Yaks defaults to singular as I've found it to\nbe the most common case. If you want specific links to be plural, then\nconfigure their rel href as such.\n\n```ruby\nhal = Yaks.new do\n  format_options :hal, plural_links: ['http://api.example.com/rels/foo']\nend\n```\n\nCURIEs are not explicitly supported (yet), but it's possible to use\nthem with some manual effort.\n\nThe line between a singular resource and a collection is fuzzy in\nHAL. To stick close to the spec you're best to create your own\nsingular types that represent collections, rather than rendering a top\nlevel CollectionResource.\n\nYaks also has a derived format called HALO, which is a non-standard\nextension to HAL which includes form elements.\n\n### HTML\n\nThe hypermedia format *par excellence*. Yaks can generate a version of\nyour API, including links and forms, that is usable straight from a\nstandard web browser. This allows API interactions to be developed and\ntested independent from any client application.\n\nIf you let Yaks handle your content type negotiation (i.e. pass it the\nrack env, and honour the content type it detects, see\n[integration](#integration), simply opening a browser and pointing it\nat your API entry point should do the trick.\n\n### JSON-API\n\n```ruby\nYaks.new do\n  default_format :json_api\nend\n```\n\nJSON-API has no concept of outbound links, so these will not be\nrendered. Instead the key will be inferred from the mapper class name\nby default. This can be changed per mapper:\n\n```ruby\nclass AnimalMapper < Yaks::Mapper\n  type :pet\nend\n```\n\nOr the policy can be overridden:\n\n```ruby\nyaks = Yaks.new do\n  derive_type_from_mapper_class do |mapper_class|\n    piglatinize(mapper_class.to_s.sub(/Mapper$/, ''))\n  end\nend\n```\n\nFor optional includes, see [`Yaks::Behaviour::OptionalIncludes`](#user-content-behaviours).\n\n### Collection+JSON\n\nCollection+JSON has support for write templates. To use them, the `:template`\noption can be used. It will map the specified form to a CJ template. Please\nnotice that CJ only allows one template per representation.\n\n```ruby\nYaks.new do\n  default_format :collection_json\n\n  collection_json = Yaks.new do\n    format_options :collection_json, template: :my_template_form\n  end\nend\n\nclass PostMapper < Yaks::Mapper\n  form :my_template_form do\n    # This will be used for template\n  end\n\n  form :not_my_template do\n    # This won't be used for template\n  end\nend\n```\n\nSubresources aren't mapped because Collection+JSON doesn't really have\nthat concept.\n\n### Transit\n\nThere is experimental support for Transit. The transit gem handles\nserialization internally, so there is no intermediate document. The\n`format` step already returns the serialized string.\n\n## Hooks\n\nIt is possible to hook into the Yaks pipeline to perform extra\nprocessing steps before, after, or around each step. It also possible\nto skip a step.\n\n``` ruby\nyaks = Yaks.new do\n  # Automatically give every resource a self link\n  after :map, :add_self_link do |resource|\n    resource.add_link(Yaks::Resource::Link.new(:self, \"/#{resource.type}/#{resource.attributes[:id]}\"))\n  end\n\n  # Skip serialization, so Ruby primitives come back instead of JSON\n  # This was the default before versions < 0.5.0\n  skip :serialize\nend\n```\n\n<a id=\"policy\"></a>\n\n## Policy over Configuration\n\nIt's an old adage in the Ruby/Rails world to have \"Convention over Configuration\", mostly to derive values that were not given explicitly. Typically based on things having similar names and a 1-1 derivable relationship.\n\nThis saves a lot of typing, but for the uninitiated it can also create confusion, the implicitness makes it hard to follow what's going on.\n\nWhat's worse, is that often the Configuration part is skipped entirely, making it very hard to deviate from the Golden Standard.\n\nThere is another old adage, \"Policy vs Mechanism\". Implement the mechanisms, but don't dictate the policy.\n\nIn Yaks whenever missing values need to be inferred, like finding an unspecified mapper for a relation, this is handled by a policy object. The default is `Yaks::DefaultPolicy`, you can go there to find all the rules of inference. Single rules of inference can be redefined directly in the Yaks configuration:\n\n```ruby\nyaks = Yaks.new do\n  mapper_for Post, SpecialMapper\n\n  derive_mapper_from_object do |model|\n    # ...\n  end\n\n  derive_mapper_from_collection do |collection|\n    # ...\n  end\n\n  derive_mapper_from_item do |model|\n    # ...\n  end\n\n  derive_type_from_mapper_class do |mapper_class|\n    # ...\n  end\n\n  derive_mapper_from_association do |association|\n    # ...\n  end\n\n  derive_rel_from_association do |mapper, association|\n    # ...\n  end\nend\n```\n\nNote that within these blocks, you may call `super()` which would call\nthe default implementation.\n\nYou can also subclass or create from scratch your own policy class\n\n```ruby\nclass MyPolicy < Yaks::DefaultPolicy\n  #...\nend\n\nyaks = Yaks.new do\n  policy_class MyPolicy\nend\n```\n\n<a id=\"derive_mapper_from_object\"></a>\n\n### derive_mapper_from_object\n\nThis is called when trying to serialize something and no explicit\nmapper is given. To recap, it's always possible to be explicit, e.g.\n\n```\nyaks.call(widget, mapper: WidgetMapper)\nyaks.call(array_of_widgets, mapper: MyCollectionMapper, item_mapper: WidgetMapper)\n```\n\nIf the mapper is left unspecified, Yaks will inspect whatever you pass\nit. First it will test the given object against the mappings defined using `mapper_for`.\nIf no mapper is found, it will call `derive_mapper_from_item` or `derive_mapper_from_collection`\ndepending on whether the given object is a collection or not. If the object responds\nto `to_ary` it is considered a collection.\n\n### mapper_for\n\nThis method allows you to define a one-to-one mapping between a mapping rule and a mapper class.\nDuring the lookup, Yaks will check if any mapping rule matches the given object using the `#===`\noperator.\n\nHere are a few examples on how to use it:\n```ruby\nyaks = Yaks.new do\n  mapper_for(:home, HomeMapper)\n  mapper_for(Post, SpecialMapper)\n  mapper_for(->(author) { author.respond_to?(:name) && author.name == 'doh' }, AuthorMapper)\nend\n\nyaks.call(:home) # would map using HomeMapper\nyaks.call(Post.new) # would map using PostMapper\nyaks.call(Author.new(name: 'doh')) # would map using AuthorMapper\n```\n\n### derive_mapper_from_collection\nThis method will try various constant lookups based on naming. These all happen\nin the configured namespace, which defaults to the Ruby top level.\n\nIf the first object in the collection has a class of `Widget`, and the\nconfigured namespace is `API`, then these are tried in turn\n\n* `API::WidgetCollectionMapper`\n* `API::CollectionMapper`\n* `Yaks::CollectionMapper`\n\nNote that Yaks can only find a specific collection mapper for a type\nif the collection passed to Yaks contains at least one element. If\nit's important that empty collections are handled by the right mapper\n(e.g. to set a specific `self` or `profile` link), then you have to be\nexplicit.\n\n### derive_mapper_from_item\n\nWhen using this method, the lookup happens based on the class name,\nand will traverse up the class hierarchy in the configured namespace if\nno suitable mapper is found. Take the following\ncode:\n```ruby\nmodule Stuff\n  class Thing ; end\n  class Widget < Thing ; end\nend\n```\nThe lookup we'll be done as followed.\n\n* If the `namespace` option is set (to `Mappers` for example):\n * `Mappers::Stuff::WidgetMapper`\n * `Mappers::Stuff::ThingMapper`\n * `Mappers::Stuff::ObjectMapper`\n * `Mappers::Stuff::BasicObjectMapper`\n * `Mappers::WidgetMapper`\n * `Mappers::ThingMapper`\n\n* If the `namespace` option is not set:\n * `Stuff::WidgetMapper`\n * `Stuff::ThingMapper`\n * `Stuff::ObjectMapper`\n * `Stuff::BasicObjectMapper`\n * `WidgetMapper`\n * `ThingMapper`\n\nIf none of these are found an error is raised.\n\n### derive_mapper_from_association\n\nWhen no mapper is specified for an association, then this method is\ncalled to find the right mapper, based on the association name. In\ncase of `has_many` collections this is the \"item mapper\", the\ncollection mapper is resolved using `derive_mapper_from_object`.\n\nBy default the mapper class is derived from the name of the association, e.g.\n\n```\nhas_many :widgets #=> WidgetMapper\nhas_one :widget   #=> WidgetMapper\n```\n\nIt is always possible to explicitly set a mapper.\n\n```\nhas_one :widget, mapper: FooMapper\nhas_many :widgets, collection_mapper: MyCollectionMapper, mapper: FooMapper\n```\n\n### derive_rel_from_association\n\nAssociations have a \"rel\", an IANA registered identifier or fully\nqualified URI, that specifies how the object relates to the parent\ndocument.\n\nWhen configuring Yaks one can set a `rel_template`, that will be used\nto generate these rels if not explicitly given. The `rel` placeholder\nin the template will be substituted with the association name.\n\n``` ruby\nyaks = Yaks.new do\n  rel_template \"http://api.example.com/rel/{rel}\"\nend\n\nclass MyMapper < Yaks::Mapper\n  # rel: \"http://api.example.com/rel/widgets\"\n  has_many :widgets\n\n  # rel: \"http://api.example.com/rel/widget\"\n  has_one :widget\nend\n```\n\n<a id=\"primitivizer\"></a>\n\n## Primitivizer\n\nFor JSON based formats, the \"syntax tree\" is merely a structure of Ruby primitives that have a JSON equivalent. If your mappers return non-primitive attribute values, you can define how they should be converted. For example, JSON has no notion of dates. If your mappers return these types as attributes, then Yaks needs to know how to turn these into primitives. To add extra types, use `map_to_primitive`\n\nHere's an example with a custom `Currency` class, which can be represented as an integer.\n\n```ruby\nYaks.new do\n  map_to_primitive Currency do |currency|\n    currency.to_i\n  end\nend\n```\n\nOne notable use case is representing dates and times. The JSON\nspecification does not define any syntax for these, so the only\nsolution is to represent them either as numbers or strings. If you're\nnot sure what to do with these then the ISO8601 standard is a safe\nbet. It defines a way to represent times and dates as strings, and is\nalso adopted by the W3C in [RFC3339](http://tools.ietf.org/html/rfc3339).\n\nAn alternative representation that is sometimes used is \"unix time\",\ndefined as the numbers of seconds passed since 1 January 1970.\n\nHere's an example for a Rails app, so including ActiveSupport's `TimeWithZone`.\n\n```ruby\nYaks.new do\n  map_to_primitive Date, Time, DateTime, ActiveSupport::TimeWithZone, &:iso8601\nend\n```\n\n`map_to_primitive` can also be used to transform alternative data\nstructures, like those from [Hamster](https://github.com/hamstergem/hamster),\ninto Ruby arrays and hashes. Use `call()` to recursively turn things into\nprimitives.\n\n```ruby\nYaks.new do\n  map_to_primitive Hamster::Vector, Hamster::List do |list|\n    list.map do |item|\n      call(item)\n    end\n  end\nend\n```\n\nYaks by default \"primitivizes\" symbols (as strings), and classes that include Enumerable (as arrays).\n\n\n<a id=\"integration\"></a>\n\n## Integration\n\nIt is recommended to let Yaks handle the negotiation of media types,\nso that consumer can request the format they prefer using an `Accept:`\nheader. To do this requires two steps: first make sure you pass the\nrack env to Yaks, this way it will detect any `Accept` header and\nhonor it. While this is enough to get the correct serialized output,\nit will likely be served up with the wrong `Content-Type` header by\nyour web framework.\n\nTo fix this, ask Yaks first for the \"runner\" for a given input, then\nget the media type and serialized resource from the runner.\n\n```ruby\n# Tell your web framework about the supported formats\nYaks::Format.all.each do |format|\n  mime_type format.format_name, format.media_type\nend\n\n# one time Yaks configuration\nyaks = Yaks.new\n\n# on each request\nrunner = yaks.runner(post, env: rack_env)\nformat = runner.format_name\noutput = runner.call\n```\n\n\n## Real World Usage\n\nYaks is used in production by\n\n* [Ticketsolve](http://www.ticketsolve.com/). You can find an example API endpoint [here](http://leicestersquaretheatre.ticketsolve.com/api).\n* Advertile Mobile for their product AppBounty (internal API)\n\n## Demo\n\nYou can find an outdated example app at [Yakports](https://github.com/plexus/yakports), or browse the HAL api directly using the [HAL browser](http://yaks-airports.herokuapp.com/browser.html).\n\n## Cookbook\n\nSee the [cookbook](COOKBOOK.md) for some usage examples taking from a real world app.\n\n## Standards Based\n\nYaks is based on internet standards, including\n\n* [RFC4288 Media types](http://tools.ietf.org/html/rfc4288)\n* [RFC5988 Web Linking](http://tools.ietf.org/html/rfc5988)\n* [RFC6906 The \"profile\" link relation](http://tools.ietf.org/search/rfc6906)\n* [RFC6570 URI Templates](http://tools.ietf.org/html/rfc6570)\n* [RFC4229 HTTP Header Field Registrations](http://tools.ietf.org/html/rfc4229).\n\n## How to contribute\n\nRun the tests, the examples, try it with your own stuff and leave your impressions in the issues.\n\nTo fix a bug\n\n1. Fork the repo\n2. Fix the bug, add tests for it\n3. Push it to a named branch\n4. Add a PR\n\nTo add a feature\n\n1. Open an issue as soon as possible to gather feedback\n2. Same as above, fork, push to named branch, make a pull-request\n\nYaks uses [Mutation Testing](https://github.com/mbj/mutant). Run `rake mutant` and look for percentage coverage. In general this should only go up.\n\n## License\n\nMIT License (Expat License), see [LICENSE](./LICENSE)\n\n![](shaved_yak.gif)\n"
  },
  {
    "path": "yaks/Rakefile",
    "content": "load '../shared/rake_tasks.rb'\n\ngem_tasks(:yaks)\n\ntask :mutant_chunked do\n  # No subjects:\n  # Yaks,\n  # Yaks::Error,\n  # Yaks::IllegalStateError,\n  # Yaks::UnsupportedOperationError,\n  # Yaks::PrimitivizeError,\n  # Yaks::Undefined,\n  # Yaks::HTML5Forms,\n\n  # Hangs:\n  # Yaks::Changelog,\n\n  # 100% verified:\n  # Yaks::Util,\n  # Yaks::Util::Deprecated,\n  # Yaks::FP\n  # Yaks::FP::Callable,\n  # Yaks::DefaultPolicy,\n  # Yaks::Mapper::HasOne,\n  # Yaks::Mapper::HasMany,\n  # Yaks::Mapper::Attribute,\n  # Yaks::Mapper::Config,\n  # Yaks::Mapper::ClassMethods,\n  # Yaks::Mapper::AssociationMapper,\n  # Yaks::Format::CollectionJson,\n  # Yaks::Config,\n  # Yaks::Config::DSL,\n  # Yaks::Attributes::InstanceMethods,\n  # Yaks::Configurable,\n  # Yaks::NullResource,\n  # Yaks::Runner,\n\n  [\n    # >> Yaks::Attributes\n    # >> Yaks::Attributes::InstanceMethods\n    # >> Yaks::Builder\n    # >> Yaks::CollectionMapper\n    # >> Yaks::CollectionResource\n    # >> Yaks::Config\n    # >> Yaks::Configurable\n    # >> Yaks::DefaultPolicy\n    # >> Yaks::Error\n    # >> Yaks::FP\n    # >> Yaks::FP::Callable\n    # >> Yaks::Format\n    # >> Yaks::Format::CollectionJson\n    # >> Yaks::Format::HTML\n    # >> Yaks::Format::Hal\n    # >> Yaks::Format::Halo\n    # >> Yaks::Format::JsonAPI\n    # >> Yaks::Format::Reader\n    # >> Yaks::Format::Transit\n    # >> Yaks::Format::Transit::ReadHandler\n    # >> Yaks::Format::Transit::WriteHandler\n    # >> Yaks::HTML5Forms\n    # >> Yaks::IllegalStateError\n    # >> Yaks::Mapper\n    # >> Yaks::Mapper::Association\n    # >> Yaks::Mapper::AssociationMapper\n    # >> Yaks::Mapper::Attribute\n    # >> Yaks::Mapper::Config\n    # >> Yaks::Mapper::Form\n    # >> Yaks::Mapper::Form::Config\n    # >> Yaks::Mapper::Form::Field\n    # >> Yaks::Mapper::Form::Field::Option\n    # >> Yaks::Mapper::Form::Fieldset\n    # >> Yaks::Mapper::HasMany\n    # >> Yaks::Mapper::HasOne\n    # >> Yaks::Mapper::Link\n    # >> Yaks::NullResource\n    # >> Yaks::Pipeline\n    # >> Yaks::Primitivize\n    # >> Yaks::PrimitivizeError\n    # >> Yaks::Reader\n    # >> Yaks::Reader::Hal\n    # Yaks::Resource,\n    # Yaks::Resource::Form,\n    # Yaks::Resource::Form::Field,\n    # Yaks::Resource::Form::Field::Option,\n    # Yaks::Resource::Form::Fieldset,\n    # Yaks::Resource::Link,\n    Yaks::Resource::HasFields,\n    # >> Yaks::Runner\n    # >> Yaks::RuntimeError\n    # >> Yaks::Serializer\n    # >> Yaks::Serializer::JSONReader\n    # >> Yaks::Serializer::JSONWriter\n    # >> Yaks::Undefined\n    # >> Yaks::UnsupportedOperationError\n    # >> Yaks::Util\n    # >> Yaks::Util::Deprecated\n  ].each do |space|\n    puts space\n    ENV['PATTERN'] = \"#{space}\"\n    Rake::Task[\"mutant\"].execute\n    break\n  end\nend\n"
  },
  {
    "path": "yaks/ataru_setup.rb",
    "content": "# \"Require your project source code, with the correct path\"\n\nrequire \"yaks\"\nrequire \"hamster\"\n\nPost = Struct.new(:id, :title, :author, :comments)\nAuthor = Struct.new(:name)\n\nmodule MyAPI\n  Product = Struct.new(:id, :label)\n\n  class ProductMapper < Yaks::Mapper\n    attributes :id, :label\n  end\nend\n\nclass AuthorMapper < Yaks::Mapper\nend\n\nclass CommentMapper < Yaks::Mapper\nend\n\nclass PostMapper < Yaks::Mapper\n  link :self, '/api/posts/{id}'\n\n  attributes :id, :title\n\n  has_one :author\n  has_many :comments\nend\n\nclass HomeMapper < Yaks::Mapper; end\n\nclass SpecialMapper < Yaks::Mapper; end\n\nmodule ActiveSupport\n  class TimeWithZone < Time ; end\nend\n\nclass Currency ; end\n\nmodule Setup\n  def setup\n    # Do some nice setup that is run before every snippet\n    # If you'd like to use instance variables define them here, e.g\n    #  @important_variable_i_will_use_in_my_code_snippets = true\n  end\n\n  def teardown\n    # Do some cleanup that is run after every snippet\n  end\n\n  # If you like local variables you can define methods, e.g\n  # def number_of_wishes\n  #  101\n  # end\n\n  def my_env\n    {'something' => true}\n  end\n  alias_method :rack_env, :my_env\n\n  def post\n    Post.new(7, \"Yaks is Al Dente\", nil, [])\n  end\n  alias_method :foo, :post\n\n  def product\n    MyAPI::Product.new(42, \"Shiny thing\")\n  end\n\n  # # Tell your web framework about the supported formats\n  # Yaks::Format.all.each do |format|\n  #   mime_type format.format_name, format.media_type\n  # end\n  def mime_type(*_args)\n  end\nend\n"
  },
  {
    "path": "yaks/find_missing_tests.rb",
    "content": "#!/usr/bin/env ruby\n\nrequire 'mutant'\nrequire 'pry'\n\n# These are private methods that are tested by other methods in the same class\nSKIP = %w[\n  Yaks::CollectionMapper#collection_rel\n  Yaks::CollectionMapper#collection_type\n  Yaks::CollectionMapper#mapper_for_model\n  Yaks::Resource::Form::Field#select_options_for_value\n  Yaks::Mapper::AssociationMapper#add_link\n  Yaks::Mapper::AssociationMapper#add_subresource\n  Yaks::Mapper::Link#resource_link_options\n]\n\nargs = [\"-Ilib\", \"-ryaks\", \"--use\", \"rspec\", \"Yaks*\"]\nenv = Mutant::Env::Bootstrap.call(Mutant::CLI.call(args))\n\nintegration = env.config.integration\n\nintegration.setup\nbinding.pry if integration.all_tests.empty? # rubocop:disable Lint/Debugger\n\nenv.subjects.each do |subject|\n  match_expression = subject.match_expressions.first\n  subject_tests = integration.all_tests.select do |test|\n    match_expression.prefix?(test.expression)\n  end\n  unless subject_tests.any? || SKIP.include?(subject.expression.syntax)\n    puts subject.identification\n    exit if ARGV.include?(\"-1\")\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/behaviour/optional_includes.rb",
    "content": "require \"rack/utils\"\n\nmodule Yaks\n  module Behaviour\n    module OptionalIncludes\n      RACK_KEY = \"yaks.optional_includes\".freeze\n\n      def associations\n        super.select do |association|\n          association.if != Undefined || include_association?(association)\n        end\n      end\n\n      private\n\n      def include_association?(association)\n        includes = env.fetch(RACK_KEY) do\n          query_string = env.fetch(\"QUERY_STRING\", nil)\n          query = Rack::Utils.parse_query(query_string)\n          env[RACK_KEY] = query.fetch(\"include\", \"\").split(\",\").map { |r| r.split(\".\") }\n        end\n\n        includes.any? do |relationship|\n          relationship[mapper_stack.size].eql?(association.name.to_s)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/breaking_changes.rb",
    "content": "module Yaks\n# These are displayed in a post-install message when installing the\n# gem to aid upgraiding\n\nBreakingChanges = {\n\n  '0.7.6' => %q~\nBreaking Changes in Yaks 0.7.6\n==============================\nBreaking change: using a symbol instead of link template no longer\nworks, use a lambda.\n\n    link :foo, :bar\n\nBecomes\n\n    link :foo, ->{ bar }\n\nStrictly speaking the equivalent version would be `link :foo, ->{\nload_attribute(:bar) }`. Depending on if `bar` is implemented on the\nmapper or is an attribute of the object, this would simplify to `link\n:foo, ->{ bar }` or `link :foo, ->{ object.bar }` respectively.\n\nThe `href` attribute of a control has been renamed `action`, in line\nwith the attribute name in HTML. An alias is available but will output\na deprecation warning.\n~,\n\n  '0.7.0' => %q~\nBreaking Changes in Yaks 0.7.0\n==============================\nYaks::Resource#subresources is now an array, not a hash. The rel is\nstored on the resource itself as Yaks::Resource#rels (an array). This\nshould only be of concern if you implement custom output formats\n\nThe general signature of all processing steps (mapper, formatter,\nhooks) has changed to incldue a second parameter, the rack env. If you\nhave custom implementations of any of these, or hooks that are not\nspecified as ruby blocks, you will need to take this into account\n~,\n\n  '0.5.0' => %q~\n\nBreaking Changes in Yaks 0.5.0\n==============================\n\nYaks now serializes its output, you no longer have to convert to JSON\nyourself. Use `skip :serialize' to get the old behavior, or\n`json_serializer` to use a different JSON implementation.\n\nThe single `after' hook has been replaced with a set of `before',\n`after', `around' and `skip' hooks.\n\nIf you've created your own subclass of `Yaks::Format' (previously:\n`Yaks::Serializer'), then you need to update the call to\n`Format.register'.\n\nThese are potentially breaking changes. See the CHANGELOG and README\nfor full documentation.\n\n~,\n\n  '0.4.3' => %q~\n\nBreaking Changes in Yaks 0.4.3\n==============================\n\nYaks::Mapper#filter was removed, if you override this method in your\nmappers to conditionally filter attributes or associations, you will\nhave to override #attributes or #associations instead.\n\nWhen specifying a rel_template, now a single {rel} placeholder is\nexpected instead of {src} and {dest}.\n\nThere are other internal changes. See the CHANGELOG and README for full\ndocumentation.\n\n~\n}\n\nBreakingChanges['0.4.4'] = BreakingChanges['0.4.3']\nBreakingChanges['0.7.1'] = BreakingChanges['0.7.0']\nend\n"
  },
  {
    "path": "yaks/lib/yaks/builder.rb",
    "content": "module Yaks\n  # State monad-ish thing.\n  #\n  # Generate a DSL syntax for immutable classes.\n  #\n  # @example\n  #\n  #   # This code\n  #   Form.create(:search)\n  #       .method(\"POST\")\n  #       .action(\"/search\")\n  #\n  #   # Can be written as\n  #   Builder.new(Form, [:method, :action]).create(:search) do\n  #     method \"POST\"\n  #     action \"/search\"\n  #   end\n  #\n  class Builder\n    include Configurable\n\n    def initialize(klass, methods = [], &block)\n      @klass = klass\n      @methods = methods\n      def_forward(*methods) if methods.any?\n      instance_eval(&block) if block\n    end\n\n    def create(*args, &block)\n      build(@klass.create(*args), &block)\n    end\n\n    def build(init_state, *extra_args, &block)\n      @config = init_state\n      instance_exec(*extra_args, &block) if block\n      @config\n    end\n\n    def inspect\n      \"#<Builder #{@klass} #{@methods}>\"\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/changelog.rb",
    "content": "module Yaks\n  module Changelog\n    module_function\n\n    def current\n      versions[Yaks::VERSION]\n    end\n\n    def versions\n      markdown.split(/(?=###\\s*[\\d\\w\\.]+\\n)/).each_with_object({}) do |section, hsh|\n        version = section.each_line.first[/[\\d\\w\\.]+/]\n        log     = section.each_line.drop(1).join.strip\n        hsh[version] = log\n      end\n    end\n\n    def markdown\n      Pathname(__FILE__).join('../../../../CHANGELOG.md').read\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/collection_mapper.rb",
    "content": "module Yaks\n  class CollectionMapper < Mapper\n    alias_method :collection, :object\n\n    # @param [Array] collection\n    # @return [Array]\n    def call(collection, _env = nil)\n      @object = collection\n\n      attrs = {\n        type: collection_type,\n        members: collection().map do |obj|\n          mapper_for_model(obj).new(context).call(obj)\n        end\n      }\n\n      # For collections from associations the rel will be based on the\n      # association. At the top level there's no association, so we\n      # use a generic rel. This matters especially for HAL, where a\n      # top-level collection is rendered as an object with the\n      # collection as a subresource.\n      attrs[:rels] = [collection_rel] if context[:mapper_stack].empty?\n\n      map_attributes(\n        map_links(\n          CollectionResource.new(attrs)\n        )\n      )\n    end\n\n    private\n\n    def collection_rel\n      if collection_type\n        policy.expand_rel(pluralize(collection_type))\n      else\n        'collection'\n      end\n    end\n\n    def collection_type\n      if item_mapper = context[:item_mapper]\n        item_mapper.config.type || policy.derive_type_from_mapper_class(item_mapper)\n      else\n        policy.derive_type_from_collection(collection)\n      end\n    end\n\n    def mapper_for_model(model)\n      context.fetch(:item_mapper) do\n        policy.derive_mapper_from_object(model)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/collection_resource.rb",
    "content": "module Yaks\n  # A collection of Resource objects, it has members, and its own set of link\n  # relations like self and profile describing the collection.\n  #\n  # A collection can be the top-level result of an API call, like all posts to\n  # a blog, or a subresource collection, like the comments on a post result.\n  #\n  class CollectionResource < Resource\n    include attributes.add(members: [])\n\n    extend Forwardable\n    def_delegators :members, :each, :map, :each_with_object\n\n    # @return [Boolean]\n    def collection?\n      true\n    end\n\n    def seq\n      self\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/config.rb",
    "content": "module Yaks\n  class Config\n    extend Yaks::Util::Deprecated\n    include Yaks::FP::Callable,\n            Attribs.new(\n              format_options_hash: Hash.new({}),\n              default_format: :hal,\n              policy_options: {},\n              policy_class: DefaultPolicy,\n              primitivize: Primitivize.create,\n              serializers: Serializer.all,\n              hooks: []\n            )\n\n    class << self\n      alias_method :create, :new\n    end\n\n    deprecated_alias :namespace, :mapper_namespace\n\n    def format_options(format, options)\n      with(format_options_hash: format_options_hash.merge(format => options))\n    end\n\n    def serializer(type, &serializer)\n      with(serializers: serializers.merge(type => serializer))\n    end\n\n    def json_serializer(&serializer)\n      serializer(:json, &serializer)\n    end\n\n    %w[before after around skip].map(&:intern).each do |hook_type|\n      define_method hook_type do |step, name = :\"#{hook_type}_#{step}\", &block|\n        append_to(:hooks, [hook_type, step, name, block])\n      end\n    end\n\n    def rel_template(template)\n      with(policy_options: policy_options.merge(rel_template: template))\n    end\n\n    def mapper_namespace(namespace)\n      with(policy_options: policy_options.merge(namespace: namespace))\n    end\n\n    def mapper_for(rule, mapper_class)\n      policy_options[:mapper_rules] ||= {}\n      mapper_rules = policy_options[:mapper_rules].merge(rule => mapper_class)\n      with(policy_options: policy_options.merge(mapper_rules: mapper_rules))\n    end\n\n    def map_to_primitive(*args, &block)\n      with(primitivize: primitivize.dup.tap { |prim| prim.map(*args, &block) })\n    end\n\n    DefaultPolicy.public_instance_methods(false).each do |method|\n      define_method method do |&block|\n        with(\n          policy_class: Class.new(policy_class) do\n            define_method method, &block\n          end\n        )\n      end\n    end\n\n    # @return [Yaks::DefaultPolicy]\n    def policy\n      @policy ||= @policy_class.new(@policy_options)\n    end\n\n    def runner(object, options)\n      Runner.new(config: self, object: object, options: options)\n    end\n\n    # Main entry point into yaks\n    #\n    # @param object [Object] The object to serialize\n    # @param options [Hash<Symbol,Object>] Serialization options\n    #\n    # @option env [Hash] The rack environment\n    # @option format [Symbol] The target format, default :hal\n    # @option mapper [Class] Mapper class to use\n    # @option item_mapper [Class] Mapper class to use for items in a top-level collection\n    #\n    def call(object, options = {})\n      runner(object, options).call\n    end\n    alias_method :serialize, :call\n\n    def map(object, options = {})\n      runner(object, options).map\n    end\n\n    def format(data, options = {})\n      runner(data, options).format\n    end\n\n    def read(data, options = {})\n      runner(data, options).read\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/configurable.rb",
    "content": "module Yaks\n  # A \"Configurable\" class is one that keeps a configuration in a\n  # separate immutable object, of type class::Config. say you have\n  #\n  #     class MyMapper < Yaks::Mapper\n  #       # use yaks configuration DSL in here\n  #     end\n  #\n  # The links, associations, etc, that you set up for MyMapper, will\n  # be available in MyMapper.config, which is an instance of\n  # Yaks::Mapper::Config.\n  #\n  # Each configuration step, like `link`, `has_many`, will replace\n  # MyMapper.config with an updated version, discarding the old\n  # config.\n  #\n  # By extending Configurable, a number of \"macros\" become available\n  # to describe the DSL that subclasses can use. See the docs for\n  # `def_set`. `def_forward`, and `def_add`.\n  module Configurable\n    attr_accessor :config\n\n    def self.extended(child)\n      child.config = child::Config.new\n    end\n\n    def inherited(child)\n      child.config = config\n    end\n\n    # Create a DSL method to set a certain config property. The\n    # generated method will take either a plain value, or a block,\n    # which will be captured and stored instead.\n    def def_set(*method_names)\n      method_names.each do |method_name|\n        define_singleton_method method_name do |arg = Undefined, &block|\n          if arg.equal?(Undefined)\n            unless block\n              raise ArgumentError, \"setting #{method_name}: no value and no block given\"\n            end\n            self.config = config.with(method_name => block)\n          else\n            if block\n              raise ArgumentError, \"ambiguous invocation setting #{method_name}: give either a value or a block, not both.\"\n            end\n            self.config = config.with(method_name => arg)\n          end\n        end\n      end\n    end\n\n    # Forward a method to the config object. This assumes the method\n    # will return an updated config instance.\n    #\n    # Either takes a list of methods to forward, or a mapping (hash)\n    # of source to destination method name.\n    def def_forward(mappings, *names)\n      if mappings.instance_of? Hash\n        mappings.each do |method_name, target|\n          define_singleton_method method_name do |*args, &block|\n            self.config = config.public_send(target, *args, &block)\n          end\n        end\n      else\n        def_forward([mappings, *names].map{|name| {name => name}}.inject(:merge))\n      end\n    end\n\n    # Generate a DSL method that creates a certain type of domain\n    # object, and adds it to a list on the config.\n    #\n    #     def_add :fieldset, create: Fieldset, append_to: :fields\n    #\n    # This will generate a `fieldset` method, which will call\n    # `Fieldset.create`, and append the result to `config.fields`\n    def def_add(name, options)\n      old_verbose, $VERBOSE = $VERBOSE, false # skip method redefinition warning\n      define_singleton_method name do |*args, &block|\n        defaults = options.fetch(:defaults, {})\n        klass    = options.fetch(:create)\n\n        if args.last.instance_of?(Hash)\n          args[-1] = defaults.merge(args[-1])\n        else\n          args << defaults\n        end\n\n        self.config = config.append_to(\n          options.fetch(:append_to),\n          klass.create(*args, &block)\n        )\n      end\n    ensure\n      $VERBOSE = old_verbose\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/default_policy.rb",
    "content": "\nmodule Yaks\n  class DefaultPolicy\n    include Util\n\n    # Default policy options.\n    DEFAULTS = {\n      rel_template: \"rel:{rel}\",\n      namespace: Object,\n      mapper_rules: {}\n    }\n\n    # @!attribute [r]\n    #   @return [Hash]\n    attr_reader :options\n\n    # @param options [Hash] options\n    def initialize(options = {})\n      @options = DEFAULTS.merge(options)\n    end\n\n    # Main point of entry for mapper derivation. Calls\n    # derive_mapper_from_collection or derive_mapper_from_item\n    # depending on the model.\n    #\n    # @param model [Object]\n    # @return [Class] A mapper, typically a subclass of Yaks::Mapper\n    #\n    # @raise [RuntimeError] occurs when no mapper is found\n    def derive_mapper_from_object(model)\n      mapper = detect_configured_mapper_for(model)\n      return mapper if mapper\n      return derive_mapper_from_collection(model) if model.respond_to? :to_ary\n      derive_mapper_from_item(model)\n    end\n\n    # Derives a mapper from the given collection.\n    #\n    # @param collection [Object]\n    # @return [Class] A mapper, typically a subclass of Yaks::Mapper\n    def derive_mapper_from_collection(collection)\n      if m = collection.first\n        name = \"#{m.class.name.split('::').last}CollectionMapper\"\n        begin\n          return @options[:namespace].const_get(name)\n        rescue NameError               # rubocop:disable Lint/HandleExceptions\n        end\n      end\n      begin\n        return @options[:namespace].const_get(:CollectionMapper)\n      rescue NameError                 # rubocop:disable Lint/HandleExceptions\n      end\n      CollectionMapper\n    end\n\n    # Derives a mapper from the given item. This item should not\n    # be a collection.\n    #\n    # @param item [Object]\n    # @return [Class] A mapper, typically a subclass of Yaks::Mapper\n    #\n    # @raise [RuntimeError] only occurs when no mapper is found for the given item.\n    def derive_mapper_from_item(item)\n      klass = item.class\n      namespaces = klass.name.split(\"::\")[0...-1]\n      begin\n        return build_mapper_class(namespaces, klass)\n      rescue NameError\n        klass = next_class_for_lookup(item, namespaces, klass)\n        retry if klass\n      end\n      raise_mapper_not_found(item)\n    end\n\n    # Derive the a mapper type name\n    #\n    # This returns the 'system name' for a mapper,\n    # e.g. ShowEventMapper => show_event.\n    #\n    # @param [Class]  mapper_class\n    #\n    # @return [String]\n    def derive_type_from_mapper_class(mapper_class)\n      underscore(mapper_class.name.split('::').last.sub(/Mapper$/, ''))\n    end\n\n    # Derive the mapper type name from a collection\n    #\n    # This inspects the first element of the collection, so\n    # it requires a collection with truthy elements. Will\n    # return `nil` if the collection has no truthy elements.\n    #\n    # @param [#first] collection\n    #\n    # @return [String|nil]\n    #\n    # @raise [NameError]\n    def derive_type_from_collection(collection)\n      return if collection.none?\n      derive_type_from_mapper_class(derive_mapper_from_object(collection.first))\n    end\n\n    def derive_mapper_from_association(association)\n      @options[:namespace].const_get(\"#{camelize(association.singular_name)}Mapper\")\n    end\n\n    # @param association [Yaks::Mapper::Association]\n    # @return [String]\n    def derive_rel_from_association(association)\n      expand_rel(association.name)\n    end\n\n    # @param relname [String]\n    # @return [String]\n    def expand_rel(relname)\n      URITemplate.new(@options[:rel_template]).expand(rel: relname)\n    end\n\n    private\n\n    def build_mapper_class(namespaces, klass)\n      mapper_class = \"#{klass.name.split('::').last}Mapper\"\n      [*namespaces, mapper_class].inject(@options[:namespace]) do |namespace, module_or_class|\n        namespace.const_get(module_or_class, false)\n      end\n    end\n\n    def next_class_for_lookup(item, namespaces, klass)\n      superclass = klass.superclass\n      return superclass if superclass < Object\n      return nil if namespaces.empty?\n      namespaces.clear\n      item.class\n    end\n\n    def raise_mapper_not_found(item)\n      namespace = \"#{@options[:namespace]}::\" unless Object.equal?(@options[:namespace])\n      mapper_class = \"#{namespace}#{item.class}Mapper\"\n      raise \"Failed to find a mapper for #{item.inspect}. Did you mean to implement #{mapper_class}?\"\n    end\n\n    def detect_configured_mapper_for(object)\n      @options[:mapper_rules].each do |rule, mapper_class|\n        return mapper_class if rule === object # rubocop:disable Style/CaseEquality\n      end\n      nil\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/errors.rb",
    "content": "module Yaks\n  Error = Class.new(StandardError)\n\n  IllegalStateError         = Class.new(Error)\n  RuntimeError              = Class.new(Error)\n  UnsupportedOperationError = Class.new(Error)\n  PrimitivizeError          = Class.new(Error)\nend\n"
  },
  {
    "path": "yaks/lib/yaks/format/collection_json.rb",
    "content": "module Yaks\n  class Format\n    class CollectionJson < self\n      register :collection_json, :json, 'application/vnd.collection+json'\n\n      include FP\n\n      # @param [Yaks::Resource] resource\n      # @return [Hash]\n      def serialize_resource(resource)\n        result = {\n          version: \"1.0\",\n          items: serialize_items(resource)\n        }\n        result[:href] = resource.self_link.uri if resource.self_link\n        result[:links] = serialize_links(resource) if links?(resource)\n        result[:queries] = serialize_queries(resource) if queries?(resource)\n        result[:template] = serialize_template(resource) if template?(resource)\n        {collection: result}\n      end\n\n      # @param [Yaks::Resource] resource\n      # @return [Array]\n      def serialize_items(resource)\n        resource.seq.map do |item|\n          attrs = item.attributes.map do |name, value|\n            {\n              name: name,\n              value: value\n            }\n          end\n          result = {data: attrs}\n          result[:href] = item.self_link.uri if item.self_link\n          item.links.each do |link|\n            next if link.rel.equal? :self\n            result[:links] = [] unless result.key?(:links)\n            result[:links] << {rel: link.rel, href: link.uri}\n            result[:links].last[:name] = link.title if link.title\n          end\n          result\n        end\n      end\n\n      def serialize_links(resource)\n        resource.links.each_with_object([]) do |link, result|\n          result << {href: link.uri, rel: link.rel}\n        end\n      end\n\n      def serialize_queries(resource)\n        resource.forms.each_with_object([]) do |form, result|\n          next unless form_is_query? form\n\n          result << {rel: form.name, href: form.action}\n          result.last[:prompt] = form.title if form.title\n\n          form.fields_flat.each do |field|\n            result.last[:data] = [] unless result.last.key? :data\n            result.last[:data] << {name: field.name, value: nil.to_s}\n            result.last[:data].last[:prompt] = field.label if field.label\n          end\n        end\n      end\n\n      def queries?(resource)\n        resource.forms.any? { |f| form_is_query? f }\n      end\n\n      def links?(resource)\n        resource.collection? && resource.links.any?\n      end\n\n      def template?(resource)\n        options.key?(:template) && template_form_exists?(resource)\n      end\n\n      protected\n\n      def form_is_query?(form)\n        form.method?(:get) && form.has_action?\n      end\n\n      def template_form_exists?(resource)\n        !resource.find_form(options.fetch(:template)).nil?\n      end\n\n      def serialize_template(resource)\n        fields = resource.find_form(options.fetch(:template)).fields\n        result = {data: []}\n        fields.each do |field|\n          result[:data] << {name: field.name, value: nil.to_s}\n          result[:data].last[:prompt] = field.label if field.label\n        end\n        result\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/format/hal.rb",
    "content": "module Yaks\n  class Format\n    # Hypertext Application Language (http://stateless.co/hal_specification.html)\n    #\n    # A lightweight JSON Hypermedia message format.\n    #\n    # Options: +:plural_links+ In HAL, a single rel can correspond to\n    # a single link, or to a list of links. Which rels are singular\n    # and which are plural is application-dependant. Yaks assumes all\n    # links are singular. If your resource might contain multiple\n    # links for the same rel, then configure that rel to be plural. In\n    # that case it will always be rendered as a collection, even when\n    # the resource only contains a single link.\n    #\n    # @example\n    #\n    #   yaks = Yaks.new do\n    #     format_options :hal, {plural_links: [:related_content]}\n    #   end\n    #\n    class Hal < self\n      register :hal, :json, 'application/hal+json'\n\n      def transitive?\n        true\n      end\n\n      def inverse\n        Yaks::Reader::Hal.new\n      end\n\n      protected\n\n      # @param [Yaks::Resource] resource\n      # @return [Hash]\n      def serialize_resource(resource)\n        # The HAL spec doesn't say explicitly how to deal missing values,\n        # looking at client behavior (Hyperagent) it seems safer to return an empty\n        # resource.\n        #\n        result = resource.attributes\n\n        if resource.links.any?\n          result = result.merge(_links: serialize_links(resource.links))\n        end\n\n        if resource.collection?\n          result = result.merge(_embedded:\n                                  serialize_embedded([resource]))\n        elsif resource.subresources.any?\n          result = result.merge(_embedded:\n                                  serialize_embedded(resource.subresources))\n        end\n\n        result\n      end\n\n      # @param [Array] links\n      # @return [Hash]\n      def serialize_links(links)\n        links.reduce({}, &method(:serialize_link))\n      end\n\n      # @param [Hash] memo\n      # @param [Yaks::Resource::Link]\n      # @return [Hash]\n      def serialize_link(memo, link)\n        hal_link = {href: link.uri}\n        hal_link.merge!(link.options)\n\n        memo[link.rel] = if singular?(link.rel)\n                           hal_link\n                         else\n                           (memo[link.rel] || []) + [hal_link]\n                         end\n        memo\n      end\n\n      # @param [String] rel\n      # @return [Boolean]\n      def singular?(rel)\n        !options.fetch(:plural_links) { [] }.include?(rel)\n      end\n\n      # @param [Array] subresources\n      # @return [Hash]\n      def serialize_embedded(subresources)\n        subresources.each_with_object({}) do |sub, memo|\n          memo[sub.rels.first] = if sub.collection?\n                                   sub.map(&method(:serialize_resource))\n                                 elsif sub.null_resource?\n                                   nil\n                                 else\n                                   serialize_resource(sub)\n                                 end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/format/halo.rb",
    "content": "module Yaks\n  class Format\n    # Extension of Hal loosely based on the example by Mike Kelly given at\n    # https://gist.github.com/mikekelly/893552\n    class Halo < Hal\n      register :halo, :json, 'application/halo+json'\n\n      def serialize_resource(resource)\n        if resource.forms.any?\n          super.merge(_controls: serialize_forms(resource.forms))\n        else\n          super\n        end\n      end\n\n      def serialize_forms(forms)\n        forms.each_with_object({}) do |form, result|\n          result[form.name] = serialize_form(form)\n        end\n      end\n\n      def serialize_form(form)\n        raw = form.to_h_compact\n        raw[:href] = raw.delete(:action) if raw[:action]\n        raw[:fields] = form.fields.map(&method(:serialize_form_field))\n        raw\n      end\n\n      def serialize_form_field(field)\n        if field.type == :fieldset\n          {\n            type: :fieldset,\n            fields: field.fields.map(&method(:serialize_form_field))\n          }\n        else\n          field.to_h_compact.each_with_object({}) do |(attr, value), hsh|\n            if attr == :options # <option>s of a <select>\n              hsh[:options] = value.map(&:to_h_compact) unless value.empty?\n            else\n              hsh[attr] = value\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/format/json_api.rb",
    "content": "module Yaks\n  class Format\n    class JsonAPI < self\n      register :json_api, :json, 'application/vnd.api+json'\n\n      include FP\n\n      # @param [Yaks::Resource] resource\n      # @return [Hash]\n      def call(resource, _env = nil)\n        output = {}\n        if resource.collection?\n          output[:data]  = resource.map(&method(:serialize_resource))\n          output[:links] = serialize_links(resource.links) if resource.links.any?\n        else\n          output[:data] = serialize_resource(resource)\n        end\n        included = resource.seq.each_with_object([]) do |res, array|\n          serialize_included_subresources(res.subresources, array)\n        end\n        output[:included] = included if included.any?\n        output[:meta] = resource[:meta] if resource[:meta]\n\n        output\n      end\n\n      # @param [Yaks::Resource] resource\n      # @return [Hash]\n      def serialize_links(links)\n        links.each_with_object({}) do |link, hash|\n          hash[link.rel] = link.uri\n        end\n      end\n\n      # @param [Yaks::Resource] resource\n      # @return [Hash]\n      def serialize_resource(resource)\n        result = {}\n        result[:type] = pluralize(resource.type)\n        result[:id]   = resource[:id].to_s if resource[:id]\n\n        attributes = resource.attributes.reject { |k| k.equal?(:id) }\n        result[:attributes] = attributes if attributes.any?\n\n        relationships = serialize_relationships(resource.subresources)\n        result[:relationships] = relationships unless relationships.empty?\n        links = serialize_links(resource.links)\n        result[:links] = links unless links.empty?\n\n        result\n      end\n\n      # @param [Array] subresources\n      # @return [Hash]\n      def serialize_relationships(subresources)\n        subresources.each_with_object({}) do |resource, hsh|\n          hsh[resource.rels.first.sub(/^rel:/, '').to_sym] = serialize_relationship(resource)\n        end\n      end\n\n      # @param [Yaks::Resource] resource\n      # @return [Array, Hash]\n      def serialize_relationship(resource)\n        if resource.collection?\n          data = resource.map { |r| {type: pluralize(r.type), id: r[:id].to_s} }\n        elsif !resource.null_resource?\n          data = {type: pluralize(resource.type), id: resource[:id].to_s}\n        end\n        {data: data}\n      end\n\n      # @param [Hash] subresources\n      # @param [Array] array\n      # @return [Array]\n      def serialize_included_subresources(subresources, array)\n        subresources.each do |resources|\n          serialize_included_resources(resources, array)\n        end\n      end\n\n      # @param [Array] resources\n      # @param [Array] included\n      # @return [Array]\n      def serialize_included_resources(subresource, included)\n        subresource.seq.each_with_object(included) do |resource, memo|\n          serialize_subresource(resource, memo)\n        end\n      end\n\n      # {shows => [{id: '3', name: 'foo'}]}\n      #\n      # @param [Yaks::Resource] resource\n      # @param [Hash] included\n      # @return [Hash]\n      def serialize_subresource(resource, included)\n        included << serialize_resource(resource) unless included.any? do |item|\n          item[:id].eql?(resource[:id].to_s) && item[:type].eql?(pluralize(resource.type))\n        end\n        serialize_included_subresources(resource.subresources, included)\n      end\n\n      def inverse\n        Yaks::Reader::JsonAPI.new\n      end\n    end\n\n    class Reader\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/format.rb",
    "content": "module Yaks\n  class Format\n    extend Forwardable\n    include Util,\n            FP::Callable\n\n    # @!attribute [r] options\n    #   @return [Hash]\n    attr_reader :options\n\n    attr_reader :env\n\n    def_delegators :resource, :links, :attributes, :subresources\n\n    protected :links, :attributes, :subresources, :options\n\n    # @param [Hash] options\n    # @return [Hash]\n    def initialize(options = {})\n      @options = options\n    end\n\n    # @param [Yaks::Resource] resource\n    # @return [Hash]\n    def call(resource, env = {})\n      @env = env\n      serialize_resource(resource)\n    end\n    alias_method :serialize, :call\n\n    # @abstract\n    def serialize_resource(_resource)\n    end\n\n    class << self\n      extend Util::Deprecated\n\n      attr_reader :format_name, :serializer, :media_type\n\n      deprecated_alias :mime_type, :media_type\n\n      def all\n        @formats ||= []\n      end\n\n      # @param [Constant] klass\n      # @param [Symbol] format_name\n      # @param [String] media_type\n      # @return [Array]\n      def register(format_name, serializer, media_type)\n        @format_name = format_name\n        @serializer = serializer\n        @media_type = media_type\n\n        Format.all << self\n      end\n\n      # @param [Symbol] format_name\n      # @return [Constant]\n      # @raise [KeyError]\n      def by_name(format_name)\n        find(:format_name, format_name)\n      end\n\n      # @param [Symbol] media_type\n      # @return [Constant]\n      # @raise [KeyError]\n      def by_media_type(media_type)\n        find(:media_type, media_type)\n      end\n      deprecated_alias :by_mime_type, :by_media_type\n\n      def by_accept_header(accept_header)\n        media_type = Rack::Accept::Charset.new(accept_header).best_of(media_types.values)\n        if media_type\n          by_media_type(media_type)\n        else\n          yield if block_given?\n        end\n      end\n\n      def media_types\n        Format.all.each_with_object({}) do |format, memo|\n          memo[format.format_name] = format.media_type\n        end\n      end\n      deprecated_alias :mime_types, :media_types\n\n      def names\n        media_types.keys\n      end\n\n      private\n\n      def find(key, cond)\n        Format.all.detect {|format| format.send(key) == cond }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/fp/callable.rb",
    "content": "module Yaks\n  module FP\n    module Callable\n      def to_proc\n        method(:call).to_proc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/html5_forms.rb",
    "content": "module Yaks\n  # Based on the HTML living standard over at WHATWG\n  # https://html.spec.whatwg.org/multipage/forms.html\n  #\n  # Does not aim to be complete, does aim to be a strict subset.\n  module HTML5Forms\n    INPUT_TYPES = [\n      :checkbox,\n      :color,\n      :date,\n      :datetime,\n      :datetime_local, # :datetime-local in the spec\n      :email,\n      :file,\n      :hidden,\n      :image,\n      :month,\n      :number,\n      :password,\n      :radio,\n      :range,\n      :reset,\n      :search,\n      :tel,\n      :text,\n      :time,\n      :url,\n      :week,\n\n      :select,\n      :textarea,\n      :datalist,\n      :legend\n    ]\n\n    FIELD_OPTIONS = {\n      type: nil,\n      required: false,\n      rows: nil,\n      value: nil,\n      pattern: nil,\n      maxlength: nil,\n      minlength: 0,\n      size: 20,\n      readonly: false,\n      multiple: false,\n      min: nil,\n      max: nil,\n      step: nil,\n      list: nil,\n      placeholder: nil,\n      checked: false,\n      disabled: false\n    }\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/association.rb",
    "content": "module Yaks\n  class Mapper\n    class Association\n      include Attribs.new(\n                name:        Undefined,\n                item_mapper: Undefined,\n                rel:         Undefined,\n                href:        Undefined,\n                link_if:     Undefined,\n                if:          Undefined\n              ),\n              Util,\n              AbstractType\n\n      def self.create(name, options = {})\n        if options.key?(:mapper)\n          options = options.dup\n          mapper  = options.delete(:mapper)\n          options[:item_mapper] = mapper\n        end\n        options[:name] = name\n        new(options)\n      end\n\n      def add_to_resource(resource, parent_mapper, context)\n        return resource unless parent_mapper.expand_value(self.if)\n        AssociationMapper.new(parent_mapper, self, context).call(resource)\n      end\n\n      def render_as_link?(parent_mapper)\n        href != Undefined && link_if != Undefined && Resolve(link_if, parent_mapper)\n      end\n\n      def map_rel(policy)\n        return rel unless rel.equal?(Undefined)\n        policy.derive_rel_from_association(self)\n      end\n\n      # @param object\n      # @param context\n      abstract_method :map_resource\n\n      # support for HasOne and HasMany\n      def resolve_association_mapper(policy)\n        return item_mapper unless item_mapper.equal?(Undefined)\n        policy.derive_mapper_from_association(self)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/association_mapper.rb",
    "content": "module Yaks\n  class Mapper\n    class AssociationMapper\n      attr_reader :parent_mapper, :context, :rel, :association\n\n      def initialize(parent_mapper, association, context)\n        @parent_mapper = parent_mapper\n        @association   = association\n        @context       = context.merge(\n          mapper_stack: context[:mapper_stack] + [parent_mapper]\n        )\n        @rel           = association.map_rel(policy) # rubocop:disable Style/ExtraSpacing\n      end\n\n      def policy\n        context.fetch(:policy)\n      end\n\n      def call(resource)\n        if association.render_as_link?(parent_mapper)\n          add_link(resource)\n        else\n          add_subresource(resource)\n        end\n      end\n\n      private\n\n      def add_link(resource)\n        Link.create(rel, association.href)\n          .add_to_resource(resource, parent_mapper, nil)\n        # Yaks::Mapper::Link doesn't do anything with the context, making it\n        # hard to test that we pass it a context. Passing nil for now, until\n        # this is actually needed and can be tested.\n      end\n\n      def add_subresource(resource)\n        object      = parent_mapper.load_association(association.name)\n        subresource = association.map_resource(object, context).add_rel(rel)\n        resource.add_subresource(subresource)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/attribute.rb",
    "content": "module Yaks\n  class Mapper\n    class Attribute\n      extend Forwardable, Util\n      include Attribs.new(:name, :block, if: true), Util\n\n      def self.create(name, options = {}, &block)\n        new(options.merge(name: name, block: block))\n      end\n\n      def add_to_resource(resource, mapper, _context)\n        return resource unless Resolve(self.if, mapper)\n\n        if block\n          attribute = Resolve(block, mapper)\n        else\n          attribute = mapper.load_attribute(name)\n        end\n\n        resource.merge_attributes(name => attribute)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/config.rb",
    "content": "module Yaks\n  class Mapper\n    class Config\n      include Attribs.new(\n                type: nil, attributes: [], links: [], associations: [], forms: []\n              )\n\n      def add_attributes(*attrs)\n        append_to(:attributes, *attrs.map(&Attribute.method(:create)))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/form/config.rb",
    "content": "module Yaks\n  class Mapper\n    class Form\n      class Config\n        include Attribs.new(\n                  name: nil,\n                  action: nil,\n                  title: nil,\n                  method: nil,\n                  media_type: nil,\n                  fields: [].freeze,\n                  if: nil\n                )\n\n        Builder = Yaks::Builder.new(self) do\n          def_set :action, :title, :method, :media_type\n          def_add :field, create: Field::Builder, append_to: :fields\n          def_add :fieldset, create: Fieldset, append_to: :fields\n          HTML5Forms::INPUT_TYPES.each do |type|\n            def_add(type,\n                    create: Field::Builder,\n                    append_to: :fields,\n                    defaults: {type: type})\n          end\n          def_add :legend, create: Legend, append_to: :fields\n          def_add :dynamic, create: DynamicField, append_to: :fields\n          def_forward :condition\n        end\n\n        # Builder expects a `create' method. Alias to constructor\n        def self.create(options)\n          new(options)\n        end\n\n        # Build up a configuration based on an initial set of\n        # attributes, and a configuration block\n        def self.build(options = {}, &block)\n          Builder.create(options, &block)\n        end\n\n        # Build up a configuration based on a config block. Provide an\n        # object to be supplied to the block\n        def self.build_with_object(object, &block)\n          Builder.build(new, object, &block)\n        end\n\n        def condition(prc = nil, &blk)\n          with(if: prc || blk)\n        end\n\n        def to_resource_fields(mapper)\n          fields.flat_map do |field|\n            field.to_resource_fields(mapper)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/form/dynamic_field.rb",
    "content": "module Yaks\n  class Mapper\n    class Form\n      class DynamicField\n        include Attribs.new(:block)\n\n        def self.create(_opts = nil, &block)\n          new(block: block)\n        end\n\n        def to_resource_fields(mapper)\n          Config.build_with_object(mapper.object, &block).to_resource_fields(mapper)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/form/field/option.rb",
    "content": "module Yaks\n  class Mapper\n    class Form\n      class Field\n        # <option>, as used in a <select>\n        class Option\n          include Attribs.new(:value, :label, selected: false, disabled: false, if: nil)\n\n          def self.create(value, opts)\n            new(opts.merge(value: value))\n          end\n\n          def to_resource_field_option(mapper)\n            return unless self.if.nil? || mapper.expand_value(self.if)\n\n            Resource::Form::Field::Option.new(\n              value: mapper.expand_value(value),\n              label: mapper.expand_value(label),\n              selected: mapper.expand_value(selected),\n              disabled: mapper.expand_value(disabled)\n            )\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/form/field.rb",
    "content": "module Yaks\n  class Mapper\n    class Form\n      class Field\n        include Attribs.new(\n                  :name,\n                  label: nil,\n                  options: [].freeze,\n                  if: nil\n                ).add(HTML5Forms::FIELD_OPTIONS)\n\n        Builder = Yaks::Builder.new(self) do\n          def_set :name, :label\n          def_add :option, create: Option, append_to: :options\n\n          def condition(blk1 = nil, &blk2)\n            @config = @config.with(if: blk1 || blk2)\n          end\n\n          HTML5Forms::FIELD_OPTIONS.each do |option, _|\n            def_set option\n          end\n        end\n\n        def self.create(*args)\n          attrs = args.last.instance_of?(Hash) ? args.pop : {}\n          if name = args.shift\n            attrs = attrs.merge(name: name)\n          end\n          new(attrs)\n        end\n\n        # Convert to a Resource::Form::Field, expanding any dynamic\n        # values\n        def to_resource_fields(mapper)\n          return [] unless self.if.nil? || mapper.expand_value(self.if)\n          [ Resource::Form::Field.new(\n              resource_attributes.each_with_object({}) do |attr, attrs|\n                attrs[attr] = mapper.expand_value(public_send(attr))\n              end.merge(options: resource_options(mapper))) ]\n        end\n\n        def resource_options(mapper)\n          # make sure all empty options arrays are the same instance,\n          # makes for prettier #pp\n          if options.empty?\n            options\n          else\n            options.map {|opt| opt.to_resource_field_option(mapper) }.compact\n          end\n        end\n\n        # All attributes that can be converted 1-to-1 to\n        # Resource::Form::Field\n        def resource_attributes\n          self.class.attributes.names - [:options, :if]\n        end\n      end # Field\n    end # Form\n  end # Mapper\nend # Yaks\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/form/fieldset.rb",
    "content": "module Yaks\n  class Mapper\n    class Form\n      class Fieldset\n        extend Forwardable\n        include Attribs.new(:config)\n\n        def_delegators :config, :fields\n\n        def self.create(options = {}, &block)\n          new(config: Config.build(options, &block))\n        end\n\n        def to_resource_fields(mapper)\n          return [] if config.if && !mapper.expand_value(config.if)\n          [ Resource::Form::Fieldset.new(fields: config.to_resource_fields(mapper)) ]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/form/legend.rb",
    "content": "module Yaks\n  class Mapper\n    class Form\n      class Legend\n        include Attribs.new(:type, :label, if: nil)\n\n        def self.create(label, opts = {})\n          new(opts.merge(type: :legend, label: label))\n        end\n\n        def to_resource_fields(mapper)\n          return [] unless self.if.nil? || mapper.expand_value(self.if)\n          [ Resource::Form::Legend.new(label: mapper.expand_value(label)) ]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/form.rb",
    "content": "module Yaks\n  class Mapper\n    class Form\n      extend Forwardable, Util\n\n      def_delegators :config, :name, :action, :title, :method,\n                              :media_type, :fields, :dynamic_blocks\n\n      def self.create(*args, &block)\n        args, options = extract_options(args)\n        options[:name] = args.first if args.first\n        new(config: Config.build(options, &block))\n      end\n\n      ############################################################\n      # instance\n\n      include Attribs.new(:config)\n\n      def add_to_resource(resource, mapper, _context)\n        return resource if config.if && !mapper.expand_value(config.if)\n        resource.add_form(to_resource_form(mapper))\n      end\n\n      def to_resource_form(mapper)\n        attrs = {\n          fields: config.to_resource_fields(mapper),\n          action: mapper.expand_uri(action)\n        }\n\n        [:name, :title, :method, :media_type].each do |attr|\n          attrs[attr] = mapper.expand_value(public_send(attr))\n        end\n\n        Resource::Form.new(attrs)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/has_many.rb",
    "content": "module Yaks\n  class Mapper\n    class HasMany < Association\n      include Util,\n              attributes.add(collection_mapper: Undefined)\n\n      def map_resource(collection, context)\n        return NullResource.new(collection: true) if collection.nil?\n        policy      = context.fetch(:policy)\n        item_mapper = resolve_association_mapper(policy)\n        context     = context.merge(item_mapper: item_mapper)\n        collection_mapper(collection, policy).new(context).call(collection)\n      end\n\n      undef collection_mapper\n      def collection_mapper(collection = nil, policy = nil)\n        return @collection_mapper unless @collection_mapper.equal? Undefined\n        policy.derive_mapper_from_object(collection) if policy && collection\n      end\n\n      def singular_name\n        singularize(name.to_s)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/has_one.rb",
    "content": "module Yaks\n  class Mapper\n    class HasOne < Association\n      def map_resource(object, context)\n        resolve_association_mapper(context.fetch(:policy))\n          .new(context)\n          .call(object)\n      end\n\n      def singular_name\n        name.to_s\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper/link.rb",
    "content": "module Yaks\n  class Mapper\n    # A Yaks::Mapper::Link is part of a mapper's configuration. It captures\n    # what is set through the mapper's class level `#link` function, and is\n    # capable of generating a `Yaks::Resource::Link` for a given mapper\n    # instance (and hence subject).\n    #\n    # @example\n    #   link :self, 'http://api.foo.org/users/{id}', title: ->{ \"User #{object.name}\" }\n    #   link :profile, 'http://apidocs.foo.org/profiles/users'\n    #   link 'http://apidocs.foo.org/rels/friends', 'http://api.foo.org/users/{id}/friends?page={page}', expand: [:id]\n    #\n    # It takes a relationship identifier, a URI template and an options hash.\n    #\n    # @param rel [Symbol|String] Either a registered relationship type (Symbol)\n    #   or a relationship URI. See [RFC5988 Web Linking](http://tools.ietf.org/html/rfc5988)\n    # @param template [String] A [RFC6570](http://tools.ietf.org/html/rfc6570) URI template\n    # @param template [Symbol] A method name that generates the link. No more expansion is done afterwards\n    # @option expand [Boolean] pass false to pass on the URI template in the response,\n    #   instead of expanding the variables\n    # @option expand [Array[Symbol]] pass a list of variable names to only expand those,\n    #   and return a partially expanded URI template in the response\n    # @option title [String] Give the link a title\n    # @option title [#to_proc] Block that returns the title. If it takes an argument,\n    #   it will receive the mapper instance as argument. Otherwise it is evaluated in the mapper context\n    class Link\n      extend Forwardable, Util\n      include Attribs.new(:rel, :template, options: {}.freeze), Util\n\n      def self.create(*args)\n        args, options = extract_options(args)\n        new(rel: args.first, template: args.last, options: options)\n      end\n\n      def add_to_resource(resource, mapper, _context)\n        if options[:remove]\n          return resource.with(links: resource.links.reject {|link| link.rel?(rel)})\n        end\n\n        resource_link = map_to_resource_link(mapper)\n        return resource unless resource_link\n\n        if options[:replace]\n          resource.with(links: resource.links.reject {|link| link.rel?(rel)} << resource_link)\n        else\n          resource.add_link(resource_link)\n        end\n      end\n\n      def rel?(rel)\n        rel().eql? rel\n      end\n\n      # A link is templated if it does not expand, or only partially\n      def templated?\n        !options.fetch(:expand) { true }.equal? true\n      end\n\n      def map_to_resource_link(mapper)\n        return unless mapper.expand_value(options.fetch(:if, true))\n\n        uri = mapper.expand_uri(template, options.fetch(:expand, true))\n        return if uri.nil?\n\n        attrs = {\n          rel: rel,\n          uri: uri\n        }\n\n        resource_link_options(mapper).tap do |opts|\n          attrs[:options] = opts unless opts.empty?\n        end\n\n        Resource::Link.new(attrs)\n      end\n\n      private\n\n      def resource_link_options(mapper)\n        options = options()\n        options = options.merge(title: Resolve(options[:title], mapper)) if options.key?(:title)\n        options = options.merge(templated: true) if templated?\n        options.reject{|key| [:expand, :replace, :if].include? key }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/mapper.rb",
    "content": "module Yaks\n  class Mapper\n    extend Configurable\n\n    def_set :type\n\n    def_forward attributes: :add_attributes\n    def_forward :append_to\n\n    def_add :link,      create: Link,      append_to: :links\n    def_add :has_one,   create: HasOne,    append_to: :associations\n    def_add :has_many,  create: HasMany,   append_to: :associations\n    def_add :attribute, create: Attribute, append_to: :attributes\n    def_add :form,      create: Form,      append_to: :forms\n\n    extend Forwardable\n    include Util, FP, FP::Callable\n\n    attr_reader :object, :context\n\n    def_delegators 'self.class', :config\n    def_delegators :config, :attributes, :links, :associations, :forms\n\n    def initialize(context)\n      @context = context\n    end\n\n    def policy\n      context.fetch(:policy)\n    end\n\n    def env\n      context.fetch(:env)\n    end\n\n    def mapper_stack\n      context.fetch(:mapper_stack)\n    end\n\n    def self.mapper_name(policy)\n      config.type || policy.derive_type_from_mapper_class(self)\n    end\n\n    def mapper_name\n      self.class.mapper_name(policy)\n    end\n\n    def call(object, _env = nil)\n      @object = object\n\n      return NullResource.new if object.nil?\n\n      [ :map_attributes,\n        :map_links,\n        :map_subresources,\n        :map_forms\n      ].inject(Resource.new(type: mapper_name)) do |resource, method|\n        __send__(method, resource)\n      end\n    end\n\n    def load_attribute(name)\n      respond_to?(name) ? public_send(name) : object.public_send(name)\n    end\n    alias_method :load_association, :load_attribute\n\n    def expand_value(value)\n      Resolve(value, self)\n    end\n\n    def expand_uri(uri, expand = true)\n      return if uri.nil?\n      return Resolve(uri, self) if uri.respond_to?(:to_proc)\n\n      template = URITemplate.new(uri)\n      expand_vars = case expand\n                    when true\n                      template.variables\n                    when false\n                      []\n                    else\n                      expand\n                    end\n\n      mapping = expand_vars.each_with_object({}) do |name, hsh|\n        hsh[name] = load_attribute(name)\n      end\n\n      template.expand_partial(mapping).to_s\n    end\n\n    private\n\n    def map_attributes(resource)\n      attributes.inject(resource) do |res, attribute|\n        attribute.add_to_resource(res, self, context)\n      end\n    end\n\n    def map_links(resource)\n      links.inject(resource) do |res, mapper_link|\n        mapper_link.add_to_resource(res, self, context)\n      end\n    end\n\n    def map_subresources(resource)\n      associations.inject(resource) do |res, association|\n        association.add_to_resource(res, self, context)\n      end\n    end\n\n    def map_forms(resource)\n      forms.inject(resource) do |res, form|\n        form.add_to_resource(res, self, context)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/null_resource.rb",
    "content": "module Yaks\n  class NullResource < Resource\n    include attributes.add(collection: false),\n            Equalizer.new(:rels, :collection)\n\n    def initialize(opts = {})\n      local_opts = {}\n      local_opts[:rels]       = opts[:rels]       if opts.key?(:rels)\n      local_opts[:collection] = opts[:collection] if opts.key?(:collection)\n      super(local_opts)\n    end\n\n    def each\n      to_enum\n    end\n\n    def collection? # rubocop:disable Style/TrivialAccessors\n      @collection\n    end\n\n    def null_resource?\n      true\n    end\n\n    def seq\n      []\n    end\n\n    def map\n      return [] if collection?\n      raise UnsupportedOperationError, \"Operation #{__method__} not supported on #{self.class}\"\n    end\n\n    def merge_attributes(_new_attrs)\n      raise UnsupportedOperationError, \"Operation #{__method__} not supported on #{self.class}\"\n    end\n\n    def add_link(_link)\n      raise UnsupportedOperationError, \"Operation #{__method__} not supported on #{self.class}\"\n    end\n\n    def add_form(_form)\n      raise UnsupportedOperationError, \"Operation #{__method__} not supported on #{self.class}\"\n    end\n\n    def add_subresource(_subresource)\n      raise UnsupportedOperationError, \"Operation #{__method__} not supported on #{self.class}\"\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/pipeline.rb",
    "content": "module Yaks\n  class Pipeline\n    include Concord.new(:steps)\n\n    def call(input, env)\n      steps.inject(input) {|memo, (_, step)| step.call(memo, env) }\n    end\n\n    def insert_hooks(hooks)\n      new_steps = hooks.inject(steps) do |steps, (type, target_step, name, hook)|\n        steps.flat_map do |step_name, callable|\n          if step_name.equal? target_step\n            case type\n            when :before\n              [[name, hook], [step_name, callable]]\n            when :after\n              [[step_name, callable], [name, hook]]\n            when :around\n              [[name, ->(x, env) { hook.call(x, env, &callable) }]]\n            when :skip\n              []\n            end\n          end || [[step_name, callable]]\n        end\n      end\n\n      self.class.new(new_steps)\n    end\n\n    def transitive?\n      steps.all? {|_name, step| step.respond_to?(:transitive?) && step.transitive?}\n    end\n\n    def inverse\n      unless transitive?\n        raise \"Unable to get inverse pipeline, not all pipeline steps are transitive.\"\n      end\n\n      self.class.new(steps.map {|name, step| [name, step.inverse]}.reverse)\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/primitivize.rb",
    "content": "module Yaks\n  class Primitivize\n    attr_reader :mappings\n\n    def initialize\n      @mappings = {}\n    end\n\n    def call(object)\n      mappings.each do |pattern, block|\n        # rubocop:disable Style/CaseEquality\n        return instance_exec(object, &block) if pattern === object\n      end\n      raise PrimitivizeError, \"don't know how to turn #{object.class} (#{object.inspect}) into a primitive\"\n    end\n\n    def map(*types, &block)\n      types.each do |type|\n        @mappings = mappings.merge(type => block)\n      end\n    end\n\n    def self.create\n      new.tap do |p|\n        p.map String, Numeric, true, false, nil do |object|\n          object\n        end\n\n        p.map Symbol, URI do |object|\n          object.to_s\n        end\n\n        p.map Hash do |object|\n          object.to_enum.with_object({}) do |(key, value), output|\n            output[call(key)] = call(value)\n          end\n        end\n\n        p.map Enumerable do |object|\n          object.map(&method(:call))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/reader/hal.rb",
    "content": "module Yaks\n  module Reader\n    class Hal\n      include Util\n\n      def call(parsed_json, _env = {})\n        attributes = parsed_json.dup\n        links      = convert_links(attributes.delete('_links') || {})\n        embedded   = convert_embedded(attributes.delete('_embedded') || {})\n\n        Resource.new(\n          type: attributes.delete('type') || type_from_links(links),\n          attributes: Util.symbolize_keys(attributes),\n          links: links,\n          subresources: embedded\n        )\n      end\n\n      def type_from_links(links)\n        profile = links.detect {|l| l.rel?(:profile)}\n        profile.uri[/\\w+$/] if profile\n      end\n\n      def convert_links(links)\n        links.flat_map do |rel, link|\n          array(link).map do |l|\n            options = symbolize_keys(slice_hash(l, 'title', 'templated'))\n            # if it looks like a keyword we'll assume it's a registered rel type\n            rel = rel.to_sym if rel =~ /\\A\\w+\\z/\n            Resource::Link.new(rel: rel, uri: l['href'], options: options)\n          end\n        end\n      end\n\n      def array(x)\n        x.instance_of?(Array) ? x : [x]\n      end\n\n      def convert_embedded(embedded)\n        embedded.flat_map do |rel, resource|\n          case resource\n          when nil\n            NullResource.new\n          when Array\n            if resource.empty?\n              NullResource.new(collection: true)\n            else\n              CollectionResource.new(\n                members: resource.map { |r|\n                  call(r).with(type: Util.singularize(rel[/\\w+$/]))\n                }\n              )\n            end\n          else\n            call(resource)\n          end.with(rels: [rel])\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/reader/json_api.rb",
    "content": "module Yaks\n  module Reader\n    class JsonAPI\n      def call(parsed_json, _env = {})\n        included = parsed_json['included'].nil? ? {} : parsed_json['included'].dup\n\n        if parsed_json['data'].is_a?(Array)\n          CollectionResource.new(\n            attributes: parsed_json['meta'].nil? ? nil : {meta: parsed_json['meta']},\n            members: parsed_json['data'].map { |data| call('data' => data, 'included' => included) }\n          )\n        else\n          attributes = parsed_json['data'].dup\n          links = attributes.delete('links') || {}\n          relationships = attributes.delete('relationships') || {}\n          type = attributes.delete('type')\n          attributes.merge!(attributes.delete('attributes') || {})\n\n          embedded   = convert_embedded(Hash[relationships], included)\n          links      = convert_links(Hash[links])\n\n          Resource.new(\n            type: Util.singularize(type),\n            attributes: Util.symbolize_keys(attributes),\n            subresources: embedded,\n            links: links\n          )\n        end\n      end\n\n      def convert_embedded(relationships, included)\n        relationships.flat_map do |rel, relationship|\n          # A Link doesn't have to contain a `data` member.\n          # It can contain URLs instead, or as well, but we are only worried about *embedded* links here.\n          data = relationship['data']\n          # Resource data MUST be represented as one of the following:\n          #\n          # * `null` for empty to-one relationships.\n          # * a \"resource identifier object\" for non-empty to-one relationships.\n          # * an empty array ([]) for empty to-many relationships.\n          # * an array of resource identifier objects for non-empty to-many relationships.\n          if data.nil?\n            NullResource.new(rels: [rel])\n          elsif data.is_a? Array\n            if data.empty?\n              NullResource.new(collection: true, rels: [rel])\n            else\n              CollectionResource.new(\n                members: data.map { |link|\n                  data = included.find{ |item| (item['id'] == link['id']) && (item['type'] == link['type']) }\n                  call('data' => data, 'included' => included)\n                },\n                rels: [rel]\n              )\n            end\n          else\n            data = included.find{ |item| (item['id'] == data['id']) && (item['type'] == data['type']) }\n            call('data' => data, 'included' => included).with(rels: [rel])\n          end\n        end.compact\n      end\n\n      def convert_links(links)\n        links.map do |rel, link|\n          Resource::Link.new(rel: rel.to_sym, uri: link)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/resource/form/field/option.rb",
    "content": "module Yaks\n  class Resource\n    class Form\n      class Field\n        class Option\n          include Attribs.new(:value, :label, selected: false, disabled: false)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/resource/form/field.rb",
    "content": "module Yaks\n  class Resource\n    class Form\n      class Field\n        include Yaks::Mapper::Form::Field.attributes.add(error: nil)\n\n        undef value\n        def value\n          if type.equal? :select\n            selected = options.find(&:selected)\n            selected.value if selected\n          else\n            @value\n          end\n        end\n\n        def with_value(value)\n          if type.equal? :select\n            with(options: select_options_for_value(value))\n          else\n            with(value: value)\n          end\n        end\n\n        private\n\n        def select_options_for_value(value)\n          unset = ->(option) { option.selected && !value().eql?(value) }\n          set   = ->(option) { !option.selected && option.value.eql?(value) }\n\n          options.each_with_object([]) do |option, new_opts|\n            new_opts << case option\n                        when unset\n                          option.with selected: false\n                        when set\n                          option.with selected: true\n                        else\n                          option\n                        end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/resource/form/fieldset.rb",
    "content": "module Yaks\n  class Resource\n    class Form\n      class Fieldset\n        include Attribs.new(:fields)\n        include Yaks::Resource::HasFields\n\n        def type\n          :fieldset\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/resource/form/legend.rb",
    "content": "module Yaks\n  class Resource\n    class Form\n      class Legend\n        include Attribs.new(:label, :type)\n\n        def initialize(opts)\n          super(opts.merge(type: :legend))\n        end\n\n        # Up to 0.9.0 legends were represented as Form::Field\n        # instances with the label stored as name, hence this alias\n        # for compatibility\n        alias_method :name, :label\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/resource/form.rb",
    "content": "module Yaks\n  class Resource\n    class Form\n      include Yaks::Mapper::Form::Config.attributes.remove(:dynamic_blocks)\n      include Yaks::Resource::HasFields\n\n      def [](name)\n        fields.find {|field| field.name.equal? name}.value\n      end\n\n      def values\n        fields_flat.each_with_object({}) do |field, values|\n          values[field.name] = field.value\n        end\n      end\n\n      def method?(meth)\n        !method.nil? && method.downcase.to_sym == meth.downcase.to_sym\n      end\n\n      def has_action? # rubocop:disable Style/PredicateName\n        !action.nil?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/resource/has_fields.rb",
    "content": "module Yaks\n  class Resource\n    module HasFields\n      def map_fields(&block)\n        with(\n          fields: fields_flat(&block)\n        )\n      end\n\n      def fields_flat(&block)\n        return to_enum(__method__) unless block_given?\n        fields.map do |field|\n          next field if field.type.equal? :legend\n          if field.respond_to?(:map_fields)\n            field.map_fields(&block)\n          else\n            block.call(field)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/resource/link.rb",
    "content": "module Yaks\n  class Resource\n    class Link\n      include Attribs.new(:rel, :uri, options: {}.freeze)\n\n      def title\n        options[:title]\n      end\n\n      def templated?\n        options.fetch(:templated) { false }\n      end\n\n      def rel?(rel)\n        rel().eql? rel\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/resource.rb",
    "content": "module Yaks\n  class Resource\n    include Attribs.new(\n              type: nil,\n              rels: [],\n              links: [],\n              attributes: {},\n              subresources: [],\n              forms: []\n            )\n    extend Util::Deprecated\n\n    def initialize(attrs = {})\n      raise attrs.inspect if attrs.key?(:subresources) && !attrs[:subresources].instance_of?(Array)\n      super\n    end\n\n    def [](attr)\n      attributes[attr]\n    end\n\n    def find_form(name)\n      forms.find { |form| form.name.equal? name }\n    end\n\n    def seq\n      [self]\n    end\n\n    def self_link\n      # This reverse is there so that the last :self link specified\n      # \"wins\". The use case is having a self link defined in a base\n      # mapper class, but having it overridden in specific\n      # subclasses. In combination with formats that expect resources\n      # to have up to one self link, this is the preferred behavior.\n      # However since 0.7.5 links take a \"replace: true\" option to\n      # specifiy they should replace previous defintions with the same\n      # rel, wich should be used instead. The behavior that the last\n      # link \"wins\" will be deprecated, the result of multiple links\n      # with the same rel will be unspecified.\n      links.reverse.find do |link|\n        link.rel.equal? :self\n      end\n    end\n\n    def collection?\n      false\n    end\n    alias_method :collection, :collection?\n\n    def with_collection(*)\n      self\n    end\n\n    def null_resource?\n      false\n    end\n\n    def members\n      raise UnsupportedOperationError, \"Only Yaks::CollectionResource has members\"\n    end\n    alias_method :each, :members\n    alias_method :map, :members\n    alias_method :each_with_object, :members\n    alias_method :with_members, :members\n\n    def merge_attributes(new_attrs)\n      with(attributes: @attributes.merge(new_attrs))\n    end\n    deprecated_alias :update_attributes, :merge_attributes\n\n    def add_rel(rel)\n      append_to(:rels, rel)\n    end\n\n    def add_link(link)\n      append_to(:links, link)\n    end\n\n    def add_form(form)\n      append_to(:forms, form)\n    end\n\n    def add_subresource(subresource)\n      append_to(:subresources, subresource)\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/runner.rb",
    "content": "module Yaks\n  class Runner\n    include Util\n    include Anima.new(:object, :config, :options)\n    include Adamantium::Flat\n    extend Forwardable\n\n    def_delegators :config,        :policy, :default_format, :format_options_hash,\n                                   :primitivize, :serializers\n    def_delegators :format_class,  :media_type, :format_name\n\n    def call\n      Pipeline.new(steps).insert_hooks(hooks).call(object, env)\n    end\n\n    def read\n      Pipeline.new([[:parse, serializer.inverse], [:read, formatter.inverse]]).insert_hooks(hooks).call(object, env)\n    end\n\n    def format\n      Pipeline.new([[:format, formatter], [:primitivize, primitivizer]]).insert_hooks(hooks).call(object, env)\n    end\n\n    def map\n      Pipeline.new([[:map, mapper]]).insert_hooks(hooks).call(object, env)\n    end\n\n    def context\n      {\n        policy: policy,\n        env: env,\n        mapper_stack: []\n      }.merge(slice_hash(options, :item_mapper))\n    end\n    memoize :context, freezer: :flat\n\n    def env\n      options.fetch(:env, {})\n    end\n    memoize :env, freezer: :noop\n\n    # @return [Class]\n    def format_class\n      Format.by_accept_header(env['HTTP_ACCEPT']) {\n        Format.by_name(options.fetch(:format) { default_format })\n      }\n    end\n    memoize :format_class, freezer: :noop\n\n    def steps\n      [[ :map, mapper ],\n       [ :format, formatter ],\n       [ :primitivize, primitivizer],\n       [ :serialize, serializer ]]\n    end\n    memoize :steps\n\n    def mapper\n      options.fetch(:mapper) do\n        policy.derive_mapper_from_object(object)\n      end.new(context)\n    end\n    memoize :mapper, freezer: :noop\n\n    def formatter\n      format_class.new(format_options_hash[format_name])\n    end\n    memoize :formatter, freezer: :noop\n\n    def primitivizer\n      proc do |input|\n        if format_class.serializer.equal? :json\n          primitivize.call(input)\n        else\n          input\n        end\n      end\n    end\n    memoize :primitivizer\n\n    def serializer\n      serializers.fetch(format_class.serializer)\n    end\n    memoize :serializer, freezer: :noop\n\n    def hooks\n      config.hooks + options.fetch(:hooks, [])\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/serializer.rb",
    "content": "module Yaks\n  module Serializer\n    def self.register(format, serializer)\n      raise \"Serializer for #{format} already registered\" if all.key? format\n      all[format] = serializer\n    end\n\n    def self.all\n      @serializers ||= {json: JSONWriter}\n    end\n\n    module JSONWriter\n      extend Yaks::FP::Callable\n\n      def self.call(data, _env)\n        JSON.pretty_generate(data)\n      end\n\n      def self.transitive?\n        true\n      end\n\n      def self.inverse\n        JSONReader\n      end\n    end\n\n    module JSONReader\n      extend Yaks::FP::Callable\n\n      def self.call(data, _env)\n        JSON.parse(data)\n      end\n\n      def self.transitive?\n        true\n      end\n\n      def self.inverse\n        JSONWriter\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/util.rb",
    "content": "module Yaks\n  module Util\n    extend self\n    extend Forwardable\n\n    def_delegators Inflection, :singular, :singularize, :pluralize\n\n    def underscore(str)\n      str.gsub(/::/, '/')\n        .gsub(%r{(?<!^|/)([A-Z])(?=[a-z$])|(?<=[a-z])([A-Z])}, '_\\1\\2')\n        .tr(\"-\", \"_\")\n        .downcase\n    end\n\n    def camelize(str)\n      str.gsub(%r{/(.?)})    { \"::#{ $1.upcase }\" }\n        .gsub!(/(?:^|_)(.)/) { $1.upcase          }\n    end\n\n    def slice_hash(hash, *keys)\n      keys.each_with_object({}) {|k, dest| dest[k] = hash[k] if hash.key?(k) }\n    end\n\n    def reject_keys(hash, *keys)\n      hash.keys.each_with_object({}) {|k, dest| dest[k] = hash[k] unless keys.include?(k) }\n    end\n\n    def symbolize_keys(hash)\n      hash.each_with_object({}) {|(k, v), hsh| hsh[k.to_sym] = v}\n    end\n\n    def extract_options(args)\n      args.last.instance_of?(Hash) ? [args[0..-2], args.last] : [args, {}]\n    end\n\n    # Turn what is maybe a Proc into its result (or itself)\n    #\n    # When input can be either a value or a proc that returns a value,\n    # this conversion function can be used to resolve the thing to a\n    # value.\n    #\n    # The proc can be evaluated (instance_evaled) in a certain context,\n    # or evaluated as a closure.\n    #\n    # @param [Object|Proc] maybe_proc\n    #   A proc or a plain value\n    # @param [Object] context\n    #   (optional) A context used to instance_eval the proc\n    def Resolve(maybe_proc, context = nil) # rubocop:disable Style/MethodName\n      if maybe_proc.respond_to?(:to_proc) && !maybe_proc.instance_of?(Symbol)\n        if context\n          if maybe_proc.arity > 0\n            context.instance_eval(&maybe_proc)\n          else\n            # In case it's a lambda with zero arity instance_eval fails\n            context.instance_exec(&maybe_proc)\n          end\n        else\n          maybe_proc.to_proc.call()\n        end\n      else\n        maybe_proc\n      end\n    end\n\n    module Deprecated\n      def deprecated_alias(name, actual)\n        define_method name do |*args, &block|\n          $stderr.puts \"WARNING: #{self.class}##{name} is deprecated, use `#{actual}'. at #{caller.first}\"\n          public_send(actual, *args, &block)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/lib/yaks/version.rb",
    "content": "module Yaks\n  VERSION = '0.13.0'\nend\n"
  },
  {
    "path": "yaks/lib/yaks.rb",
    "content": "require 'forwardable'\nrequire 'set'\nrequire 'pathname'\nrequire 'json'\nrequire 'csv'\n\nrequire 'concord'\nrequire 'attribs'\nrequire 'inflection'\nrequire 'uri_template'\nrequire 'rack/accept'\n\nrequire 'yaks/version'\nrequire 'yaks/util'\nrequire 'yaks/configurable'\nrequire 'yaks/fp/callable'\nrequire 'yaks/primitivize'\nrequire 'yaks/builder'\nrequire 'yaks/errors'\n\nrequire 'yaks/default_policy'\nrequire 'yaks/serializer'\nrequire 'yaks/config'\n\nmodule Yaks\n  Undefined = Module.new.freeze\n\n  # Set the Root constant as the gems root path\n  Root = Pathname(__FILE__).join('../..')\n\n  DSL_METHODS = [\n    :format_options,\n    :rel_template,\n    :mapper_for,\n    :before,\n    :after,\n    :around,\n    :skip,\n    :namespace,\n    :mapper_namespace,\n    :serializer,\n    :json_serializer,\n    :map_to_primitive\n  ]\n\n  ConfigBuilder = Builder.new(Yaks::Config) do\n    def_set(*Yaks::Config.attributes.names)\n    def_forward(*DSL_METHODS)\n    def_forward(*Yaks::DefaultPolicy.public_instance_methods(false))\n  end\n\n  class << self\n    # @param [Proc] blk\n    # @return [Yaks::Config]\n\n    def new(&blk)\n      ConfigBuilder.create(&blk)\n    end\n  end\nend\n\nrequire 'yaks/resource'\nrequire 'yaks/null_resource'\nrequire 'yaks/resource/link'\nrequire 'yaks/collection_resource'\n\nrequire 'yaks/html5_forms'\n\nrequire 'yaks/mapper/association'\nrequire 'yaks/mapper/has_one'\nrequire 'yaks/mapper/has_many'\nrequire 'yaks/mapper/attribute'\nrequire 'yaks/mapper/link'\nrequire 'yaks/mapper/form/field/option'\nrequire 'yaks/mapper/form/field'\nrequire 'yaks/mapper/form/fieldset'\nrequire 'yaks/mapper/form/legend'\nrequire 'yaks/mapper/form/dynamic_field'\nrequire 'yaks/mapper/form/config'\nrequire 'yaks/mapper/form'\nrequire 'yaks/mapper/config'\nrequire 'yaks/mapper'\nrequire 'yaks/mapper/association_mapper'\nrequire 'yaks/collection_mapper'\n\nrequire 'yaks/resource/has_fields'\nrequire 'yaks/resource/form'\nrequire 'yaks/resource/form/field'\nrequire 'yaks/resource/form/field/option'\nrequire 'yaks/resource/form/fieldset'\nrequire 'yaks/resource/form/legend'\n\nrequire 'yaks/format'\nrequire 'yaks/format/hal'\nrequire 'yaks/format/halo'\nrequire 'yaks/format/json_api'\nrequire 'yaks/format/collection_json'\n\nrequire 'yaks/reader/hal'\nrequire 'yaks/reader/json_api'\nrequire 'yaks/pipeline'\nrequire 'yaks/runner'\n"
  },
  {
    "path": "yaks/spec/acceptance/acceptance_spec.rb",
    "content": "require 'acceptance/models'\nrequire 'acceptance/json_shared_examples'\n\nRSpec.describe Yaks::Format::Hal do\n  let(:format_name) { :hal }\n\n  context 'with a configured rel template' do\n    let(:yaks_config) {\n      Yaks.new do\n        format_options :hal, plural_links: ['http://literature.example.com/rels/quotes']\n        rel_template \"http://literature.example.com/rel/{rel}\"\n      end\n    }\n\n    include_examples 'JSON Writer',     'confucius'\n    include_examples 'JSON round trip', 'confucius'\n  end\n\n  context 'with a rel computed by a policy override' do\n    let(:yaks_config) {\n      Yaks.new do\n        format_options :hal, plural_links: ['http://literature.example.com/rels/quotes']\n        derive_rel_from_association do |association|\n          \"http://literature.example.com/rel/#{association.name}\"\n        end\n      end\n    }\n\n    include_examples 'JSON Writer',     'confucius'\n    include_examples 'JSON round trip', 'confucius'\n    include_examples 'JSON Writer',     'list_of_quotes'\n    include_examples 'JSON round trip', 'list_of_quotes'\n  end\nend\n\nRSpec.describe Yaks::Format::Halo do\n  let(:format_name) { :halo }\n  let(:yaks_config) {\n    Yaks.new do\n      default_format :halo\n      rel_template \"http://literature.example.com/rel/{rel}\"\n    end\n  }\n\n  include_examples 'JSON Writer', 'confucius'\nend\n\nRSpec.describe Yaks::Format::JsonAPI do\n  let(:format_name) { :json_api }\n  let(:yaks_config) { Yaks.new }\n\n  include_examples 'JSON Writer', 'confucius'\n  # include_examples 'JSON Reader', 'confucius'\n  include_examples 'JSON round trip', 'confucius'\n  include_examples 'JSON Writer', 'list_of_quotes'\n  # include_examples 'JSON round trip', 'list_of_quotes'\nend\n\nRSpec.describe Yaks::Format::CollectionJson do\n  let(:format_name) { :collection_json }\n  let(:yaks_config) { Yaks.new }\n\n  include_examples 'JSON Writer', 'confucius'\n  include_examples 'JSON Writer', 'list_of_quotes'\n\n  context 'with a namespace' do\n    let(:yaks_config) {\n      Yaks.new do\n        mapper_namespace Youtypeitwepostit\n      end\n    }\n\n    include_examples 'JSON Writer', 'youtypeitwepostit'\n  end\nend\n"
  },
  {
    "path": "yaks/spec/acceptance/json_shared_examples.rb",
    "content": "RSpec.shared_examples_for 'JSON Writer' do |fixture_name|\n  describe 'Yaks::Resource => JSON' do\n    let(:object) { load_yaml_fixture(fixture_name) }\n    let(:json_fixture) { load_json_fixture(\"#{fixture_name}.#{format_name}\") }\n    let(:serialized) {\n      yaks_config.call(object, hooks: [[:skip, :serialize]], format: format_name)\n    }\n\n    # before do\n    #   puts \"============================expected=========================================\"\n    #   puts JSON.pretty_generate(expected)\n    #   puts \"=================yaks.call(object, format: #{format.inspect})================\"\n    #   puts JSON.pretty_generate(serialized)\n    # end\n\n    it 'should match the JSON fixture' do\n      expect(serialized).to deep_eql json_fixture\n    end\n  end\nend\n\nRSpec.shared_examples_for 'JSON Reader' do |fixture_name|\n  describe 'JSON => Yaks::Resource' do\n    let(:object) { load_yaml_fixture(fixture_name) }\n    let(:json_fixture) { load_json_fixture(\"#{fixture_name}.#{format_name}\") }\n    let(:resource) {\n      yaks_config.read(json_fixture, hooks: [[:skip, :parse]], format: format_name)\n    }\n\n    it 'should equal the corresponding Yaks::Resource' do\n      # Comparing type+to_h to get better RSpec output upon failure\n      expect(resource).to be_a Yaks::Resource\n      expect(resource.to_h).to eql yaks.map(object).to_h\n    end\n  end\nend\n\nRSpec.shared_examples_for 'JSON round trip' do |fixture_name|\n  describe 'JSON => Yaks::Resource => JSON' do\n    let(:json_fixture) { load_json_fixture(\"#{fixture_name}.#{format_name}\") }\n    let(:read_and_written) {\n      config = yaks_config.with(default_format: format_name)\n      config.format(\n        config.read(json_fixture, hooks: [[:skip, :parse]])\n      )\n    }\n\n    specify 'it should be identical' do\n      expect(read_and_written).to deep_eql json_fixture\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/acceptance/models.rb",
    "content": "require 'anima'\n\nclass Scholar\n  include Anima.new(:id, :name, :pinyin, :latinized, :works)\nend\n\nclass Work\n  include Anima.new(:id, :chinese_name, :english_name, :quotes, :era)\nend\n\nclass Quote\n  include Anima.new(:id, :chinese, :english, :sources)\nend\n\nclass Era\n  include Anima.new(:id, :name)\nend\n\nclass LiteratureBaseMapper < Yaks::Mapper\n  link :profile, 'http://literature.example.com/profiles/{mapper_name}', expand: true\n  link :self, 'http://literature.example.com/{mapper_name}/{id}'\nend\n\nclass ScholarMapper < LiteratureBaseMapper\n  attributes :id, :name, :pinyin, :latinized\n  has_many :works\n\n  link 'http://literature.example.com/rels/quotes', 'http://literature.example.com/quotes/?author={downcased_pinyin}&q={query}', expand: [:downcased_pinyin], title: 'Search for quotes'\n  link :self, 'http://literature.example.com/authors/{downcased_pinyin}', replace: true\n\n  form :search do\n    title 'Find a Scholar'\n    method 'POST'\n    media_type 'application/x-www-form-urlencoded'\n\n    field :name,   label: 'Scholar Name', type: 'text'\n    field :pinyin, label: 'Hanyu Pinyin', type: 'text'\n  end\n\n  def downcased_pinyin\n    object.pinyin.downcase\n  end\nend\n\nclass WorkMapper < LiteratureBaseMapper\n  attributes :id, :chinese_name, :english_name\n  has_many :quotes\n  has_one :era\nend\n\nclass QuoteMapper < Yaks::Mapper\n  attributes :id, :chinese\nend\n\nclass EraMapper < Yaks::Mapper\n  attributes :id, :name\nend\n"
  },
  {
    "path": "yaks/spec/fixture_helpers.rb",
    "content": "require 'yaml'\nrequire 'json'\n\nmodule FixtureHelpers\n  module_function\n\n  def load_json_fixture(name)\n    JSON.parse(Yaks::Root.join('spec/json', name + '.json').read)\n  end\n\n  def load_yaml_fixture(name)\n    YAML.load(Yaks::Root.join('spec/yaml', name + '.yaml').read)\n  end\nend\n"
  },
  {
    "path": "yaks/spec/integration/dynamic_form_fields_spec.rb",
    "content": "RSpec.describe 'dynamic form fields' do\n  let(:mapper) do\n    Class.new(Yaks::Mapper) do\n      type :awesome\n      form :foo do\n        text :name\n        dynamic do |object|\n          object.each do |x|\n            text x\n          end\n        end\n      end\n    end\n  end\n\n  let(:yaks) { Yaks.new }\n  let(:object) { [:a, :b, :c] }\n\n  it 'should create dynamic form fields' do\n    expect(yaks.map(object, mapper: mapper)).to eql Yaks::Resource.new(\n      type: :awesome,\n      forms: [\n        Yaks::Resource::Form.new(\n          name: :foo,\n          fields: [\n            Yaks::Resource::Form::Field.new(name: :name, type: :text),\n            Yaks::Resource::Form::Field.new(name: :a, type: :text),\n            Yaks::Resource::Form::Field.new(name: :b, type: :text),\n            Yaks::Resource::Form::Field.new(name: :c, type: :text)\n          ]\n        )\n      ]\n    )\n  end\nend\n"
  },
  {
    "path": "yaks/spec/integration/fieldset_spec.rb",
    "content": "RSpec.describe 'dynamic form fields' do\n  let(:mapper) do\n    Class.new(Yaks::Mapper) do\n      type :awesome\n      form :foo do\n        fieldset do\n          legend \"I am legend\"\n          text :bar\n        end\n      end\n    end\n  end\n\n  let(:yaks) { Yaks.new }\n\n  it 'should create fieldsets with fields' do\n    expect(yaks.map(:foo, mapper: mapper)).to eql Yaks::Resource.new(\n      type: :awesome,\n      forms: [\n        Yaks::Resource::Form.new(\n          name: :foo,\n          fields: [\n            Yaks::Resource::Form::Fieldset.new(\n              fields: [\n                Yaks::Resource::Form::Legend.new(label: \"I am legend\", type: :legend),\n                Yaks::Resource::Form::Field.new(name: :bar, type: :text)\n              ]\n            )\n          ]\n        )\n      ]\n    )\n  end\n\n  it 'should convert to halo' do\n    expect(\n      yaks.with(default_format: :halo, hooks: [[:skip, :serialize]]).call(:foo, mapper: mapper)\n    ).to eql(\n      \"_controls\" => {\n        \"foo\" => {\n          \"name\" => \"foo\",\n          \"fields\" => [\n            {\n              \"type\" => \"fieldset\",\n              \"fields\" => [\n                {\n                  \"label\" => \"I am legend\",\n                  \"type\" => \"legend\"\n                }, {\n                  \"name\" => \"bar\",\n                  \"type\" => \"text\"\n                }\n              ]\n            }\n          ]\n        }\n      }\n    )\n  end\nend\n"
  },
  {
    "path": "yaks/spec/integration/map_to_resource_spec.rb",
    "content": "RSpec.describe 'Mapping domain models to Resource objects' do\n  include_context 'fixtures'\n  include_context 'yaks context'\n\n  subject { mapper.call(john) }\n  let(:mapper) { FriendMapper.new(yaks_context) }\n\n  it { should be_a Yaks::Resource }\n  its(:type)         { should eql 'friend' }\n  its(:attributes)   { should eql(id: 1, name: 'john') }\n  its(:links)        { should eql [ Yaks::Resource::Link.new(rel: :copyright, uri: '/api/copyright/2024') ] }\n\n  specify {\n    subject.subresources == [\n      Yaks::Resource.new(\n        type: 'pet_peeve',\n        rels: ['rel:pet_peeve'],\n        attributes: {id: 4, type: 'parsing with regexps'}\n      ),\n      Yaks::CollectionResource.new(\n        type: 'pet',\n        rels: ['rel:pets'],\n        members: [\n          Yaks::Resource.new(\n            type: 'pet',\n            attributes: {id: 2, species: 'dog', name: 'boingboing'}\n          ),\n          Yaks::Resource.new(\n            type: 'pet',\n            attributes: {id: 3, species: 'cat', name: 'wassup'}\n          )\n        ]\n      )\n    ]\n  }\n\n  its(:subresources) {\n    should eq(\n      [\n        Yaks::Resource.new(\n          type: 'pet_peeve',\n          rels: ['rel:pet_peeve'],\n          attributes: {id: 4, type: 'parsing with regexps'}\n        ),\n        Yaks::CollectionResource.new(\n          type: 'pet',\n          rels: ['rel:pets'],\n          members: [\n            Yaks::Resource.new(\n              type: 'pet',\n              attributes: {id: 2, species: 'dog', name: 'boingboing'}\n            ),\n            Yaks::Resource.new(\n              type: 'pet',\n              attributes: {id: 3, species: 'cat', name: 'wassup'}\n            )\n          ]\n        )\n      ]\n    )\n  }\nend\n"
  },
  {
    "path": "yaks/spec/json/confucius.collection_json.json",
    "content": "{\n  \"collection\": {\n    \"version\": \"1.0\",\n    \"href\": \"http://literature.example.com/authors/kongzi\",\n    \"items\": [\n      {\n        \"href\": \"http://literature.example.com/authors/kongzi\",\n        \"data\": [\n          { \"name\": \"id\",         \"value\": 9           },\n          { \"name\": \"name\",       \"value\": \"孔子\"      },\n          { \"name\": \"pinyin\",     \"value\": \"Kongzi\"    },\n          { \"name\": \"latinized\",  \"value\": \"Confucius\" }\n        ],\n        \"links\": [\n          {\n            \"rel\": \"profile\",\n            \"href\": \"http://literature.example.com/profiles/scholar\"\n          },\n          {\n            \"name\": \"Search for quotes\",\n            \"rel\": \"http://literature.example.com/rels/quotes\",\n            \"href\": \"http://literature.example.com/quotes/?author=kongzi&q={query}\"\n          }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "yaks/spec/json/confucius.hal.json",
    "content": "{\n  \"id\": 9,\n  \"name\": \"孔子\",\n  \"pinyin\": \"Kongzi\",\n  \"latinized\": \"Confucius\",\n  \"_links\": {\n    \"self\":    { \"href\": \"http://literature.example.com/authors/kongzi\"  },\n    \"profile\": { \"href\": \"http://literature.example.com/profiles/scholar\" },\n    \"http://literature.example.com/rels/quotes\": [\n        {\n          \"href\": \"http://literature.example.com/quotes/?author=kongzi&q={query}\",\n          \"templated\": true,\n          \"title\": \"Search for quotes\"\n        }\n    ]\n  },\n  \"_embedded\": {\n    \"http://literature.example.com/rel/works\": [\n      {\n        \"id\": 11,\n        \"chinese_name\": \"論語\",\n        \"english_name\": \"Analects\",\n        \"_links\": {\n          \"self\":    { \"href\": \"http://literature.example.com/work/11\" },\n          \"profile\": { \"href\": \"http://literature.example.com/profiles/work\" }\n        },\n        \"_embedded\": {\n          \"http://literature.example.com/rel/quotes\": [\n            {\n              \"id\": 17,\n              \"chinese\": \"廄焚。子退朝，曰：“傷人乎？” 不問馬。\"\n            },\n            {\n              \"id\": 18,\n              \"chinese\": \"子曰：“其恕乎！己所不欲、勿施於人。”\"\n            }\n          ],\n          \"http://literature.example.com/rel/era\": {\n            \"id\": 99,\n            \"name\": \"Zhou Dynasty\"\n          }\n        }\n      },\n      {\n        \"id\": 12,\n        \"chinese_name\": \"易經\",\n        \"english_name\": \"Commentaries to the Yi-jing\",\n        \"_links\": {\n          \"self\":    { \"href\": \"http://literature.example.com/work/12\" },\n          \"profile\": { \"href\": \"http://literature.example.com/profiles/work\" }\n        },\n        \"_embedded\": {\n          \"http://literature.example.com/rel/quotes\": [],\n          \"http://literature.example.com/rel/era\": null\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "yaks/spec/json/confucius.halo.json",
    "content": "{\n  \"id\": 9,\n  \"name\": \"孔子\",\n  \"pinyin\": \"Kongzi\",\n  \"latinized\": \"Confucius\",\n  \"_links\": {\n    \"self\":    { \"href\": \"http://literature.example.com/authors/kongzi\"  },\n    \"profile\": { \"href\": \"http://literature.example.com/profiles/scholar\" },\n    \"http://literature.example.com/rels/quotes\": {\n       \"href\": \"http://literature.example.com/quotes/?author=kongzi&q={query}\",\n       \"templated\": true,\n       \"title\": \"Search for quotes\"\n     }\n  },\n  \"_embedded\": {\n    \"http://literature.example.com/rel/works\": [\n      {\n        \"id\": 11,\n        \"chinese_name\": \"論語\",\n        \"english_name\": \"Analects\",\n        \"_links\": {\n          \"self\":    { \"href\": \"http://literature.example.com/work/11\" },\n          \"profile\": { \"href\": \"http://literature.example.com/profiles/work\" }\n        },\n        \"_embedded\": {\n          \"http://literature.example.com/rel/quotes\": [\n            {\n              \"id\": 17,\n              \"chinese\": \"廄焚。子退朝，曰：“傷人乎？” 不問馬。\"\n            },\n            {\n              \"id\": 18,\n              \"chinese\": \"子曰：“其恕乎！己所不欲、勿施於人。”\"\n            }\n          ],\n          \"http://literature.example.com/rel/era\": {\n            \"id\": 99,\n            \"name\": \"Zhou Dynasty\"\n          }\n        }\n      },\n      {\n        \"id\": 12,\n        \"chinese_name\": \"易經\",\n        \"english_name\": \"Commentaries to the Yi-jing\",\n        \"_links\": {\n          \"self\":    { \"href\": \"http://literature.example.com/work/12\" },\n          \"profile\": { \"href\": \"http://literature.example.com/profiles/work\" }\n        },\n        \"_embedded\": {\n          \"http://literature.example.com/rel/quotes\": [],\n          \"http://literature.example.com/rel/era\": null\n        }\n      }\n    ]\n  },\n  \"_controls\": {\n    \"search\": {\n      \"name\": \"search\",\n      \"title\": \"Find a Scholar\",\n      \"method\": \"POST\",\n      \"media_type\": \"application/x-www-form-urlencoded\",\n      \"fields\": [\n        {\n          \"name\": \"name\",\n          \"label\": \"Scholar Name\",\n          \"type\": \"text\"\n        },\n        {\n          \"name\": \"pinyin\",\n          \"label\": \"Hanyu Pinyin\",\n          \"type\": \"text\"\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "yaks/spec/json/confucius.json_api.json",
    "content": "{\n  \"data\": {\n    \"id\": \"9\",\n    \"type\": \"scholars\",\n    \"attributes\": {\n      \"name\": \"孔子\",\n      \"pinyin\": \"Kongzi\",\n      \"latinized\": \"Confucius\"\n    },\n    \"relationships\": {\n      \"works\": {\n        \"data\": [{\"type\": \"works\", \"id\": \"11\"}, {\"type\": \"works\", \"id\": \"12\"}]\n      }\n    },\n\t\t\"links\": {\n  \t  \"profile\": \"http://literature.example.com/profiles/scholar\",\n      \"self\": \"http://literature.example.com/authors/kongzi\",\n\t\t\t\"http://literature.example.com/rels/quotes\": \"http://literature.example.com/quotes/?author=kongzi&q={query}\"\n\t  }\n  },\n  \"included\": [\n      {\n        \"id\": \"11\",\n        \"type\": \"works\",\n        \"attributes\": {\n          \"chinese_name\": \"論語\",\n          \"english_name\": \"Analects\"\n        },\n        \"relationships\": {\n          \"quotes\": {\n            \"data\": [{\"type\": \"quotes\", \"id\": \"17\"}, {\"type\": \"quotes\", \"id\": \"18\"}]\n          },\n          \"era\": {\"data\": {\"type\": \"erae\", \"id\": \"99\"}}\n        },\n        \"links\": {\n          \"profile\": \"http://literature.example.com/profiles/work\",\n          \"self\": \"http://literature.example.com/work/11\"\n        }\n      },\n      {\n        \"id\": \"17\",\n        \"type\": \"quotes\",\n        \"attributes\": {\n          \"chinese\": \"廄焚。子退朝，曰：“傷人乎？” 不問馬。\"\n        }\n      },\n      {\n        \"id\": \"18\",\n        \"type\": \"quotes\",\n        \"attributes\": {\n          \"chinese\": \"子曰：“其恕乎！己所不欲、勿施於人。”\"\n        }\n      },\n      {\n        \"id\": \"99\",\n        \"type\": \"erae\",\n        \"attributes\": {\n          \"name\": \"Zhou Dynasty\"\n        }\n      },\n      {\n        \"id\": \"12\",\n        \"type\": \"works\",\n        \"attributes\": {\n          \"chinese_name\": \"易經\",\n          \"english_name\": \"Commentaries to the Yi-jing\"\n        },\n        \"relationships\": {\n          \"quotes\": { \"data\": [] },\n          \"era\": { \"data\": null }\n        },\n        \"links\": {\n          \"profile\": \"http://literature.example.com/profiles/work\",\n          \"self\": \"http://literature.example.com/work/12\"\n        }\n      }\n  ]\n}\n"
  },
  {
    "path": "yaks/spec/json/john.hal.json",
    "content": "{\n  \"id\": 1,\n  \"name\": \"john\",\n  \"_links\": {\n    \"copyright\": [\n      {\n        \"href\": \"/api/copyright/2024\"\n      }\n    ]\n  },\n  \"_embedded\": {\n    \"http://api.mysuperfriends.com/pet_peeve\": {\n      \"id\": 4,\n      \"type\": \"parsing with regexps\"\n    },\n    \"http://api.mysuperfriends.com/pets\": [\n      {\n        \"id\": 2,\n        \"name\": \"boingboing\",\n        \"species\": \"dog\"\n      },\n      {\n        \"id\": 3,\n        \"name\": \"wassup\",\n        \"species\": \"cat\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "yaks/spec/json/list_of_quotes.collection_json.json",
    "content": "{\n  \"collection\": {\n    \"version\": \"1.0\",\n    \"items\": [\n      {\n        \"data\": [\n          {\n            \"name\": \"id\",\n            \"value\": 17\n          },\n          {\n            \"name\": \"chinese\",\n            \"value\": \"廄焚。子退朝，曰：“傷人乎？” 不問馬。\"\n          }\n        ]\n      },\n      {\n        \"data\": [\n          {\n            \"name\": \"id\",\n            \"value\": 18\n          },\n          {\n            \"name\": \"chinese\",\n            \"value\": \"子曰：“其恕乎！己所不欲、勿施於人。”\"\n          }\n        ]\n      },\n      {\n        \"data\": [\n          {\n            \"name\": \"id\",\n            \"value\": 99\n          },\n          {\n            \"name\": \"chinese\",\n            \"value\": null\n          }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "yaks/spec/json/list_of_quotes.hal.json",
    "content": "{\n  \"_embedded\": {\n    \"rel:quotes\": [\n      {\n        \"id\": 17,\n        \"chinese\": \"廄焚。子退朝，曰：“傷人乎？” 不問馬。\"\n      },\n      {\n        \"id\": 18,\n        \"chinese\": \"子曰：“其恕乎！己所不欲、勿施於人。”\"\n      },\n      {\n        \"id\": 99,\n        \"chinese\": null\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "yaks/spec/json/list_of_quotes.json_api.json",
    "content": " {\n  \"data\": [\n    {\n      \"type\": \"quotes\",\n      \"id\": \"17\",\n      \"attributes\": {\n        \"chinese\": \"廄焚。子退朝，曰：“傷人乎？” 不問馬。\"\n      }\n    },\n    {\n      \"type\": \"quotes\",\n      \"id\": \"18\",\n      \"attributes\": {\n        \"chinese\": \"子曰：“其恕乎！己所不欲、勿施於人。”\"\n      }\n    },\n    {\n      \"type\": \"quotes\",\n      \"id\": \"99\",\n      \"attributes\": {\n        \"chinese\": null\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "yaks/spec/json/plant_collection.collection.json",
    "content": "{\n  \"collection\": {\n    \"version\": \"1.0\",\n    \"href\": \"http://api.example.com/plants\",\n    \"items\": [\n      {\n        \"href\": \"http://api.example.com/plants/7/plain_grass\",\n        \"data\": [ { \"name\": \"name\", \"value\": \"Plain grass\" },\n                  { \"name\": \"type\", \"value\": \"grass\" } ],\n        \"links\": [\n          { \"rel\": \"profile\", \"href\": \"http://api.example.com/doc/plant\" }\n        ]\n      },\n      {\n        \"href\": \"http://api.example.com/plants/15/oak\",\n        \"data\": [ { \"name\": \"name\", \"value\": \"Oak\" },\n                  { \"name\": \"type\", \"value\": \"tree\" } ],\n        \"links\": [\n          { \"rel\": \"profile\", \"href\": \"http://api.example.com/doc/plant\" }\n        ]\n      },\n      {\n        \"href\": \"http://api.example.com/plants/33/passiflora\",\n        \"data\": [ { \"name\": \"name\", \"value\": \"Passiflora\" },\n                  { \"name\": \"type\", \"value\": \"flower\" } ],\n        \"links\": [\n          { \"rel\": \"profile\", \"href\": \"http://api.example.com/doc/plant\" }\n        ]\n      }\n    ],\n    \"links\": [\n      { \"href\": \"http://api.example.com/plants\", \"rel\": \"self\" },\n      { \"href\": \"http://api.example.com/doc/plant_collection\", \"rel\": \"profile\" }\n    ]\n  }\n}\n"
  },
  {
    "path": "yaks/spec/json/plant_collection.hal.json",
    "content": "{\n   \"_links\" : {\n      \"profile\" : { \"href\" : \"http://api.example.com/doc/plant_collection\" },\n      \"self\" : { \"href\" : \"http://api.example.com/plants\" }\n  },\n   \"_embedded\" : {\n      \"http://api.example.com/rels/plants\" : [\n         {\n            \"_links\" : {\n               \"profile\" : { \"href\" : \"http://api.example.com/doc/plant\" },\n               \"self\" : { \"href\" : \"http://api.example.com/plants/7/plain_grass\" }\n            },\n            \"name\" : \"Plain grass\",\n            \"type\" : \"grass\"\n         },\n         {\n            \"_links\" : {\n               \"profile\" : { \"href\" : \"http://api.example.com/doc/plant\" },\n               \"self\" : { \"href\" : \"http://api.example.com/plants/15/oak\" }\n            },\n            \"name\" : \"Oak\",\n            \"type\" : \"tree\"\n         },\n         {\n            \"_links\" : {\n               \"profile\" : { \"href\" : \"http://api.example.com/doc/plant\" },\n               \"self\" : { \"href\" : \"http://api.example.com/plants/33/passiflora\" }\n            },\n            \"name\" : \"Passiflora\",\n            \"type\" : \"flower\"\n         }\n      ]\n   }\n}\n"
  },
  {
    "path": "yaks/spec/json/youtypeitwepostit.collection_json.json",
    "content": "{\n  \"collection\": {\n    \"version\": \"1.0\",\n    \"href\": \"http://www.youtypeitwepostit.com/api/\",\n\n    \"links\": [\n      { \"rel\": \"self\", \"href\": \"http://www.youtypeitwepostit.com/api/\" }\n    ],\n\n    \"items\": [\n      {\n        \"href\": \"http://www.youtypeitwepostit.com/api/12091295723803341\",\n        \"data\": [\n          {\n            \"name\": \"text\",\n            \"value\": \"massage\"\n          },\n          {\n            \"name\": \"date_posted\",\n            \"value\": \"2014-05-29T07:56:58.035Z\"\n          }\n        ],\n        \"links\": [\n          { \"rel\": \"profile\", \"href\": \"http://www.youtypeitwepostit.com/profiles/message\" }\n        ]\n      },\n      {\n        \"href\": \"http://www.youtypeitwepostit.com/api/613856331910938\",\n        \"data\": [\n          {\n            \"name\": \"text\",\n            \"value\": \"Squid!\"\n          },\n          {\n            \"name\": \"date_posted\",\n            \"value\": \"2013-03-28T21:51:08.406Z\"\n          }\n        ],\n        \"links\": [\n          { \"rel\": \"profile\", \"href\": \"http://www.youtypeitwepostit.com/profiles/message\" }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "yaks/spec/sanity_spec.rb",
    "content": "RSpec.describe 'assorted sanity checks' do\n  let(:resource_methods)            { Yaks::Resource.public_instance_methods.sort }\n  let(:collection_resource_methods) { Yaks::CollectionResource.public_instance_methods.sort }\n  let(:null_resource_methods)       { Yaks::NullResource.public_instance_methods.sort }\n\n  specify 'all resource classes should have the exact same public API' do\n    expect(resource_methods).to eql null_resource_methods\n    expect(resource_methods).to eql collection_resource_methods\n  end\nend\n"
  },
  {
    "path": "yaks/spec/spec_helper.rb",
    "content": "require 'yaks'\nrequire 'yaks-html'\nrequire 'virtus'\n\nrequire_relative '../../shared/rspec_config'\n\nrequire 'fixture_helpers'\n\nRSpec.configure do |rspec|\n  rspec.include FixtureHelpers\nend\n\nBogus.configure do |bogus|\n  bogus.search_modules << Yaks\n  bogus.search_modules << Yaks::Mapper\nend\n\nrequire_relative 'support/models'\nrequire_relative 'support/pet_mapper'\nrequire_relative 'support/pet_peeve_mapper'\nrequire_relative 'support/friends_mapper'\nrequire_relative 'support/fixtures'\nrequire_relative 'support/shared_contexts'\nrequire_relative 'support/youtypeit_models_mappers'\nrequire_relative 'support/deep_eql'\nrequire_relative 'support/classes_for_policy_testing'\n"
  },
  {
    "path": "yaks/spec/support/classes_for_policy_testing.rb",
    "content": "# Used by Yaks::DefaultPolicy* tests to test various name inference schemes\n\nclass SoyMapper; end\nclass Soy; end\nclass WildSoy < Soy; end\n\nmodule Grain\n  class Soy; end\n  class WildSoy < Soy; end\n\n  class Wheat; end\n  class Durum < Wheat; end\n\n  module Dry\n    class Soy < ::Grain::Soy; end\n    class SoyMapper; end\n  end\n\n  class SoyMapper; end\n  class SoyCollectionMapper; end\nend\n\nclass HomeMapper; end\nclass WheatMapper; end\nclass ObjectMapper; end\nclass BasicObjectMapper; end\n\nmodule MyMappers\n  class SoyMapper; end\n  class WheatMapper; end\n\n  module Grain\n    class SoyMapper; end\n  end\nend\n\nclass SoyCollectionMapper; end\n\nmodule Namespace\n  module Nested\n    class Rye; end\n    class Mung\n      alias_method :inspect, :to_s # on 1.9 inspect calls to_s\n      def to_s\n        \"mungbean\"\n      end\n    end\n  end\n\n  class RyeMapper; end\n  class RyeCollectionMapper; end\n\n  class CollectionMapper; end\n\n  class ShoeMapper; end\nend\n\nmodule DislikesCollectionMapper\n  def self.const_get(const)\n    raise \"not a NameError\" if const.to_s == 'CollectionMapper'\n  end\nend\n\nmodule DislikesOtherMappers\n  def self.const_get(const)\n    raise \"not a NameError\" if const.to_s != 'CollectionMapper'\n  end\nend\n"
  },
  {
    "path": "yaks/spec/support/deep_eql.rb",
    "content": "# When comparing deep nested structures, it can be really hard to figure out what\n# the actual differences are looking at the RSpec output. This custom matcher\n# traverses nested hashes and arrays recursively, and reports each difference\n# separately, with a JSONPath string of where the difference was found\n#\n# e.g.\n#\n# at $.shows[0].venues[0].name, got Foo, expected Bar\n\nmodule Matchers\n  class DeepEql\n    extend Forwardable\n    attr_reader :expectation, :stack, :target, :diffs, :result\n    def_delegators :stack, :push, :pop\n\n    def initialize(expectation, stack = [], diffs = [])\n      @expectation = expectation\n      @stack       = stack\n      @diffs       = diffs\n      @result      = true\n    end\n\n    def description\n      'be deeply equal'\n    end\n\n    def recurse(target, expectation)\n      # leave this in two lines so it doesn't short circuit\n      result = DeepEql.new(expectation, stack, diffs).matches?(target)\n      @result &&= result\n    end\n\n    def stack_as_jsonpath\n      '$' + stack.map do |item|\n        case item\n        when Integer, /\\W/\n          \"[#{item.inspect}]\"\n        else\n          \".#{item}\"\n        end\n      end.join\n    end\n\n    def add_failure_message(message)\n      diffs << \"at %s, %s\" % [stack_as_jsonpath, message]\n      @result = false\n    end\n\n    def compare(key)\n      push key\n      if target[key] != expectation[key]\n        if [Hash, Array].any?{|klz| target[key].is_a? klz }\n          recurse(target[key], expectation[key])\n        else\n          add_failure_message begin\n                                if expectation[key].class == target[key].class\n                                  \"expected #{expectation[key].inspect}, got #{target[key].inspect}\"\n                                else\n                                  \"expected #{expectation[key].class}: #{expectation[key].inspect}, got #{target[key].class}: #{target[key].inspect}\"\n                                end\n                              rescue Encoding::CompatibilityError\n                                \"expected #{expectation[key].encoding}, got #{target[key].encoding}\"\n                              end\n        end\n      end\n      pop\n    end\n\n    def matches?(target)\n      @target = target\n      case expectation\n      when Hash\n        if target.is_a?(Hash)\n          if target.class != expectation.class # e.g. HashWithIndifferentAccess\n            add_failure_message(\"expected #{expectation.class}, got #{target.class}\")\n          end\n          (expectation.keys - target.keys).each do |key|\n            add_failure_message \"Expected key #{key.inspect} => #{expectation[key].inspect}\"\n          end\n          (target.keys - expectation.keys).each do |key|\n            add_failure_message \"Unexpected key #{key.inspect} => #{target[key].inspect}\"\n          end\n          (target.keys & expectation.keys).each do |key|\n            compare key\n          end\n        else\n          add_failure_message(\"expected Hash got #{@target.inspect}\")\n        end\n\n      when Array\n        if target.is_a?(Array)\n          0.upto([target.count, expectation.count].max) do |idx|\n            compare idx\n          end\n        else\n          add_failure_message(\"expected Array got #{@target.inspect}\")\n        end\n\n      else\n        if target != expectation\n          add_failure_message(\"expected #{expectation.inspect}, got #{@target.inspect}\")\n        end\n      end\n\n      result\n    end\n\n    def failure_message_for_should\n      diffs.join(\"\\n\")\n    end\n    alias_method :failure_message, :failure_message_for_should\n\n    def failure_message_for_should_not\n      \"expected #{@target.inspect} not to be in deep_eql with #{@expectation.inspect}\"\n    end\n    alias_method :failure_message_when_negated, :failure_message_for_should_not\n  end\nend\n\nmodule RSpec\n  module Matchers\n    def deep_eql(exp)\n      ::Matchers::DeepEql.new(exp)\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/support/fixtures.rb",
    "content": "RSpec.shared_context 'fixtures' do\n  let(:john)       { Friend.new(id: 1, name: 'john', pets: [boingboing, wassup], pet_peeve: regexps) }\n  let(:boingboing) { Pet.new(id: 2, name: 'boingboing', species: 'dog')                              }\n  let(:wassup)     { Pet.new(id: 3, name: 'wassup', species: 'cat')                                  }\n  let(:regexps)    { PetPeeve.new(id: 4, type: 'parsing with regexps')                               }\nend\n"
  },
  {
    "path": "yaks/spec/support/friends_mapper.rb",
    "content": "class FriendMapper < Yaks::Mapper\n  attributes :id, :name\n\n  link :copyright, '/api/copyright/{year}'\n\n  def year\n    2024\n  end\n\n  has_one :pet_peeve, mapper: PetPeeveMapper\n  has_many :pets, mapper: PetMapper\n\n  # has_one :production_company,          # retrieve as `show.production_company`\n  #         profile: :company,            # use the company profile link from the registry, e.g. 'http://foo.api/profiles/company'\n  #         as: :producer,                # serialize as {\"producers\" => []} in JSON-API or [{class: [\"producer\"]}] in Siren\n  #         embed: :link,                 # don't embed the whole data, link to it, could also be embed: :resource\n  #         mapper: CompanyMapper,        # use this to find the resource URL, or to map the embedded resource\n  #         rel: :show_production_company # find the relation in the relation registry, e.g. http://foo.apo/rels/show_production_company\n\n  # # Full derived defaults\n  # has_one :production_company\n  #   # profile: :production_company,  # same as the 'name'\n  #   # as: :production_company,       # same as the 'name'\n  #   # embed: :links                  # depends on what is configured as default\n  #   # mapper: CompanyMapper          # found by matching the profile\n  #   # rel: :show_production_company  # \"#{context.profile_name}_#{relation.profile_name}\"\n\n  # # has_many :pets\nend\n"
  },
  {
    "path": "yaks/spec/support/models.rb",
    "content": "class Pet\n  include Virtus.model\n\n  attribute :id, Integer\n  attribute :name, String\n  attribute :species, String\nend\n\nclass PetPeeve\n  include Virtus.model\n\n  attribute :id, Integer\n  attribute :type, String\nend\n\nclass Friend\n  include Virtus.model\n\n  attribute :id, Integer\n  attribute :name, String\n  attribute :pets, Array[Pet]\n  attribute :pet_peeve, PetPeeve\nend\n"
  },
  {
    "path": "yaks/spec/support/pet_mapper.rb",
    "content": "class PetMapper < Yaks::Mapper\n  attributes :id, :name, :species\nend\nclass GreatPetMapper < Yaks::Mapper; end\nclass GreatPetCollectionMapper < Yaks::Mapper; end\n"
  },
  {
    "path": "yaks/spec/support/pet_peeve_mapper.rb",
    "content": "class PetPeeveMapper < Yaks::Mapper\n  attributes :id, :type\nend\n"
  },
  {
    "path": "yaks/spec/support/shared_contexts.rb",
    "content": "RSpec.shared_context 'collection resource' do\n  let(:resource) do\n    Yaks::CollectionResource.new(\n      links: links,\n      members: members,\n      rels: ['http://api.example.com/rels/plants']\n    )\n  end\n  let(:links)    { [] }\n  let(:members)  { [] }\nend\n\nRSpec.shared_context 'yaks context' do\n  let(:policy)            { Yaks::DefaultPolicy.new }\n  let(:rack_env)          { {} }\n  let(:mapper_stack)      { [] }\n  let(:yaks_context)      { {policy: policy, env: rack_env, mapper_stack: mapper_stack} }\nend\n\nRSpec.shared_context 'plant collection resource' do\n  include_context 'collection resource'\n\n  let(:links)   { [ plants_self_link, plants_profile_link ] }\n  let(:members) { [ plain_grass, oak, passiflora ] }\n\n  [\n    [ :plant,        :profile,  'http://api.example.com/doc/plant'            ],\n    [ :plants,       :profile,  'http://api.example.com/doc/plant_collection' ],\n    [ :plants,       :self,     'http://api.example.com/plants'               ],\n    [ :plain_grass,  :self,     'http://api.example.com/plants/7/plain_grass' ],\n    [ :oak,          :self,     'http://api.example.com/plants/15/oak'        ],\n    [ :passiflora,   :self,     'http://api.example.com/plants/33/passiflora' ]\n  ].each do |name, type, uri|\n    let(:\"#{name}_#{type}_link\") { Yaks::Resource::Link.new(rel: type, uri: uri) }\n  end\n\n  let(:plain_grass) do\n    Yaks::Resource.new(\n      attributes: {name: \"Plain grass\", type: \"grass\"},\n      links: [plain_grass_self_link, plant_profile_link]\n    )\n  end\n\n  let(:oak) do\n    Yaks::Resource.new(\n      attributes: {name: \"Oak\", type: \"tree\"},\n      links: [oak_self_link, plant_profile_link]\n    )\n  end\n\n  let(:passiflora) do\n    Yaks::Resource.new(\n      attributes: {name: \"Passiflora\", type: \"flower\"},\n      links: [passiflora_self_link, plant_profile_link]\n    )\n  end\nend\n"
  },
  {
    "path": "yaks/spec/support/youtypeit_models_mappers.rb",
    "content": "module Youtypeitwepostit\n  class Message\n    include Virtus.model\n\n    attribute :id, Integer\n    attribute :text, String\n    attribute :date_posted, String\n  end\n\n  class MessageMapper < Yaks::Mapper\n    link :self, 'http://www.youtypeitwepostit.com/api/{id}'\n    link :profile, 'http://www.youtypeitwepostit.com/profiles/message'\n\n    attributes :text, :date_posted\n  end\n\n  class CollectionMapper < Yaks::CollectionMapper\n    link :self, 'http://www.youtypeitwepostit.com/api/'\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/behaviour/optional_includes_spec.rb",
    "content": "require \"yaks/behaviour/optional_includes\"\n\nRSpec.describe Yaks::Behaviour::OptionalIncludes do\n  include_context 'yaks context'\n\n  subject(:mapper)   { mapper_class.new(yaks_context) }\n  let(:resource)     { mapper.call(instance) }\n\n  let(:mapper_class) do\n    Class.new(Yaks::Mapper).tap do |mapper_class|\n      mapper_class.send :include, Yaks::Behaviour::OptionalIncludes\n      mapper_class.type \"user\"\n      mapper_class.has_many :posts, mapper: post_mapper_class\n      mapper_class.has_one :account, mapper: account_mapper_class\n    end\n  end\n  let(:post_mapper_class) do\n    Class.new(Yaks::Mapper).tap do |mapper_class|\n      mapper_class.type \"post\"\n      mapper_class.has_many :comments, mapper: comment_mapper_class\n    end\n  end\n  let(:account_mapper_class) { Class.new(Yaks::Mapper) { type \"account\" } }\n  let(:comment_mapper_class) { Class.new(Yaks::Mapper) { type \"comment\" } }\n\n  let(:instance) { fake(posts: [fake(comments: [fake])], account: fake) }\n\n  it \"includes the associations\" do\n    rack_env[\"QUERY_STRING\"] = \"include=posts.comments,account\"\n\n    expect(resource.type).to eq \"user\"\n    expect(resource.subresources[0].type).to eq \"post\"\n    expect(resource.subresources[0].members[0].type).to eq \"post\"\n    expect(resource.subresources[0].members[0].subresources[0].type).to eq \"comment\"\n    expect(resource.subresources[0].members[0].subresources[0].members[0].type).to eq \"comment\"\n    expect(resource.subresources[1].type).to eq \"account\"\n  end\n\n  it \"excludes associations not specified in the QUERY_STRING\" do\n    rack_env[\"QUERY_STRING\"] = \"include=posts\"\n\n    expect(resource.subresources.count).to eq 1\n  end\n\n  it \"doesn't include the associations when QUERY_STRING is empty\" do\n    expect(resource.type).to eq \"user\"\n    expect(resource.subresources).to be_empty\n  end\n\n  it \"allows :if to override the query parameter checking\" do\n    mapper_class.has_one :account, mapper: account_mapper_class, if: true\n\n    expect(resource.subresources.count).to eq 1\n  end\n\n  it \"caches parsing of the query parameter\" do\n    rack_env[\"QUERY_STRING\"] = \"include=posts\"\n    expect(mapper.call(instance).subresources.count).to eq 1\n\n    rack_env[\"QUERY_STRING\"] = nil\n    expect(mapper.call(instance).subresources.count).to eq 1\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/builder_spec.rb",
    "content": "RSpec.describe Yaks::Builder do\n  class Buildable\n    include Attribs.new(:foo, :bar)\n\n    def self.create(foo, bar)\n      new(foo: foo, bar: bar)\n    end\n\n    def finalize\n      with(foo: 7, bar: 8)\n    end\n\n    def wrong_type(x, y)\n      \"foo #{x} #{y}\"\n    end\n  end\n\n  subject do\n    Yaks::Builder.new(Buildable, [:finalize]) do\n      def_set :foo, :bar\n      def_forward :wrong_type, :with\n    end\n  end\n\n  describe '#initialize' do\n    it 'should set the class to be instantiated' do\n      expect(subject.create(1, 2)).to be_instance_of Buildable\n    end\n\n    it 'should accept just a class' do\n      expect { Yaks::Builder.new(Buildable) }.to_not raise_error\n    end\n\n    it 'should define delegators for passed in method names' do\n      expect(subject).to respond_to(:finalize)\n    end\n\n    it 'should store the methods for inspection' do\n      expect(subject.inspect).to eql \"#<Builder Buildable [:finalize]>\"\n    end\n\n    it 'should evaluate the passed in block' do\n      expect(subject).to respond_to(:foo)\n    end\n  end\n\n  describe '#create' do\n    it 'should keep state' do\n      expect(\n        subject.create(3, 4) do\n          foo 7\n          with bar: 6\n        end.to_h\n      ).to eql(foo: 7, bar: 6)\n    end\n\n    it 'should unwrap again' do\n      expect(subject.create(3, 4) { finalize }).to eql Buildable.new(foo: 7, bar: 8)\n    end\n\n    context 'with no methods to forward' do\n      subject do\n        Yaks::Builder.new(Buildable)\n      end\n\n      it 'should still work' do\n        expect(subject.create(3, 4)).to eql Buildable.new(foo: 3, bar: 4)\n      end\n    end\n  end\n\n  describe '#build' do\n    it 'should pass on the initial state if no block is given' do\n      expect(subject.build(:foo)).to equal :foo\n    end\n\n    it 'should pass any extra args to the block' do\n      expect(subject.build(Buildable.new(foo: 1, bar: 2), 9) {|f| foo(f)}).to eql(\n        Buildable.new(foo: 9, bar: 2)\n      )\n    end\n  end\n\n  describe '#inspect' do\n    subject do\n      Yaks::Builder.new(Buildable, [:foo, :bar])\n    end\n\n    it 'should show the class and methods' do\n      expect(subject.inspect).to eql '#<Builder Buildable [:foo, :bar]>'\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/collection_mapper_spec.rb",
    "content": "RSpec.describe Yaks::CollectionMapper, '#call' do\n  include_context 'fixtures'\n\n  subject(:mapper) { mapper_class.new(context) }\n  let(:mapper_class) { described_class }\n  let(:mapper_stack) { [] }\n\n  let(:context) {\n    {item_mapper: item_mapper,\n     policy: policy,\n     env: {},\n     mapper_stack: mapper_stack}\n  }\n\n  let(:collection) { [] }\n  let(:item_mapper) { Class.new(Yaks::Mapper) { type 'the_type' } }\n  let(:policy) { Yaks::DefaultPolicy.new }\n\n  it 'should map the collection' do\n    expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(\n      type: 'the_type',\n      links: [],\n      attributes: {},\n      members: [],\n      rels: ['rel:the_types']\n    )\n  end\n\n  it 'should accept a second \"env\" argument' do\n    expect(mapper.call(collection, {})).to be_a Yaks::CollectionResource\n  end\n\n  context 'when at the top of the stack' do\n    it 'should have a \"collection\" rel derived from the type' do\n      expect(mapper.call(collection).rels).to eql ['rel:the_types']\n    end\n  end\n\n  context 'when not at the top of the stack' do\n    let(:mapper_stack) { [ :foo ]}\n\n    it 'should not have a rel' do\n      expect(mapper.call(collection).rels).to eql []\n    end\n  end\n\n  context 'with members' do\n    let(:collection) { [boingboing, wassup]}\n    let(:item_mapper) { PetMapper }\n\n    it 'should map the members' do\n      stub(policy).derive_mapper_from_object(any_args) do\n        raise \":item_mapper was specified explicitly, should not be derived from object\"\n      end\n\n      expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(\n        type: 'pet',\n        links: [],\n        attributes: {},\n        members: [\n          Yaks::Resource.new(type: 'pet', attributes: {id: 2, species: \"dog\", name: \"boingboing\"}),\n          Yaks::Resource.new(type: 'pet', attributes: {id: 3, species: \"cat\", name: \"wassup\"})\n        ],\n        rels: ['rel:pets']\n      )\n    end\n  end\n\n  context 'without an item_mapper in the context' do\n    let(:context) {\n      {\n        policy: policy,\n        env: {},\n        mapper_stack: []\n      }\n    }\n    let(:collection) { [boingboing, wassup]}\n\n    it 'should infer the item mapper' do\n      expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(\n        type: 'pet',\n        links: [],\n        attributes: {},\n        members: [\n          Yaks::Resource.new(type: 'pet', attributes: {id: 2, species: \"dog\", name: \"boingboing\"}),\n          Yaks::Resource.new(type: 'pet', attributes: {id: 3, species: \"cat\", name: \"wassup\"})\n        ],\n        rels: ['rel:pets']\n      )\n    end\n  end\n\n  context 'with collection attributes' do\n    subject(:mapper) {\n      Class.new(Yaks::CollectionMapper) do\n        attributes :foo, :bar\n      end.new(context)\n    }\n\n    let(:collection) {\n      Class.new(SimpleDelegator) do\n        def foo\n          123\n        end\n\n        def bar\n          'pi la~~~'\n        end\n      end.new([])\n    }\n\n    it 'should map the attributes' do\n      expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(\n        type: 'the_type',\n        links: [],\n        attributes: {foo: 123, bar: 'pi la~~~'},\n        members: [],\n        rels: ['rel:the_types']\n      )\n    end\n  end\n\n  context 'with collection links' do\n    subject(:mapper) {\n      Class.new(Yaks::CollectionMapper) do\n        link :self, 'http://api.example.com/orders'\n      end.new(context)\n    }\n\n    it 'should map the links' do\n      expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(\n        type: 'the_type',\n        links: [ Yaks::Resource::Link.new(rel: :self, uri: 'http://api.example.com/orders') ],\n        attributes: {},\n        members: [],\n        rels: ['rel:the_types']\n      )\n    end\n  end\n\n  describe 'overriding #collection' do\n    let(:mapper_class) do\n      Class.new(described_class) do\n        type 'pet'\n\n        def collection\n          super.drop(1)\n        end\n      end\n    end\n\n    let(:collection) { [boingboing, wassup]}\n    let(:item_mapper) { PetMapper }\n\n    it 'should use the redefined collection method' do\n      expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(\n        type: 'pet',\n        links: [],\n        attributes: {},\n        members: [\n          Yaks::Resource.new(type: 'pet', attributes: {id: 3, species: \"cat\", name: \"wassup\"})\n        ],\n        rels: ['rel:pets']\n      )\n    end\n  end\n\n  context 'with an empty collection' do\n    context 'without an item_mapper specified' do\n      let(:context) { Yaks::Util.slice_hash(super(), :policy, :env, :mapper_stack) }\n\n      it 'should use a rel of \"collection\"' do\n        expect(mapper.call([]).rels).to eq ['collection']\n      end\n    end\n\n    context 'with an item_mapper specified' do\n      let(:context) { Yaks::Util.slice_hash(super(), :policy, :env, :mapper_stack, :item_mapper) }\n\n      it 'should derive the collection rel from the item mapper' do\n        expect(mapper.call([]).rels).to eq ['rel:the_types']\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/collection_resource_spec.rb",
    "content": "RSpec.describe Yaks::CollectionResource do\n  subject(:collection) { described_class.new(init_opts) }\n  let(:init_opts) { {} }\n\n  its(:collection?)    { should be true }\n  its(:null_resource?) { should be false }\n\n  context 'with nothing passed in the contstructor' do\n    its(:type)         { should be_nil  }\n    its(:links)        { should eql []  }\n    its(:attributes)   { should eql({}) }\n    its(:members)      { should eql []  }\n    its(:subresources) { should eql []  }\n    its(:rels)         { should eql []  }\n  end\n\n  context 'with a full constructor' do\n    let(:init_opts) {\n      {\n        type: 'order',\n        links: [\n          Yaks::Resource::Link.new(rel: 'http://rels/summary', uri: 'http://order/10/summary'),\n          Yaks::Resource::Link.new(rel: :profile, uri: 'http://rels/collection')\n        ],\n        attributes: {total: 10.00},\n        members: [\n          Yaks::Resource.new(\n            type: 'order',\n            links: [Yaks::Resource::Link.new(rel: :self, uri: 'http://order/10')],\n            attributes: {customer: 'John Doe', price: 10.00}\n          )\n        ],\n        rels: ['http://api.example.org/rels/orders']\n      }\n    }\n\n    its(:type)       { should eql 'order' }\n    its(:links)      {\n      should eql [\n        Yaks::Resource::Link.new(rel: 'http://rels/summary', uri: 'http://order/10/summary'),\n        Yaks::Resource::Link.new(rel: :profile, uri: 'http://rels/collection')\n      ]\n    }\n    its(:attributes) { should eql(total: 10.00) }\n    its(:members)    {\n      should eql [\n        Yaks::Resource.new(\n          type: 'order',\n          links: [Yaks::Resource::Link.new(rel: :self, uri: 'http://order/10')],\n          attributes: {customer: 'John Doe', price: 10.00}\n        )\n      ]\n    }\n    its(:rels) { should eq ['http://api.example.org/rels/orders'] }\n\n    its(:subresources) { should eql [] }\n  end\n\n  describe '#seq' do\n    let(:init_opts) { {members: [1, 2, 3]} }\n\n    it 'iterates over the members' do\n      expect(subject.seq.map(&:next)).to eql [2, 3, 4]\n    end\n  end\n\n  describe '#collection?' do\n    it 'should always be true' do\n      expect(subject.collection?).to be true\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/config_spec.rb",
    "content": "RSpec.describe Yaks::Config do\n  include_context 'fixtures'\n\n  def self.configure(&blk)\n    subject(:config) { Yaks::ConfigBuilder.create(&blk) }\n  end\n\n  describe '#initialize' do\n    context 'defaults' do\n      configure {}\n\n      its(:default_format)      { should equal :hal }\n      its(:policy_class)        { should <= Yaks::DefaultPolicy }\n      its(:primitivize)         { should be_a Yaks::Primitivize }\n      its(:serializers)         { should eql(Yaks::Serializer.all) }\n      its(:hooks)               { should eql([]) }\n      its(:format_options_hash) { should eql({}) }\n    end\n  end\n\n  describe '#default_format' do\n    configure do\n      default_format :json_api\n    end\n\n    its(:default_format) { should equal :json_api }\n  end\n\n  describe '#policy_class' do\n    MyPolicy = Struct.new(:options)\n    configure do\n      policy_class MyPolicy\n    end\n\n    its(:policy_class) { should equal MyPolicy }\n    its(:policy)       { should be_a MyPolicy  }\n  end\n\n  describe '#rel_template' do\n    configure do\n      mapper_namespace Object\n      rel_template 'http://rel/foo'\n    end\n\n    its(:policy_options) { should eql(namespace: Object, rel_template: 'http://rel/foo') }\n  end\n\n  describe '#mapper_for' do\n    let(:expected_options) do\n      {\n        namespace: Object,\n        mapper_rules: {\n          home: HomeMapper,\n          Soy => MyMappers::WheatMapper\n        }\n      }\n    end\n    configure do\n      mapper_namespace Object\n      mapper_for Soy, MyMappers::WheatMapper\n      mapper_for :home, HomeMapper\n    end\n\n    its(:policy_options) { should eql(expected_options) }\n  end\n\n  describe '#format_options' do\n    configure do\n      format_options :hal, plural_links: [:self, :profile]\n      format_options :collection_json, template: :template_form_name\n    end\n\n    it 'should set format options' do\n      expect(config.format_options_hash[:hal]).to eql(plural_links: [:self, :profile])\n    end\n  end\n\n  describe '#json_serializer' do\n    configure do\n      json_serializer(&:upcase)\n    end\n\n    specify do\n      expect(config.serializers[:json].call('foo')).to eql \"FOO\"\n    end\n  end\n\n  describe '#map_to_primitive' do\n    configure do\n      map_to_primitive Date do |date|\n        date.strftime(\"%Y-%m\")\n      end\n    end\n\n    it 'registers how to handle a primitive type' do\n      expect(config.primitivize.call(Date.new(2015, 10))).to eql \"2015-10\"\n    end\n\n    it 'should not change the existing primitize instance' do\n      old_prim = config.primitivize\n      config.map_to_primitive Date do |date|\n        date.strftime(\"%m-%Y\")\n      end\n      expect(old_prim.call(Date.new(2015, 10))).to eql \"2015-10\"\n    end\n  end\n\n  describe '#mapper_namespace' do\n    configure do\n      rel_template '/foo/{rel}'\n      mapper_namespace Yaks::Mapper\n    end\n\n    it 'configures the policy to look in the given namespace' do\n      expect(config.policy.options[:namespace]).to eql Yaks::Mapper\n    end\n\n    it 'should not overwrite other options' do\n      expect(config.policy.options[:rel_template]).to eql '/foo/{rel}'\n    end\n  end\n\n  describe '#policy' do\n    PolicyClass = Class.new do\n      include Attribs.new(:namespace)\n    end\n\n    configure do\n      policy_class PolicyClass\n      mapper_namespace Yaks::Mapper\n    end\n\n    it 'returns an instantiated policy' do\n      expect(config.policy).to eql PolicyClass.new(namespace: Yaks::Mapper)\n    end\n  end\n\n  describe '#derive_mapper_from_object' do\n    configure { }\n    let(:object) { Pet.new(id: 7, name: 'fifi', species: 'cat') }\n    subject do\n      config.derive_mapper_from_object do |object|\n        mapper_class = super(object)\n        Object.const_get(\"Great#{mapper_class.name}\")\n      end\n    end\n\n    its(:policy_class) { should <= Yaks::DefaultPolicy }\n\n    it 'should override the policy method' do\n      expect(subject.policy.derive_mapper_from_object(object)).to be GreatPetMapper\n    end\n  end\n\n  describe '#derive_mapper_from_item' do\n    configure { }\n    let(:object) { Pet.new(id: 7, name: 'fifi', species: 'cat') }\n    subject do\n      config.derive_mapper_from_item do |object|\n        mapper_class = super(object)\n        Object.const_get(\"Great#{mapper_class.name}\")\n      end\n    end\n\n    its(:policy_class) { should <= Yaks::DefaultPolicy }\n\n    it 'should override the policy method' do\n      expect(subject.policy.derive_mapper_from_item(object)).to be GreatPetMapper\n      expect(subject.policy.derive_mapper_from_object(object)).to be GreatPetMapper\n    end\n  end\n\n  describe '#derive_mapper_from_collection' do\n    configure { }\n    let(:object) { [Pet.new(id: 7, name: 'fifi', species: 'cat')] }\n    subject do\n      config.derive_mapper_from_collection do |object|\n        mapper_class_name = super(object).name.split('::').last\n        Object.const_get(\"GreatPet#{mapper_class_name}\")\n      end\n    end\n\n    its(:policy_class) { should <= Yaks::DefaultPolicy }\n\n    it 'should override the policy method' do\n      expect(subject.policy.derive_mapper_from_collection(object)).to be GreatPetCollectionMapper\n      expect(subject.policy.derive_mapper_from_object(object)).to be GreatPetCollectionMapper\n    end\n  end\n\n  describe '#call' do\n    configure do\n      rel_template 'http://api.mysuperfriends.com/{rel}'\n      format_options :hal, plural_links: [:copyright]\n      skip :serialize\n    end\n\n    specify do\n      expect(config.call(john)).to eql(load_json_fixture 'john.hal')\n    end\n  end\n\n  describe '#map' do\n    configure {}\n\n    it 'only performs the mapping stage' do\n      expect(config.map(wassup))\n        .to eql Yaks::Resource.new(\n                  type: \"pet\",\n                  attributes: {id: 3, name: \"wassup\", species: \"cat\"}\n                )\n    end\n  end\n\n  describe '#read' do\n    configure {\n      default_format :json_api\n    }\n\n    it 'invokes the reader for the given format' do\n      expect(config.read('{\"data\": [{\"type\": \"pets\",\n                                     \"id\": 3,\n                                     \"name\": \"wassup\",\n                                     \"species\": \"cat\"}]}').members.first)\n        .to eql Yaks::Resource.new(\n                  type: \"pet\",\n                  attributes: {id: 3, name: \"wassup\", species: \"cat\"}\n                )\n    end\n  end\n\n  describe '#format' do\n    configure {\n      default_format :hal\n    }\n\n    it 'invokes the formatter on a resource' do\n      expect(config.format(Yaks::Resource.new(attributes: {shape: \"round\"})))\n        .to eql(\"shape\" => \"round\")\n    end\n  end\n\n  describe '#runner' do\n    configure {}\n\n    it 'provides a Yaks::Runner' do\n      expect(config.runner(:foo, bar: 1))\n        .to eql Yaks::Runner.new(config: config, object: :foo, options: {bar: 1})\n    end\n  end\n\n  describe '#serializer' do\n    configure do\n      serializer(:foo) {|x| x.upcase.reverse }\n    end\n\n    it 'registers a serializer for a given type' do\n      expect(config.serializers[:foo].call('bar')).to eql 'RAB'\n    end\n\n    it 'should keep existing serializers' do\n      expect(config.serializers[:json].call([:foo, :bar], {})).to eql %{[\\n  \"foo\",\\n  \"bar\"\\n]}\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/configurable_spec.rb",
    "content": "require 'securerandom'\n\nclass Kitten\n  include Attribs.new(:furriness)\n\n  def self.create(opts, &block)\n    level = opts[:fur_level]\n    level = block.call(level) if block\n    new(furriness: level)\n  end\nend\n\nclass Hanky\n  include Attribs.new(:stickyness, :size, :color)\n\n  def self.create(sticky, opts = {})\n    new(stickyness: sticky, size: opts[:size], color: opts[:color])\n  end\nend\n\nRSpec.describe Yaks::Configurable do\n  let(:suffix) { SecureRandom.hex(16) }\n  subject do\n    # rubocop:disable Lint/Eval\n    eval %<\nclass TestConfigurable#{suffix}\n  class Config\n    include Attribs.new(color: 'blue', taste: 'sour', contents: [])\n\n    def turn_into_orange\n      with(color: 'orange', taste: 'like an orange')\n    end\n\n    def turn_into_apple\n      with(color: 'green', taste: 'like an apple')\n    end\n\n    def with_color_and_contents(color)\n      with(color: color, contents: yield(contents))\n    end\n  end\n  extend Yaks::Configurable\n\n  def_set :color, :taste\n  def_forward :turn_into_apple, :turn_into_orange, :with_color_and_contents\n  def_forward appleize: :turn_into_apple\n  def_add :kitten, create: Kitten, append_to: :contents, defaults: {fur_level: 7}\n  def_add :cat, create: Kitten, append_to: :contents\n  def_add :hanky, create: Hanky, append_to: :contents, defaults: {size: 12, color: :red}\nend\n>\n    Kernel.const_get(\"TestConfigurable#{suffix}\")\n  end\n\n  describe '.extended' do\n    it 'should initialize an empty config object' do\n      expect(subject.config).to eql subject::Config.new\n    end\n  end\n\n  describe '#def_add' do\n    it 'should add' do\n      subject.kitten(fur_level: 9)\n      expect(subject.config.contents).to eql [Kitten.new(furriness: 9)]\n    end\n\n    it 'should use defaults if configured' do\n      subject.kitten\n      expect(subject.config.contents).to eql [Kitten.new(furriness: 7)]\n    end\n\n    it 'should work without defaults configured' do\n      subject.cat(fur_level: 3)\n      expect(subject.config.contents).to eql [Kitten.new(furriness: 3)]\n    end\n\n    it 'should pass on a block' do\n      subject.cat(fur_level: 3) {|l| l + 3}\n      expect(subject.config.contents).to eql [Kitten.new(furriness: 6)]\n    end\n\n    it 'should work with a create with positional arguments - defaults' do\n      subject.hanky(3)\n      expect(subject.config.contents).to eql [Hanky.new(stickyness: 3, size: 12, color: :red)]\n    end\n\n    it 'should work with a create with positional arguments' do\n      subject.hanky(5, size: 15)\n      expect(subject.config.contents).to eql [Hanky.new(stickyness: 5, size: 15, color: :red)]\n    end\n  end\n\n  describe '#def_set' do\n    it 'should set' do\n      subject.color 'red'\n      expect(subject.config.color).to eql 'red'\n    end\n\n    it 'should capture blocks as closures' do\n      subject.color {|x| \"#{x} blue\"}\n      expect(subject.config.color.call(\"dark\")).to eql 'dark blue'\n    end\n\n    it 'should signal when called without arguments' do\n      expect { subject.color }.to raise_error(ArgumentError, \"setting color: no value and no block given\")\n    end\n\n    it 'is an error when both a value and a block are passed in' do\n      expect { subject.color('red') {'blue'} }.to raise_error(ArgumentError, \"ambiguous invocation setting color: give either a value or a block, not both.\")\n    end\n  end\n\n  describe '#def_forward' do\n    it 'should generate a method that delegates to the config instance - first position' do\n      subject.turn_into_orange\n      expect(subject.config.color).to eql 'orange'\n    end\n\n    it 'should generate a method that delegates to the config instance - last position' do\n      subject.turn_into_apple\n      expect(subject.config.color).to eql 'green'\n    end\n\n    it 'should work with a hash for mappings' do\n      subject.appleize\n      expect(subject.config.color).to eql 'green'\n    end\n\n    it 'should forward arguments and block' do\n      subject.with_color_and_contents('brown') do |contents|\n        contents + [1, 2, 3]\n      end\n\n      expect(subject.config.color).to eql 'brown'\n      expect(subject.config.contents).to eql [1, 2, 3]\n    end\n  end\n\n  describe '#inherited' do\n    it 'should propagate the config state' do\n      subject.appleize\n      expect(Class.new(subject).config.color).to eql 'green'\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/default_policy/derive_mapper_from_collection_spec.rb",
    "content": "RSpec.describe Yaks::DefaultPolicy, '#derive_mapper_from_collection' do\n  subject(:policy) { described_class.new(options) }\n\n  let(:options) { {} }\n\n  context 'given an empty array' do\n    it 'should return the vanilla CollectionMapper' do\n      expect(policy.derive_mapper_from_collection([])).to be Yaks::CollectionMapper\n    end\n  end\n\n  it 'should find the mapper based on naming' do\n    expect(policy.derive_mapper_from_collection([Soy.new])).to be SoyCollectionMapper\n  end\n\n  it 'should not care about the object module' do\n    expect(policy.derive_mapper_from_collection([Grain::Soy.new])).to be SoyCollectionMapper\n  end\n\n  context 'if no collection mapper with a similar name is defined' do\n    let(:options) { {namespace: Namespace} }\n\n    it 'should look for a CollectionMapper in the namespace' do\n      expect(policy.derive_mapper_from_collection([WildSoy.new])).to be(Namespace::CollectionMapper)\n    end\n\n    context 'when trying to lookup CollectionMapper results in something other than an NameError' do\n      let(:options) { {namespace: DislikesCollectionMapper} }\n\n      it 'should propagate the error' do\n        expect {\n          policy.derive_mapper_from_object([])\n        }.to raise_error(RuntimeError)\n      end\n    end\n\n    context 'when trying to lookup a specific collection mapper results in something other than an NameError' do\n      let(:options) { {namespace: DislikesOtherMappers} }\n\n      it 'should propagate the error' do\n        expect {\n          policy.derive_mapper_from_object([Namespace::Nested::Rye.new])\n        }.to raise_error(RuntimeError)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/default_policy/derive_mapper_from_item_spec.rb",
    "content": "RSpec.describe Yaks::DefaultPolicy, '#derive_mapper_from_item' do\n  subject(:policy) { described_class.new(options) }\n\n  let(:options) { {} }\n\n  it 'should use const_get with second argument set to false' do\n    stub(Object).const_get(any_args) { SoyMapper }\n    policy.derive_mapper_from_item(Soy.new)\n    expect(Object).to have_received.const_get(\"SoyMapper\", false)\n  end\n\n  context 'at top level' do\n    it 'should derive it by name' do\n      expect(policy.derive_mapper_from_item(Soy.new)).to be SoyMapper\n    end\n\n    it 'should look for parent class if not found' do\n      expect(policy.derive_mapper_from_item(WildSoy.new)).to be SoyMapper\n    end\n\n    context 'with namespace option set' do\n      let(:options) { {namespace: MyMappers} }\n\n      it 'should look inside the namespace' do\n        expect(policy.derive_mapper_from_item(Soy.new)).to be MyMappers::SoyMapper\n      end\n\n      it 'should look for its parent class mapper in the namespace if not found' do\n        expect(policy.derive_mapper_from_item(WildSoy.new)).to be MyMappers::SoyMapper\n      end\n    end\n  end\n\n  context 'inside a module' do\n    it 'should look inside the module' do\n      expect(policy.derive_mapper_from_item(Grain::Soy.new)).to be Grain::SoyMapper\n    end\n\n    it 'should look for its parent class mapper in the module if not found' do\n      expect(policy.derive_mapper_from_item(Grain::WildSoy.new)).to be Grain::SoyMapper\n    end\n\n    context 'no mapper defined in module' do\n      it 'should look for mapper outside module' do\n        expect(policy.derive_mapper_from_item(Grain::Wheat.new)).to be WheatMapper\n      end\n\n      it 'should look for its parent class mapper outside module' do\n        expect(policy.derive_mapper_from_item(Grain::Durum.new)).to be WheatMapper\n      end\n    end\n\n    context 'with namespace option set' do\n      let(:options) { {namespace: MyMappers} }\n\n      it 'should look inside the module' do\n        expect(policy.derive_mapper_from_item(Grain::Soy.new)).to be MyMappers::Grain::SoyMapper\n      end\n\n      it 'should look for its parent class mapper if not found' do\n        expect(policy.derive_mapper_from_item(Grain::WildSoy.new)).to be MyMappers::Grain::SoyMapper\n      end\n\n      context 'no mapper defined in module' do\n        it 'should look for mapper in namespace top level' do\n          expect(policy.derive_mapper_from_item(Grain::Wheat.new)).to be MyMappers::WheatMapper\n        end\n\n        it 'should look for its parent mapper in namespace top level' do\n          expect(policy.derive_mapper_from_item(Grain::Durum.new)).to be MyMappers::WheatMapper\n        end\n      end\n    end\n\n    context 'deeply nested module' do\n      it 'should look inside the module' do\n        expect(policy.derive_mapper_from_item(Grain::Dry::Soy.new)).to be Grain::Dry::SoyMapper\n      end\n    end\n  end\n\n  context 'when no mapper is found' do\n    it 'should give a nice message' do\n      expect do\n        policy.derive_mapper_from_item(Namespace::Nested::Mung.new)\n      end.to raise_error /Failed to find a mapper for #<Namespace::Nested::Mung:0x\\h+>. Did you mean to implement Namespace::Nested::MungMapper\\?/\n    end\n\n    context 'with namespace option specified' do\n      let(:options) { {namespace: MyMappers} }\n\n      it 'should give a nice message' do\n        expect do\n          policy.derive_mapper_from_item(Namespace::Nested::Rye.new)\n        end.to raise_error /Failed to find a mapper for #<Namespace::Nested::Rye:0x\\h+>. Did you mean to implement MyMappers::Namespace::Nested::RyeMapper\\?/\n      end\n    end\n  end\n\n  context 'Object instance' do\n    it 'return ObjectMapper' do\n      expect(policy.derive_mapper_from_item(Object.new)).to be ObjectMapper\n    end\n\n    context 'no mapper available' do\n      before { Object.send(:remove_const, \"ObjectMapper\") }\n      after  { Object::ObjectMapper = Class.new }\n\n      it 'raise error' do\n        expect { policy.derive_mapper_from_item(Object.new) }.to raise_error RuntimeError\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/default_policy/derive_mapper_from_object_spec.rb",
    "content": "RSpec.describe Yaks::DefaultPolicy, '#derive_mapper_from_object' do\n  subject(:policy) { described_class.new }\n\n  context 'for a single instance' do\n    let(:object) { Soy.new }\n\n    it 'should call derive_mapper_for_item' do\n      stub(policy).derive_mapper_from_item(object) { SoyMapper }\n      policy.derive_mapper_from_object(object)\n      expect(policy).to have_received.derive_mapper_from_item(object)\n    end\n  end\n\n  context 'for array-like objects' do\n    let(:object) { [Soy.new] }\n\n    it 'should call derive_mapper_for_item' do\n      stub(policy).derive_mapper_from_collection(object) { SoyCollectionMapper }\n      policy.derive_mapper_from_object(object)\n      expect(policy).to have_received.derive_mapper_from_collection(object)\n    end\n  end\n\n  context 'mapper_for options set' do\n    subject(:policy) { described_class.new(options) }\n\n    context 'when mapping a class' do\n      let(:options) { {mapper_rules: {home: HomeMapper, Soy => MyMappers::WheatMapper}} }\n\n      it 'should use the mapping' do\n        expect(policy.derive_mapper_from_object(Soy.new)).to be MyMappers::WheatMapper\n      end\n    end\n\n    context 'when mapping a symbol' do\n      let(:options) { {mapper_rules: {soy: SoyMapper}} }\n\n      it 'should use the mapping' do\n        expect(policy.derive_mapper_from_object(:soy)).to be SoyMapper\n      end\n    end\n\n    context 'when mapping a lambda' do\n      let(:user) { fake(logged_in?: true) }\n      let(:options) { {mapper_rules: {->(user){ user.logged_in? } => SoyMapper}} }\n\n      it 'should use the mapping' do\n        expect(policy.derive_mapper_from_object(user)).to be SoyMapper\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/default_policy_spec.rb",
    "content": "RSpec.describe Yaks::DefaultPolicy do\n  subject(:policy) { described_class.new(options) }\n\n  let(:options) { {} }\n  let(:association) { Yaks::Mapper::HasMany.create('shoes') }\n\n  describe '#initialize' do\n    it 'should work without arguments' do\n      expect(described_class.new.options).to eql described_class::DEFAULTS\n    end\n\n    let(:options) { {foo: :bar} }\n\n    it 'should merge default and given options' do\n      expect(policy.options.values_at(:namespace, :foo)).to eql [Object, :bar]\n    end\n  end\n\n  describe '#derive_type_from_mapper_class' do\n    specify do\n      expect(\n        policy.derive_type_from_mapper_class(Namespace::RyeMapper)\n      ).to eql 'rye'\n    end\n  end\n\n  describe '#derive_type_from_collection' do\n    specify do\n      expect(\n        policy.derive_type_from_collection([Soy.new])\n      ).to eql 'soy'\n    end\n\n    specify do\n      expect(\n        policy.derive_type_from_collection([])\n      ).to be_nil\n    end\n  end\n\n  describe '#derive_mapper_from_association' do\n    let(:options) { {namespace: Namespace} }\n\n    it 'should derive using the singular association name, and look inside the namespace' do\n      expect(policy.derive_mapper_from_association(association)).to be Namespace::ShoeMapper\n    end\n  end\n\n  describe '#derive_rel_from_association' do\n    it 'should expand the rel based on the association name' do\n      expect(policy.derive_rel_from_association(association)).to eql 'rel:shoes'\n    end\n  end\n\n  describe '#expand_rel' do\n    let(:options) { {rel_template: 'http://foo/{?rel}'} }\n    it 'should expand the given template' do\n      expect(policy.expand_rel('rockets')).to eql 'http://foo/?rel=rockets'\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/format/collection_json_spec.rb",
    "content": "RSpec.describe Yaks::Format::CollectionJson do\n  context 'with the plant collection resource' do\n    include_context 'plant collection resource'\n\n    subject { Yaks::Primitivize.create.call(described_class.new.call(resource)) }\n\n    it { should deep_eql(load_json_fixture('plant_collection.collection')) }\n  end\n\n  describe '#links?' do\n    context 'when resource is not a collection' do\n      let(:resource) {\n        Yaks::Resource.new(\n          attributes: {foo: 'fooval', bar: 'barval'},\n          links: [Yaks::Resource::Link.new(rel: 'the_rel', uri: 'the_uri')]\n        )\n      }\n\n      let(:cj) { Yaks::Format::CollectionJson.new(resource) }\n\n      it 'should return false' do\n        expect(cj.links?(resource)).to eq false\n      end\n    end\n\n    context 'when resource is a collection' do\n      let(:cj) { Yaks::Format::CollectionJson.new(resource) }\n\n      context 'and has links' do\n        let(:resource) {\n          Yaks::CollectionResource.new(\n            links: [Yaks::Resource::Link.new(rel: 'the_rel', uri: 'the_uri')]\n          )\n        }\n\n        it 'should return true' do\n          expect(cj.links?(resource)).to eq true\n        end\n      end\n\n      context 'and has no links' do\n        let(:resource) {\n          Yaks::CollectionResource.new(\n            links: []\n          )\n        }\n\n        it 'should return false' do\n          expect(cj.links?(resource)).to eq false\n        end\n      end\n    end\n  end\n\n  describe '#queries?' do\n    let(:resource) {\n      Yaks::Resource.new(\n        attributes: {foo: 'fooval', bar: 'barval'},\n        forms: [Yaks::Resource::Form.new(full_args)]\n      )\n    }\n\n    subject {\n      Yaks::Primitivize.create.call(described_class.new.call(resource))\n    }\n\n    context 'when resource has GET forms' do\n      context 'and form has an action' do\n        let(:full_args) {\n          {\n            name: :search,\n            method: 'GET',\n            action: '/foo'\n          }\n        }\n\n        it 'should return true' do\n          cj = Yaks::Format::CollectionJson.new(resource)\n          expect(cj.queries?(resource)).to eq true\n        end\n      end\n\n      context 'and form has no action' do\n        let(:full_args) {\n          {\n            name: :search,\n            method: 'GET'\n          }\n        }\n\n        it 'should return false' do\n          cj = Yaks::Format::CollectionJson.new(resource)\n          expect(cj.queries?(resource)).to eq false\n        end\n      end\n\n      context 'and form has no method' do\n        let(:full_args) {\n          {\n            name: :search,\n            action: '/foo'\n          }\n        }\n\n        it 'should return false' do\n          cj = Yaks::Format::CollectionJson.new(resource)\n          expect(cj.queries?(resource)).to eq false\n        end\n      end\n    end\n\n    context 'when resource has not GET forms' do\n      let(:full_args) {\n        {\n          name: :search,\n          method: 'POST'\n        }\n      }\n\n      it 'should return false' do\n        cj = Yaks::Format::CollectionJson.new(resource)\n        expect(cj.queries?(resource)).to eq false\n      end\n    end\n  end\n\n  describe '#template?' do\n    context 'when no template form has been specified' do\n      let(:format) {\n        described_class.new\n      }\n\n      let(:resource) {\n        Yaks::Resource.new(\n          attributes: {foo: 'fooval', bar: 'barval'},\n          forms: [Yaks::Resource::Form.new(name: :just_a_form)]\n        )\n      }\n\n      it 'should return false' do\n        expect(format.template?(resource)).to eq false\n      end\n    end\n\n    context 'when a template form has been specified' do\n      let(:format) {\n        described_class.new(template: :template_form_name)\n      }\n\n      context 'and the form is not present' do\n        let(:resource) {\n          Yaks::Resource.new(\n            attributes: {foo: 'fooval', bar: 'barval'},\n            forms: [Yaks::Resource::Form.new(name: :not_the_form_name)]\n          )\n        }\n\n        subject {\n          Yaks::Primitivize.create.call(format.call(resource))\n        }\n\n        it 'should return false' do\n          expect(format.template?(resource)).to eq false\n        end\n      end\n\n      context 'and the form is present' do\n        let(:resource) {\n          Yaks::Resource.new(\n            attributes: {foo: 'fooval', bar: 'barval'},\n            forms: [Yaks::Resource::Form.new(name: :template_form_name)]\n          )\n        }\n\n        subject {\n          Yaks::Primitivize.create.call(format.call(resource))\n        }\n\n        it 'should return true' do\n          expect(format.template?(resource)).to eq true\n        end\n      end\n    end\n  end\n\n  describe '#serialize_links' do\n    context 'without title' do\n      let(:resource) {\n        Yaks::Resource.new(\n          attributes: {foo: 'fooval', bar: 'barval'},\n          links: [Yaks::Resource::Link.new(rel: 'the_rel', uri: 'the_uri')]\n        )\n      }\n\n      subject {\n        Yaks::Primitivize.create.call(described_class.new.call(resource))\n      }\n\n      it 'should not render a name' do\n        should deep_eql(\n          \"collection\" => {\n            \"version\" => \"1.0\",\n            \"items\" => [\n              {\n                \"data\" => [\n                  {\"name\" => \"foo\", \"value\" => \"fooval\"},\n                  {\"name\" => \"bar\", \"value\" => \"barval\"}\n                ],\n                \"links\" => [{\"rel\" => \"the_rel\", \"href\" => \"the_uri\"}]\n              }\n            ]\n          }\n        )\n      end\n    end\n\n    context 'with a title' do\n      let(:resource) {\n        Yaks::Resource.new(\n          attributes: {foo: 'fooval', bar: 'barval'},\n          links: [Yaks::Resource::Link.new(options: {title: 'the_name'}, rel: 'the_rel', uri: 'the_uri')]\n        )\n      }\n\n      subject {\n        Yaks::Primitivize.create.call(described_class.new.call(resource))\n      }\n\n      it 'should render a name' do\n        should deep_eql(\n          \"collection\" => {\n            \"version\" => \"1.0\",\n            \"items\" => [\n              {\n                \"data\" => [\n                  {\"name\" => \"foo\", \"value\" => \"fooval\"},\n                  {\"name\" => \"bar\", \"value\" => \"barval\"}\n                ],\n                \"links\" => [{\"name\" => \"the_name\", \"rel\" => \"the_rel\", \"href\" => \"the_uri\"}]\n              }\n            ]\n          }\n        )\n      end\n    end\n  end\n\n  describe '#serialize_queries' do\n    let(:resource) {\n      Yaks::Resource.new(\n        attributes: {foo: 'fooval', bar: 'barval'},\n        forms: [\n          Yaks::Resource::Form.new(full_args),\n          Yaks::Resource::Form.new(name: :no_render, action: '/foo', method: 'POST')\n        ]\n      )\n    }\n\n    subject {\n      Yaks::Primitivize.create.call(described_class.new.call(resource))\n    }\n\n    context 'when form method is GET' do\n      context \"form uses only required fields\" do\n        let(:full_args) {\n          {\n            name: :search,\n            action: '/foo',\n            method: 'GET'\n          }\n        }\n\n        it 'should render the queries array' do\n          should deep_eql(\n            \"collection\" => {\n              \"version\" => \"1.0\",\n              \"items\" => [\n                {\n                  \"data\" => [\n                    {\"name\" => \"foo\", \"value\" => \"fooval\"},\n                    {\"name\" => \"bar\", \"value\" => \"barval\"}\n                  ]\n                }\n              ],\n              \"queries\" => [\n                {\"href\" => \"/foo\", \"rel\" => \"search\"}\n              ]\n            }\n          )\n        end\n      end\n\n      context \"form uses optional fields\" do\n        let(:fields) {\n          [\n            Yaks::Resource::Form::Field.new(name: 'foo'),\n            Yaks::Resource::Form::Field.new(name: 'bar', label: 'My Bar Field')\n          ]\n        }\n\n        let(:full_args) {\n          {\n            name: :search,\n            action: '/foo',\n            method: 'GET',\n            title: 'My query prompt',\n            fields: fields\n          }\n        }\n\n        it 'should render the queries array with optional fields' do\n          should deep_eql(\n            \"collection\" => {\n              \"version\" => \"1.0\",\n              \"items\" => [\n                {\n                  \"data\" => [\n                    {\"name\" => \"foo\", \"value\" => \"fooval\"},\n                    {\"name\" => \"bar\", \"value\" => \"barval\"}\n                  ]\n                }\n              ],\n              \"queries\" => [\n                {\"href\" => \"/foo\", \"rel\" => \"search\", \"prompt\" => \"My query prompt\",\n                 \"data\" => [\n                    {\"name\" => \"foo\", \"value\" => \"\"},\n                    {\"name\" => \"bar\", \"value\" => \"\", \"prompt\" => \"My Bar Field\"}\n                  ]\n                }\n              ]\n            }\n          )\n        end\n      end\n    end\n  end\n\n  describe '#serialize_template' do\n    let(:format) {\n      described_class.new(template: :form_for_new)\n    }\n\n    let(:resource) {\n      Yaks::Resource.new(\n        attributes: {foo: 'fooval', bar: 'barval'},\n        forms: [Yaks::Resource::Form.new(name: :form_for_new, fields: fields)]\n      )\n    }\n\n    subject {\n      Yaks::Primitivize.create.call(format.call(resource))\n    }\n\n    context \"template uses prompts\" do\n      let(:fields) {\n        [\n          Yaks::Resource::Form::Field.new(name: 'foo', label: 'My Foo Field'),\n          Yaks::Resource::Form::Field.new(name: 'bar', label: 'My Bar Field')\n        ]\n      }\n\n      it 'should render a template' do\n        should deep_eql(\n          \"collection\" => {\n            \"version\" => \"1.0\",\n            \"items\" => [\n              {\n                \"data\" => [\n                  {\"name\" => \"foo\", \"value\" => \"fooval\"},\n                  {\"name\" => \"bar\", \"value\" => \"barval\"}\n                ]\n              }\n            ],\n            \"template\" => {\n              \"data\" => [\n                {\"name\" => \"foo\", \"value\" => \"\", \"prompt\" => \"My Foo Field\"},\n                {\"name\" => \"bar\", \"value\" => \"\", \"prompt\" => \"My Bar Field\"}\n              ]\n            }\n          }\n        )\n      end\n    end\n\n    context \"template does not use prompts\" do\n      let(:fields) {\n        [\n          Yaks::Resource::Form::Field.new(name: 'foo'),\n          Yaks::Resource::Form::Field.new(name: 'bar')\n        ]\n      }\n\n      it 'should render a template without prompts' do\n        should deep_eql(\n          \"collection\" => {\n            \"version\" => \"1.0\",\n            \"items\" => [\n              {\n                \"data\" => [\n                  {\"name\" => \"foo\", \"value\" => \"fooval\"},\n                  {\"name\" => \"bar\", \"value\" => \"barval\"}\n                ]\n              }\n            ],\n            \"template\" => {\n              \"data\" => [\n                {\"name\" => \"foo\", \"value\" => \"\"},\n                {\"name\" => \"bar\", \"value\" => \"\"}\n              ]\n            }\n          }\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/format/hal_spec.rb",
    "content": "RSpec.describe Yaks::Format::Hal do\n  context 'with the plant collection resource' do\n    include_context 'plant collection resource'\n\n    subject { Yaks::Primitivize.create.call(described_class.new.call(resource)) }\n\n    it { should deep_eql(load_json_fixture('plant_collection.hal')) }\n  end\n\n  context 'with multiple links on the same rel' do\n    let(:format) {\n      described_class.new(plural_links: 'my_plural_rel')\n    }\n\n    let(:resource) {\n      Yaks::Resource.new(\n        attributes: {foo: 'fooval', bar: 'barval'},\n        links: [\n          Yaks::Resource::Link.new(rel: 'my_plural_rel', uri: 'the_uri1'),\n          Yaks::Resource::Link.new(rel: 'my_plural_rel', uri: 'the_uri2')\n        ]\n      )\n    }\n\n    subject {\n      Yaks::Primitivize.create.call(format.call(resource))\n    }\n\n    it 'should render both links' do\n      should deep_eql(\n        'foo' => 'fooval',\n        'bar' => 'barval',\n        '_links' => {\n          \"my_plural_rel\" => [\n            {\"href\" => \"the_uri1\"},\n            {\"href\" => \"the_uri2\"}\n          ]\n        }\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/format/halo_spec.rb",
    "content": "RSpec.describe Yaks::Format::Halo do\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/format/html_spec.rb",
    "content": "# RSpec.describe Yaks::Format::HTML do\n\n# end\n"
  },
  {
    "path": "yaks/spec/unit/yaks/format/json_api_spec.rb",
    "content": "# Mainly tested through the acceptance tests, here covering a few specific edge cases\nRSpec.describe Yaks::Format::JsonAPI do\n  let(:format) { Yaks::Format::JsonAPI.new }\n\n  context 'with no subresources' do\n    let(:resource) { Yaks::Resource.new(type: 'wizard', attributes: {foo: :bar}) }\n\n    it 'should not include an \"included\" key' do\n      expect(format.call(resource)).to eql(\n        data: {type: 'wizards', attributes: {foo: :bar}}\n      )\n    end\n  end\n\n  context 'collection with metadata' do\n    let(:resource) {\n      Yaks::CollectionResource.new(\n        type: 'wizard',\n        members: [Yaks::Resource.new(type: 'wizard', attributes: {foo: :bar})],\n        attributes: {meta: {page: {limit: 20, offset: 0, count: 25}}}\n      )\n    }\n\n    it 'should include the \"meta\" key' do\n      expect(format.call(resource)).to eql(\n        meta: {page: {limit: 20, offset: 0, count: 25}},\n        data: [{type: 'wizards', attributes: {foo: :bar}}]\n      )\n    end\n  end\n\n  context 'with links and subresources' do\n    let(:resource) {\n      Yaks::Resource.new(\n        type: 'wizard',\n        subresources: [\n          Yaks::Resource.new(rels: ['rel:favourite_spell'], type: 'spell', attributes: {id: 1}),\n        ],\n        links: [\n          Yaks::Resource::Link.new(rel: :self, uri: '/the/self/link'),\n          Yaks::Resource::Link.new(rel: :profile, uri: '/the/profile/link'),\n        ]\n      )\n    }\n\n    it 'should include the links in the \"links\" key' do\n      expect(format.call(resource)).to eql(\n        data: {\n          type: 'wizards',\n          relationships: {\n            favourite_spell: {data: {type: \"spells\", id: \"1\"}}\n          },\n          links: {\n            self: \"/the/self/link\",\n            profile: \"/the/profile/link\"\n          }\n        },\n        included: [{type: 'spells', id: \"1\"}]\n      )\n    end\n  end\n\n  context 'with subresources' do\n    let(:resource) {\n      Yaks::Resource.new(\n        type: 'wizard',\n        subresources: [\n          Yaks::Resource.new(rels: ['rel:favourite_spell'], type: 'spell', attributes: {id: 777, name: 'Lucky Sevens'})\n        ]\n      )\n    }\n\n    it 'should include subresource links and included' do\n      expect(format.call(resource)).to eql(\n        data: {\n          type: 'wizards',\n          relationships: {favourite_spell: {data: {type: 'spells', id: \"777\"}}}\n        },\n        included: [{type: 'spells', id: \"777\", attributes: {name: 'Lucky Sevens'}}]\n      )\n    end\n  end\n\n  context 'with duplicate subresources' do\n    let(:resource) {\n      Yaks::CollectionResource.new(\n        type: 'wizard',\n        members: [\n          Yaks::Resource.new(type: 'wizard', attributes: {id: 7}, subresources: [\n            Yaks::Resource.new(type: 'spell', attributes: {id: 1}, rels: ['rel:favourite_spell']),\n          ]),\n          Yaks::Resource.new(type: 'wizard', attributes: {id: 3}, subresources: [\n            Yaks::Resource.new(type: 'spell', attributes: {id: 1}, rels: ['rel:favourite_spell']),\n          ]),\n          Yaks::Resource.new(type: 'wizard', attributes: {id: 2}, subresources: [\n            Yaks::Resource.new(type: 'spell', attributes: {id: 12}, rels: ['rel:favourite_spell']),\n          ]),\n          Yaks::Resource.new(type: 'wizard', attributes: {id: 9}, subresources: [\n            Yaks::Resource.new(type: 'wand', attributes: {id: 1}, rels: ['rel:wand']),\n          ]),\n        ],\n      )\n    }\n\n    it 'should include the each subresource only once' do\n      expect(format.call(resource)).to eql(\n        data: [\n          {type: 'wizards', id: '7', relationships: {favourite_spell: {data: {type: 'spells', id: '1'}}}},\n          {type: 'wizards', id: '3', relationships: {favourite_spell: {data: {type: 'spells', id: '1'}}}},\n          {type: 'wizards', id: '2', relationships: {favourite_spell: {data: {type: 'spells', id: '12'}}}},\n          {type: 'wizards', id: '9', relationships: {wand:            {data: {type: 'wands',  id: '1'}}}},\n        ],\n        included: [\n          {type: 'spells', id: '1'},\n          {type: 'spells', id: '12'},\n          {type: 'wands',  id: '1'},\n        ]\n      )\n    end\n  end\n\n  context 'with null subresources' do\n    let(:resource) {\n      Yaks::Resource.new(\n        type: 'wizard',\n        subresources: [subresource]\n      )\n    }\n\n    context \"non-collection subresouce\" do\n      let(:subresource) { Yaks::NullResource.new.add_rel(\"rel:wand\") }\n\n      it 'should include a nil linkage object' do\n        expect(format.call(resource)).to eql(\n          data: {\n            type: 'wizards',\n            relationships: {\n              wand: {data: nil}\n            }\n          }\n        )\n      end\n    end\n\n    context \"collection subresouce\" do\n      let(:subresource) { Yaks::NullResource.new(collection: true).add_rel(\"rel:wands\") }\n\n      it 'should include a nil linkage object' do\n        expect(format.call(resource)).to eql(\n          data: {\n            type: 'wizards',\n            relationships: {\n              wands: {data: []}\n            }\n          }\n        )\n      end\n    end\n  end\n\n  context 'with no subresources or subresource links' do\n    let(:resource) {\n      Yaks::Resource.new(\n        type: 'wizard',\n        subresources: []\n      )\n    }\n\n    it 'should not include subresource links' do\n      expect(format.call(resource)).to eql(\n        data: {type: 'wizards'}\n      )\n    end\n  end\n\n  context 'with links as collection' do\n    let(:resource) {\n      Yaks::CollectionResource.new(\n        type: 'wizard',\n        links: [\n          Yaks::Resource::Link.new(rel: :prev, uri: '/prev/page/link'),\n          Yaks::Resource::Link.new(rel: :next, uri: '/next/page/link'),\n        ]\n      )\n    }\n\n    it 'should include links' do\n      expect(format.call(resource)).to eql(\n        data: [],\n        links: {\n          prev: '/prev/page/link',\n          next: '/next/page/link',\n        }\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/format_spec.rb",
    "content": "RSpec.describe Yaks::Format do\n  describe '.by_name' do\n    specify do\n      expect(Yaks::Format.by_name(:hal)).to eql Yaks::Format::Hal\n    end\n    specify do\n      expect(Yaks::Format.by_name(:json_api)).to eql Yaks::Format::JsonAPI\n    end\n  end\n\n  describe '.by_media_type' do\n    specify do\n      expect(Yaks::Format.by_media_type('application/hal+json')).to eql Yaks::Format::Hal\n    end\n  end\n\n  describe '.by_accept_header' do\n    specify do\n      expect(Yaks::Format.by_accept_header('application/hal+json;q=0.8, application/vnd.api+json')).to eql Yaks::Format::JsonAPI\n    end\n    specify do\n      expect(Yaks::Format.by_accept_header('application/hal+json;q=0.8, application/vnd.api+json;q=0.7')).to eql Yaks::Format::Hal\n    end\n  end\n\n  describe '.media_types' do\n    specify do\n      expect(Yaks::Format.media_types.values_at(:collection_json, :hal, :json_api)).to eql([\"application/vnd.collection+json\", \"application/hal+json\", \"application/vnd.api+json\"])\n    end\n  end\n\n  let(:init_opts) { Hash.new }\n  subject(:format) { Yaks::Format.new(init_opts) }\n\n  describe \"#initialize\" do\n    it 'should set options' do\n      expect(format.send(:options)).to equal init_opts\n    end\n\n    it 'should default to an empty hash' do\n      expect(Yaks::Format.new.send(:options)).to eql({})\n    end\n  end\n\n  describe \"#call\" do\n    it 'should set the environment' do\n      format.call(nil, foo: 1)\n      expect(format.env).to eql(foo: 1)\n    end\n\n    it 'should default to an empty environment' do\n      format.call(:foo)\n      expect(format.env).to eql({})\n    end\n\n    it 'should delegate to #serialize_resource' do\n      stub(format).serialize_resource(:foo) {|_r| :bar}\n      expect(format.call(:foo)).to equal :bar\n    end\n  end\n\n  describe '#serialize_resource' do\n    specify { expect(format.serialize_resource(:foo)).to be_nil }\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/fp/callable_spec.rb",
    "content": "RSpec.describe Yaks::FP::Callable, \"#to_proc\" do\n  it 'should delegate to_proc to method(:call)' do\n    obj = Class.new do\n      include Yaks::FP::Callable\n\n      def call(x)\n        x * x\n      end\n    end.new\n\n    expect([1, 2, 3].map(&obj)).to eql [1, 4, 9]\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/association_mapper_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::AssociationMapper do\n  include_context 'yaks context'\n\n  subject(:association_mapper) {\n    described_class.new(parent_mapper, association, yaks_context)\n  }\n\n  let(:parent_mapper_class) { Yaks::Mapper }\n  let(:parent_mapper) { parent_mapper_class.new(yaks_context) }\n  let(:mapper_stack) { [:bottom_mapper] }\n\n  fake(:association) { Yaks::Mapper::Association }\n\n  describe \"#policy\" do\n    its(:policy) { should be policy }\n  end\n\n  describe \"#initialize\" do\n    its(:parent_mapper) { should equal parent_mapper }\n    its(:association) { should equal association }\n\n    it \"should add the parent mapper to the mapper stack\" do\n      expect(association_mapper.context[:mapper_stack])\n        .to eql [:bottom_mapper, parent_mapper]\n    end\n\n    it \"should set the rel based on the association\" do\n      stub(association).map_rel(policy) { '/association/rel' }\n      expect(association_mapper.rel).to eql '/association/rel'\n    end\n  end\n\n  describe '#call' do\n    context 'when the association should be rendered as link' do\n      before do\n        stub(association).render_as_link?(parent_mapper) { true }\n        stub(association).map_rel(policy) { 'rels:the_rel' }\n        stub(association).href { 'http://this/is_where_the_associated_thing_can_be_found' }\n      end\n\n      it 'should render a link' do\n        expect(association_mapper.call(Yaks::Resource.new)).to eql Yaks::Resource.new(\n          links: [\n            Yaks::Resource::Link.new(\n              rel: 'rels:the_rel',\n              uri: 'http://this/is_where_the_associated_thing_can_be_found'\n            )\n          ]\n        )\n      end\n    end\n\n    context 'when the association should be rendered as a subresource' do\n      before do\n        stub(association).render_as_link?(parent_mapper) { false }\n        stub(association).map_rel(policy) { 'rels:the_rel' }\n        stub(association).name { :the_name }\n        stub(association).map_resource(:the_object, association_mapper.context) { Yaks::Resource.new }\n\n        stub(parent_mapper).load_association(:the_name) { :the_object }\n      end\n\n      it 'should render a subresource' do\n        expect(association_mapper.call(Yaks::Resource.new)).to eql Yaks::Resource.new(\n          subresources: [ Yaks::Resource.new(rels: ['rels:the_rel']) ]\n        )\n      end\n\n      it 'should add the mapper to the mapper_stack' do\n        expect(association_mapper.context[:mapper_stack]).to eql [:bottom_mapper, parent_mapper]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/association_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::Association do\n  include_context 'yaks context'\n\n  let(:association_class) {\n    Class.new(described_class) do\n      def map_resource(_object, _context)\n      end\n    end\n  }\n\n  subject(:association) do\n    association_class.new(\n      name: name,\n      item_mapper: mapper,\n      rel: rel,\n      href: href,\n      link_if: link_if,\n      if: self.if\n    )\n  end\n\n  let(:name)              { :shoes          }\n  let(:mapper)            { Yaks::Mapper    }\n  let(:rel)               { Yaks::Undefined }\n  let(:href)              { Yaks::Undefined }\n  let(:link_if)           { Yaks::Undefined }\n  let(:if)                { Yaks::Undefined }\n\n  its(:name)        { should equal :shoes }\n  its(:item_mapper) { should equal Yaks::Mapper }\n\n  context 'with a minimal constructor' do\n    subject(:association) { association_class.new(name: :foo) }\n\n    its(:name)         { should be :foo }\n    its(:item_mapper)  { should be Yaks::Undefined }\n    its(:rel)          { should be Yaks::Undefined }\n    its(:href)         { should be Yaks::Undefined }\n    its(:link_if)      { should be Yaks::Undefined }\n  end\n\n  let(:parent_mapper_class) { Yaks::Mapper }\n  let(:parent_mapper)       { parent_mapper_class.new(yaks_context) }\n\n  describe '#add_to_resource' do\n    let(:object) { fake(shoes: []) }\n    let(:rel)    { 'rel:shoes' }\n\n    before do\n      parent_mapper.call(object)\n      stub(association).map_resource(any_args) { Yaks::Resource.new }\n    end\n\n    it 'should delegate to AssociationMapper' do\n      expect(association.add_to_resource(Yaks::Resource.new, parent_mapper, yaks_context)).to eql Yaks::Resource.new(subresources: [Yaks::Resource.new(rels: ['rel:shoes'])])\n    end\n\n    context 'with a truthy condition' do\n      let(:if) { ->{ true } }\n\n      it 'should add the association' do\n        expect(association.add_to_resource(Yaks::Resource.new, parent_mapper, yaks_context).subresources.length).to be 1\n      end\n    end\n\n    context 'without a condition' do\n      it 'should add the association' do\n        expect(association.add_to_resource(Yaks::Resource.new, parent_mapper, yaks_context).subresources.length).to be 1\n      end\n    end\n\n    context 'with a falsey condition' do\n      let(:if) { ->{ false } }\n\n      it 'should not add the association' do\n        expect(association.add_to_resource(Yaks::Resource.new, parent_mapper, yaks_context).subresources.length).to be 0\n      end\n    end\n  end\n\n  describe '#render_as_link?' do\n    let(:href)     { '/foo/{bar}/baz' }\n    let(:link_if)  { -> { env.fetch('env_entry') == 123 } }\n    let(:rack_env) { {'env_entry' => 123} }\n\n    let(:render_as_link?) { association.render_as_link?(parent_mapper) }\n\n    context 'when evaluating to true' do\n      it 'should resolve :link_if in the context of the mapper' do\n        expect(render_as_link?).to be true\n      end\n    end\n\n    context 'when evaluating to false' do\n      let(:rack_env) { {'env_entry' => 0} }\n\n      it 'should resolve :link_if in the context of the mapper' do\n        expect(render_as_link?).to be false\n      end\n    end\n\n    context 'with an Undefined href' do\n      let(:href) { Yaks::Undefined }\n\n      it 'should return falsey' do\n        expect(render_as_link?).to be_falsey\n      end\n    end\n\n    context 'with an Undefined link_if' do\n      let(:link_if) { Yaks::Undefined }\n\n      it 'should return falsey' do\n        expect(render_as_link?).to be_falsey\n      end\n    end\n  end\n\n  describe '#map_rel' do\n    let(:association_rel) { association.map_rel(policy) }\n\n    context 'with a rel specified' do\n      let(:rel) { 'http://api.com/rels/shoes' }\n\n      it 'should use the specified rel' do\n        expect(association_rel).to eql 'http://api.com/rels/shoes'\n      end\n    end\n\n    context 'without a rel specified' do\n      before do\n        stub(policy).derive_rel_from_association(association) {\n          'http://api.com/rel/derived'\n        }\n      end\n\n      it 'should infer a rel based on policy' do\n        expect(association_rel).to eql 'http://api.com/rel/derived'\n      end\n    end\n  end\n\n  describe '#resolve_association_mapper' do\n    context 'with a specified mapper' do\n      let(:mapper) { :a_specific_mapper_class }\n\n      it 'should return the mapper' do\n        expect(association.resolve_association_mapper(nil)).to equal :a_specific_mapper_class\n      end\n    end\n\n    context 'with the mapper undefined' do\n      let(:mapper) { Yaks::Undefined }\n\n      before do\n        stub(policy).derive_mapper_from_association(association) {\n          :a_derived_mapper_class\n        }\n      end\n\n      it 'should derive a mapper based on policy' do\n        expect(association.resolve_association_mapper(policy)).to equal :a_derived_mapper_class\n      end\n    end\n  end\n\n  describe '.create' do\n    it 'should take a name' do\n      expect(association_class.create(:foo).name).to be :foo\n    end\n\n    it 'should optionally take a mapper' do\n      expect(association_class.create(:foo, mapper: :bar).item_mapper).to be :bar\n    end\n\n    it 'should take other options' do\n      expect(association_class.create(:foo, mapper: :bar, href: 'xxx').href).to eql 'xxx'\n    end\n\n    it 'should respect attribute defaults' do\n      expect(association_class.create(:foo, href: 'xxx').item_mapper).to be Yaks::Undefined\n    end\n\n    it 'should not munge the options hash' do\n      opts = {mapper: :foo}\n      association_class.create(:foo, opts)\n      expect(opts).to eql(mapper: :foo)\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/attribute_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::Attribute do\n  include_context 'yaks context'\n\n  let(:attribute_with_block) { described_class.create(:the_name) { \"Alice\" } }\n  subject(:attribute) { described_class.create(:the_name) }\n  fake(:mapper)\n\n  before do\n    stub(mapper).load_attribute(:the_name) { 123 }\n    stub(mapper).object { fake(name: \"Bob\") }\n  end\n\n  describe \".create\" do\n    its(:name) { should be :the_name }\n    its(:if) { should be_truthy }\n    its(:block) { should be_nil }\n\n    it \"should accept only the name\" do\n      expect{described_class.create(:the_name)}.not_to raise_error()\n    end\n\n    context \"with block\" do\n      subject(:attribute) { attribute_with_block }\n\n      its(:block) { should_not be_nil }\n\n      it \"should store the given block\" do\n        expect(subject.block.call).to eq(\"Alice\")\n      end\n    end\n\n    describe \"options\" do\n\n      let(:object) { Struct.new(:x, :y, :returns_nil).new(3, 4, nil) }\n\n      let(:mapper_class) do\n        Class.new(Yaks::Mapper) do\n          type 'foo'\n        end\n      end\n\n      let(:mapper) do\n        mapper_class.new(yaks_context).tap do |mapper|\n          mapper.call(object) # set @object\n        end\n      end\n\n      subject(:attribute) { described_class.create(:the_name, options) }\n\n      context 'with :if defined and resolving to false' do\n\n        let(:options) { {if: ->{ false }} }\n\n        it 'should not render the attribute' do\n          expect(attribute.add_to_resource(Yaks::Resource.new, mapper, yaks_context)).not_to eql(\n            Yaks::Resource.new(attributes: {the_name: 123})\n          )\n        end\n      end\n\n      context 'with :if defined and resolving to true' do\n        let(:options) { {if: ->{ true }} }\n\n        it 'should render the attribute' do\n          expect(attribute.add_to_resource(Yaks::Resource.new, mapper, yaks_context)).to eql(\n            Yaks::Resource.new(attributes: {the_name: 123})\n          )\n        end\n      end\n\n      context 'with :if defined and resolving to true via instance_eval' do\n        let(:options) { {if: ->{ object.name.length > 2 } } }\n\n        it 'should render the attribute' do\n          expect(attribute.add_to_resource(Yaks::Resource.new, mapper, yaks_context)).to eql(\n            Yaks::Resource.new(attributes: {the_name: 123})\n          )\n        end\n      end\n    end\n\n  end\n\n  describe \"#add_to_resource\" do\n\n    let(:options) { {if: ->{ true }} }\n    let(:options_false) { {if: ->{ 0 == 1 }} }\n    let(:attribute_with_block_and_false_options) {described_class.create(:the_name,options_false) { \"Alice\" } }\n    let(:attribute_with_block_and_options) { described_class.create(:the_name,options) { \"Alice\" } }\n\n\n\n    it \"should add itself to a resource based on a lookup\" do\n      expect(attribute.add_to_resource(Yaks::Resource.new, mapper, yaks_context))\n        .to eql(Yaks::Resource.new(attributes: {the_name: 123}))\n    end\n\n    context \"when the attribute has a block and true options\" do\n      subject(:attribute) { attribute_with_block_and_options }\n\n      it \"should add itself to a resource with the block value\" do\n        expect(attribute.add_to_resource(Yaks::Resource.new, mapper, yaks_context))\n          .to eql(Yaks::Resource.new(attributes: {the_name: \"Alice\"}))\n      end\n\n      \n    end\n\n    context \"when the attribute has a block and false options\" do\n      subject(:attribute) { attribute_with_block_and_false_options }\n\n      it \"should not add itself to a resource with the block value\" do\n        expect(attribute.add_to_resource(Yaks::Resource.new, mapper, yaks_context))\n          .not_to eql(Yaks::Resource.new(attributes: {the_name: \"Alice\"}))\n      end\n\n      \n    end\n\n    context \"when the attribute has a block\" do\n      subject(:attribute) { attribute_with_block }\n\n      it \"should add itself to a resource with the block value\" do\n        expect(attribute.add_to_resource(Yaks::Resource.new, mapper, yaks_context))\n          .to eql(Yaks::Resource.new(attributes: {the_name: \"Alice\"}))\n      end\n\n      context \"using the mapper context\" do\n        let(:attribute) { described_class.create(:the_name) { object.name } }\n\n        it \"should add itself to a resource with the block value\" do\n          expect(attribute.add_to_resource(Yaks::Resource.new, mapper, yaks_context))\n            .to eql(Yaks::Resource.new(attributes: {the_name: \"Bob\"}))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/config_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::Config do\n  describe '#add_attributes' do\n    it 'should add attributes' do\n      expect(subject.add_attributes(:bar).add_attributes(:baz)).to eql described_class.new(\n        attributes: [\n          Yaks::Mapper::Attribute.create(:bar),\n          Yaks::Mapper::Attribute.create(:baz)\n        ]\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form/config_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::Form::Config do\n  let(:config) { described_class.new }\n\n  describe \".create\" do\n    let(:config) {\n      described_class.create name: :bar, method: \"POST\"\n    }\n\n    it \"should take an attribute hash\" do\n      expect(config)\n        .to eql Yaks::Mapper::Form::Config.new(\n          name: :bar,\n          method: \"POST\"\n        )\n    end\n  end\n\n  describe \".build\" do\n    context \"with no arguments\" do\n      it \"should create a default Form::Config\" do\n        expect(described_class.build).to eql described_class.new\n      end\n    end\n\n    context \"with a block\" do\n      let(:config) {\n        described_class.build name: :bar do\n          method \"DELETE\"\n          password :secret\n        end\n      }\n\n      it \"should evaluate as Form DSL\" do\n        expect(config)\n          .to eql Yaks::Mapper::Form::Config.new(\n            name: :bar,\n            method: \"DELETE\",\n            fields: [\n              Yaks::Mapper::Form::Field.new(name: :secret, type: :password)\n            ]\n          )\n      end\n    end\n  end\n\n  describe \".build_with_object\" do\n    let(:config) {\n      described_class.build_with_object \"the_object\" do |obj|\n        title obj\n      end\n    }\n\n    it \"should pass the object to the config block\" do\n      expect(config)\n        .to eql Yaks::Mapper::Form::Config.new(\n          title: \"the_object\"\n        )\n    end\n  end\n\n  describe \"#condition\" do\n    it \"should work with a lambda\" do\n      expect(config.condition(->{ :okay }).if.call).to equal :okay\n    end\n\n    it \"should work with a block\" do\n      expect(config.condition { :okay }.if.call).to equal :okay\n    end\n  end\n\n  describe \"#to_resource_fields\" do\n    include_context 'yaks context'\n    let(:mapper) { Yaks::Mapper.new(yaks_context) }\n    let(:config) {\n      described_class.build do\n        fieldset do\n          text :first_name\n          text :last_name\n        end\n      end\n    }\n\n    it \"should map to resource fields\" do\n      expect(config.to_resource_fields(mapper))\n        .to eql [\n          Yaks::Resource::Form::Fieldset.new(\n            fields: [\n              Yaks::Resource::Form::Field.new(name: :first_name, type: :text),\n              Yaks::Resource::Form::Field.new(name: :last_name, type: :text)\n            ]\n          )\n        ]\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form/dynamic_field_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::Form::DynamicField do\n  describe \".create\" do\n    it \"take a block\" do\n      expect(described_class.create { :foo }.block.call).to equal :foo\n    end\n\n    it \"should ignore any options hash given\" do\n      expect { described_class.create(foo: :bar) }.to_not raise_error\n    end\n  end\n\n  describe \"#to_resource_fields\" do\n    include_context \"yaks context\"\n    let(:mapper) { Yaks::Mapper.new(yaks_context) }\n    let(:field) {\n      described_class.create do |obj|\n        text :first_name, value: obj\n        text :last_name\n      end\n    }\n\n    it \"should return an array of fields\" do\n      mapper.call(\"Arne\")\n      expect(field.to_resource_fields(mapper)).to eql [\n        Yaks::Resource::Form::Field.new(name: :first_name, type: :text, value: \"Arne\"),\n        Yaks::Resource::Form::Field.new(name: :last_name, type: :text)\n      ]\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form/field/option_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::Form::Field::Option do\n  include_context \"yaks context\"\n\n  let(:mapper_class) do\n    Class.new(Yaks::Mapper) do\n      def color\n        :yellow\n      end\n    end\n  end\n\n  let(:mapper) { mapper_class.new(yaks_context) }\n  let(:args) {\n    {\n      value: ->{ color },\n      label: ->{ :color },\n      selected: ->{ true },\n      disabled: ->{ true }\n    }\n  }\n\n  let(:option) { described_class.new(args) }\n\n  describe \".create\" do\n    it \"should take the first argument as 'value'\" do\n      expect(described_class.create(0, label: 'zero'))\n        .to eql Yaks::Mapper::Form::Field::Option.new(\n          value: 0,\n          label: 'zero'\n        )\n    end\n  end\n\n  describe \"#to_resource_field_option\" do\n    let(:resource_field_option) { option.to_resource_field_option(mapper) }\n\n    it \"should expand procs in the context of the mapper\" do\n      expect(resource_field_option)\n        .to eql Yaks::Resource::Form::Field::Option.new(\n          value: :yellow,\n          label: :color,\n          selected: true,\n          disabled: true\n        )\n    end\n\n    context \"with a truthy condition\" do\n      let(:args) { super().merge(if: ->{ true }) }\n\n      it \"should return an Option instance\" do\n        expect(resource_field_option).to be_a Yaks::Resource::Form::Field::Option\n      end\n    end\n\n    context \"with a falsey condition\" do\n      let(:args) { super().merge(if: ->{ false }) }\n\n      it \"should return nil\" do\n        expect(resource_field_option).to be_nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form/field_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::Form::Field do\n  include_context 'yaks context'\n\n  let(:field)     { described_class.new(full_args) }\n  let(:name)      { :the_field }\n  let(:full_args) { {name: name, options: options}.merge(args) }\n  let(:options)   { [] }\n  let(:args) {\n    {\n      label: 'a label',\n      type: 'text',\n      value: 'hello'\n    }\n  }\n\n  let(:mapper) do\n    Class.new(Yaks::Mapper) do\n      def month\n        'January'\n      end\n    end.new(yaks_context)\n  end\n\n  describe '.create' do\n    it 'can take all args as a hash' do\n      expect(described_class.create(full_args)).to eql described_class.new(full_args)\n    end\n\n    it 'can take a name as a positional arg' do\n      expect(described_class.create(name, args)).to eql described_class.new(full_args)\n    end\n\n    it 'can take only a name' do\n      expect(described_class.create(name)).to eql described_class.new(name: :the_field)\n    end\n  end\n\n  describe '#to_resource_fields' do\n    it 'creates a Yaks::Resource::Form::Field with the same attributes' do\n      expect(field.to_resource_fields(mapper)).to eql [Yaks::Resource::Form::Field.new(full_args)]\n    end\n\n    context 'with dynamic attributes' do\n      let(:name) { ->{ month } }\n\n      it 'should expand attributes using the mapper' do\n        expect(field.to_resource_fields(mapper).first.name).to eql 'January'\n      end\n    end\n\n    context 'with a falsey if condition' do\n      let(:args) { super().merge(if: ->{ false })}\n      it 'returns an empty array' do\n        expect(field.to_resource_fields(mapper)).to eql []\n      end\n    end\n\n    context 'with a truthy if condition' do\n      let(:args) { super().merge(if: ->{ true })}\n      it 'returns a field' do\n        expect(field.to_resource_fields(mapper).first).to be_a Yaks::Resource::Form::Field\n      end\n    end\n\n    context 'with select optons' do\n      let(:options) {\n        [\n          Yaks::Mapper::Form::Field::Option.new(\n            label: 'Jan',\n            value: ->{ 'January' },\n            selected: ->{ month == 'January' }\n          ),\n          Yaks::Mapper::Form::Field::Option.new(\n            label: 'Feb',\n            value: ->{ 'February' },\n            selected: ->{ month == 'February' }\n          )\n        ]\n      }\n\n      it 'should convert them to Yaks::Form* objects' do\n        form_field = Yaks::Resource::Form::Field.new(\n          name: :the_field,\n          label: \"a label\",\n          options: [\n            Yaks::Resource::Form::Field::Option.new(value: \"January\", label: \"Jan\", selected: true),\n            Yaks::Resource::Form::Field::Option.new(value: \"February\", label: \"Feb\")\n          ],\n          type: \"text\",\n          value: \"hello\"\n        )\n        expect(field.to_resource_fields(mapper)).to eql [form_field]\n      end\n    end\n  end\n\n  describe \"#resource_options\" do\n    context \"when empty\" do\n      it \"should always be the same identical object\" do\n        opt1 = described_class.new(name: :foo).resource_options(mapper)\n        opt2 = described_class.new(name: :bar).resource_options(mapper)\n        expect(opt1).to eql []\n        expect(opt1).to equal opt2\n      end\n    end\n\n    context \"with select options\" do\n      let(:options) do\n        [\n          Yaks::Mapper::Form::Field::Option.new(value: 0, label: \"zero\"),\n          Yaks::Mapper::Form::Field::Option.new(value: 1, label: \"one\"),\n          Yaks::Mapper::Form::Field::Option.new(value: 2, label: \"two\", if: ->{ false })\n        ]\n      end\n\n      it \"should map to Resource::Field::Option instances\" do\n        expect(field.resource_options(mapper))\n          .to eql [\n            Yaks::Resource::Form::Field::Option.new(value: 0, label: \"zero\"),\n            Yaks::Resource::Form::Field::Option.new(value: 1, label: \"one\")\n          ]\n      end\n    end\n  end\n\n  describe \"#resource_attributes\" do\n    it \"should have all the HTML form field attributes\" do\n      expect(field.resource_attributes).to eql [\n        :name, :label, :type, :required, :rows, :value, :pattern,\n        :maxlength, :minlength, :size, :readonly, :multiple, :min,\n        :max, :step, :list, :placeholder, :checked, :disabled\n      ]\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form/fieldset_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::Form::Fieldset do\n  include_context 'yaks context'\n  let(:mapper) { Yaks::Mapper.new(yaks_context) }\n\n  describe \".create\" do\n    let(:fieldset) {\n      described_class.create do\n        text :first_name\n        text :last_name\n      end\n    }\n\n    it \"should take a config block\" do\n      expect(fieldset.config)\n        .to eql Yaks::Mapper::Form::Config.new(\n          fields: [\n            Yaks::Mapper::Form::Field.new(name: :first_name, type: :text),\n            Yaks::Mapper::Form::Field.new(name: :last_name, type: :text)\n          ]\n        )\n    end\n\n    context \"with extra options\" do\n      let(:fieldset) {\n        described_class.create if: true do\n          text :first_name\n        end\n      }\n\n      it \"should take an :if option (the rest doesn't make sense for a fieldset)\" do\n        expect(fieldset.config)\n          .to eql Yaks::Mapper::Form::Config.new(\n            fields: [\n              Yaks::Mapper::Form::Field.new(name: :first_name, type: :text)\n            ],\n            if: true\n          )\n      end\n    end\n  end\n\n  describe \"#to_resource_fields\" do\n    context \"with dynamic elements\" do\n      let(:fieldset) {\n        described_class.create do\n          dynamic do |object|\n            text object.name\n          end\n        end\n      }\n\n      it \"should render them based on the mapped object\" do\n        mapper.call(fake(name: :anthony)) # HACK: set the mapper's object\n        expect(fieldset.to_resource_fields(mapper)).to eql(\n          [\n            Yaks::Resource::Form::Fieldset.new(\n              fields: [\n                Yaks::Resource::Form::Field.new(name: :anthony, type: :text)\n              ]\n            )\n          ]\n        )\n      end\n    end\n\n    context \"with a truthy `:if` condition\" do\n      let(:fieldset) {\n        described_class.create if: ->{ true } do\n        end\n      }\n\n      it \"should return an array of fieldsets\" do\n        expect(fieldset.to_resource_fields(mapper).first)\n          .to be_a Yaks::Resource::Form::Fieldset\n      end\n    end\n\n    context \"with a falsey `:if` condition\" do\n      let(:fieldset) {\n        described_class.create if: ->{ false } do\n        end\n      }\n\n      it \"should return nil\" do\n        expect(fieldset.to_resource_fields(mapper).first).to be_nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form/legend_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::Form::Legend do\n  subject(:legend) { described_class.create(\"a legend\") }\n\n  describe \".create\" do\n    its(:type) { should equal :legend }\n    its(:label) { should eql \"a legend\" }\n\n    context \"with an `:if` option\" do\n      subject(:legend) { described_class.create(\"a legend\", if: ->{ true }) }\n\n      it \"should set the attribute\" do\n        expect(legend.if.call).to be true\n      end\n    end\n  end\n\n  describe \"#to_resource_fields\" do\n    include_context \"yaks context\"\n    let(:mapper) { Yaks::Mapper.new(yaks_context) }\n\n    it \"should return an array of Resource::Form::Legend\" do\n      expect(legend.to_resource_fields(mapper))\n        .to eql [Yaks::Resource::Form::Legend.new(label: \"a legend\", type: :legend)]\n    end\n\n    context \"with a truthy condition\" do\n      subject(:legend) { described_class.create(\"a legend\", if: ->{ true }) }\n\n      it \"should return an array of Resource::Form::Legend\" do\n        expect(legend.to_resource_fields(mapper).first)\n          .to be_a Yaks::Resource::Form::Legend\n      end\n    end\n\n    context \"with a falsey condition\" do\n      subject(:legend) { described_class.create(\"a legend\", if: ->{ false }) }\n\n      it \"should return an empty array\" do\n        expect(legend.to_resource_fields(mapper)).to eql []\n      end\n    end\n\n    context \"with a lambda for a label\" do\n      subject(:legend) { described_class.create(->{ self.class.to_s }) }\n\n      it \"should expand the lambda in the context of the mapper\" do\n        expect(legend.to_resource_fields(mapper))\n          .to eql [Yaks::Resource::Form::Legend.new(label: \"Yaks::Mapper\", type: :legend)]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::Form do\n  include_context 'yaks context'\n\n  let(:form) { described_class.create(*full_args, &block_arg) }\n  let(:name) { :the_name }\n  let(:full_args) { [{name: name}.merge(args)] }\n  let(:block_arg) { nil }\n  let(:args) {\n    {\n      action: '/foo',\n      title: 'a title',\n      method: 'PATCH',\n      media_type: 'application/hal+json',\n      fields: fields\n    }\n  }\n  let(:fields) { [] }\n  let(:mapper) { Yaks::Mapper.new(yaks_context) }\n\n  describe \".create\" do\n    it \"should have a name of nil when ommitted\" do\n      expect(described_class.create.name).to be_nil\n    end\n\n    context \"with a symbol arg\" do\n      let(:full_args) { [:the_name] }\n      it \"should use the symbol as the form's name\" do\n        expect(form.name).to be :the_name\n      end\n    end\n\n    context \"with a name given in the options hash\" do\n      it \"should use that name\" do\n        expect(form.name).to be :the_name\n      end\n    end\n\n    context \"with a block\" do\n      let(:block_arg) { ->{ method 'POST' } }\n\n      it \"should use it as configuration block\" do\n        expect(form.method).to eql 'POST'\n      end\n    end\n  end\n\n  describe '#add_to_resource' do\n    let(:resource) { form.new.add_to_resource(Yaks::Resource.new, mapper, nil) }\n\n    context 'with fields' do\n      let(:fields) {\n        [\n          Yaks::Mapper::Form::Field.new(\n            name: 'field name',\n            label: 'field label',\n            type: 'text',\n            value: 7\n          )\n        ]\n      }\n    end\n\n    it \"should add the form to the resource\" do\n      expect(form.add_to_resource(Yaks::Resource.new, mapper, nil))\n        .to eql Yaks::Resource.new(\n          forms: [\n            Yaks::Resource::Form.new(\n              name: :the_name,\n              action: \"/foo\",\n              title: \"a title\",\n              method: \"PATCH\",\n              media_type: \"application/hal+json\",\n              fields: []\n            )\n          ]\n        )\n    end\n\n    context 'with a truthy condition' do\n      let(:form) { described_class.create { condition { true }}}\n\n      it 'should add the form' do\n        expect(form.add_to_resource(Yaks::Resource.new, mapper, nil).forms.length).to be 1\n      end\n    end\n\n    context 'with a falsey condition' do\n      let(:form) { described_class.create { condition { false }}}\n\n      it 'should not add the form' do\n        expect(form.add_to_resource(Yaks::Resource.new, mapper, nil).forms.length).to be 0\n      end\n    end\n  end\n\n  describe '#to_resource_form' do\n    let(:block_arg) do\n      -> do\n        method { 'POST' }\n        action { '/foo/bar' }\n\n        text :name, required: true\n      end\n    end\n\n    it \"should create a matching Yaks::Resource::Form\" do\n      expect(form.to_resource_form(mapper))\n        .to eql Yaks::Resource::Form.new(\n          name: :the_name,\n          action: \"/foo/bar\",\n          title: \"a title\",\n          method: \"POST\",\n          media_type: \"application/hal+json\",\n          fields: [\n            Yaks::Resource::Form::Field.new(name: :name, type: :text, required: true)\n          ]\n        )\n    end\n\n    context 'with dynamic elements' do\n      let(:args) {{}}\n      let(:block_arg) do\n        -> do\n          dynamic do |object|\n            text object.name\n          end\n        end\n      end\n\n      it 'should render them based on the mapped object' do\n        mapper.call(fake(name: :anthony)) # HACK: set the mapper's object\n        expect(form.to_resource_form(mapper)).to eql(\n          Yaks::Resource::Form.new(\n            name: :the_name,\n            fields: [\n              Yaks::Resource::Form::Field.new(name: :anthony, type: :text)\n            ]\n          )\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/has_many_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::HasMany do\n  include_context 'yaks context'\n\n  let(:closet_mapper) { closet_mapper_class.new(yaks_context) }\n\n  let(:closet_mapper_class) do\n    Class.new(Yaks::Mapper) do\n      type 'closet'\n      has_many :shoes,\n        rel: 'http://foo/shoes',\n        item_mapper: Class.new(Yaks::Mapper) { type 'shoe'; attributes :size, :color }\n    end\n  end\n\n  subject(:shoe_association) { closet_mapper.associations.first }\n\n  describe \"#singular_name\" do\n    its(:singular_name) { should eq 'shoe' }\n  end\n\n  let(:closet) {\n    fake(\n      shoes: [\n        fake(size: 9,    color: :blue),\n        fake(size: 11.5, color: :red)\n      ]\n    )\n  }\n\n  describe \"#map_resource\" do\n    it 'should map the subresources' do\n      expect(closet_mapper.call(closet).subresources).to eql([\n        Yaks::CollectionResource.new(\n          type: 'shoe',\n          members: [\n            Yaks::Resource.new(type: 'shoe', attributes: {size: 9, color: :blue}),\n            Yaks::Resource.new(type: 'shoe', attributes: {size: 11.5, color: :red})\n          ],\n          rels: ['http://foo/shoes']\n        )\n      ])\n    end\n\n    it 'should map nil to a NullResource collection' do\n      expect(closet_mapper.call(fake(shoes: nil)).subresources).to eql([\n        Yaks::NullResource.new(collection: true, rels: ['http://foo/shoes'])\n      ])\n    end\n\n    context 'without an explicit mapper' do\n      let(:dress_mapper) {\n        Class.new(Yaks::Mapper) { type 'dress'; attributes :color }\n      }\n\n      before do\n        closet_mapper_class.class_eval do\n          has_many :dresses\n        end\n\n        stub(closet_mapper.policy).derive_mapper_from_association(any_args) do\n          dress_mapper\n        end\n      end\n\n      it 'should derive it from policy' do\n        expect(closet_mapper.policy).to equal policy\n        closet_mapper.call(fake(shoes: [], dresses: [fake(color: 'blue')]))\n      end\n    end\n  end\n\n  describe '#collection_mapper' do\n    let(:collection_mapper) { Yaks::Undefined }\n\n    subject(:has_many) { described_class.new(name: :name, item_mapper: :mapper, rel: :rel, collection_mapper: collection_mapper) }\n\n    context 'when the collection mapper is undefined' do\n      it 'should derive one from collection and policy' do\n        expect(has_many.collection_mapper([], Yaks::DefaultPolicy.new)).to equal Yaks::CollectionMapper\n      end\n\n      it 'should return nil if no policy is given' do\n        expect(has_many.collection_mapper([])).to be_nil\n      end\n\n      it 'should return nil if no collection is given' do\n        expect(has_many.collection_mapper(nil, Yaks::DefaultPolicy.new)).to be_nil\n      end\n\n      it 'should return nil if no params are given' do\n        expect(has_many.collection_mapper).to be_nil\n      end\n    end\n\n    context 'when the collection mapper is specified' do\n      let(:collection_mapper) { :foo }\n\n      it 'should use the given collection mapper' do\n        expect(has_many.collection_mapper([], Yaks::DefaultPolicy.new)).to equal :foo\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/has_one_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::HasOne do\n  include_context 'yaks context'\n\n  AuthorMapper = Class.new(Yaks::Mapper) { attributes :name }\n\n  subject(:has_one) do\n    described_class.new(\n      name: :author,\n      item_mapper: association_mapper,\n      rel: 'http://rel'\n    )\n  end\n\n  let(:association_mapper) { AuthorMapper }\n  let(:name)               { 'William S. Burroughs' }\n  let(:author)             { fake(name: name) }\n\n  fake(:policy,\n    derive_type_from_mapper_class: 'author',\n    derive_mapper_from_association: AuthorMapper\n  ){ Yaks::DefaultPolicy }\n\n  describe \"#singular_name\" do\n    its(:singular_name) { should eq 'author' }\n  end\n\n  describe \"#map_resource\" do\n    it 'should map to a single Resource' do\n      expect(has_one.map_resource(author, yaks_context))\n        .to eq Yaks::Resource.new(type: 'author', attributes: {name: name})\n    end\n\n    context 'with no mapper specified' do\n      subject(:subresource) {\n        has_one.add_to_resource(Yaks::Resource.new, parent_mapper, yaks_context)\n      }\n      let(:association_mapper) { Yaks::Undefined }\n      fake(:parent_mapper) { Yaks::Mapper }\n\n      before do\n        stub(parent_mapper).load_association(:author) { author }\n      end\n\n      it 'should derive one based on policy' do\n        expect(subresource).to eql(\n          Yaks::Resource.new(subresources: [\n            Yaks::Resource.new(\n              type: 'author',\n              attributes: {name: name},\n              rels: ['http://rel']\n            )\n          ])\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/link_spec.rb",
    "content": "RSpec.describe Yaks::Mapper::Link do\n  include_context 'yaks context'\n\n  subject(:link) { described_class.create(rel, template, options) }\n\n  let(:rel)      { :next }\n  let(:template) { '/foo/bar/{x}/{y}' }\n  let(:options)  { {} }\n\n  let(:object) { Struct.new(:x, :y, :returns_nil).new(3, 4, nil) }\n\n  let(:mapper_class) do\n    Class.new(Yaks::Mapper) do\n      type 'foo'\n    end\n  end\n\n  let(:mapper) do\n    mapper_class.new(yaks_context).tap do |mapper|\n      mapper.call(object) # set @object\n    end\n  end\n\n  describe '#add_to_resource' do\n    it 'should add itself to the resource' do\n      expect(link.add_to_resource(Yaks::Resource.new, mapper, yaks_context)).to eql(\n        Yaks::Resource.new(links: [Yaks::Resource::Link.new(rel: :next, uri: \"/foo/bar/3/4\")])\n      )\n    end\n\n    context 'with a link function returning nothing' do\n      let(:template) { ->{ link_computer } }\n      before do\n        mapper_class.class_eval do\n          def link_computer; end\n        end\n      end\n\n      it 'should not render the link' do\n        expect(link.add_to_resource(Yaks::Resource.new, mapper, yaks_context)).to eql(\n          Yaks::Resource.new\n        )\n      end\n    end\n\n    context 'with remove: true' do\n      let(:options) { {remove: true} }\n      let(:resource) {\n        Yaks::Resource.new(links: [\n          Yaks::Resource::Link.new(rel: :next, uri: '/api/next'),\n          Yaks::Resource::Link.new(rel: :prev, uri: '/api/prev')\n        ])\n      }\n\n      it 'should remove earlier links of the same rel' do\n        expect(link.add_to_resource(resource, mapper, yaks_context)).to eql(\n          Yaks::Resource.new(links: [\n            Yaks::Resource::Link.new(rel: :prev, uri: '/api/prev')\n          ])\n        )\n      end\n    end\n\n    context 'with a link with the same rel already present' do\n      let(:resource) {\n        Yaks::Resource.new(links: [Yaks::Resource::Link.new(rel: :next, uri: '/api/next')])\n      }\n\n      it 'should keep both links' do\n        expect(link.add_to_resource(resource, mapper, yaks_context)).to eql(\n          Yaks::Resource.new(links: [\n            Yaks::Resource::Link.new(rel: :next, uri: \"/api/next\"),\n            Yaks::Resource::Link.new(rel: :next, uri: \"/foo/bar/3/4\")\n          ])\n        )\n      end\n    end\n\n    context 'with replace: true' do\n      let(:options) { {replace: true} }\n      let(:resource) {\n        Yaks::Resource.new(links: [\n          Yaks::Resource::Link.new(rel: :next, uri: '/api/next'),\n          Yaks::Resource::Link.new(rel: :prev, uri: '/api/prev')\n        ])\n      }\n\n      it 'should replace earlier links of the same rel' do\n        expect(link.add_to_resource(resource, mapper, yaks_context)).to eql(\n          Yaks::Resource.new(links: [\n            Yaks::Resource::Link.new(rel: :prev, uri: \"/api/prev\"),\n            Yaks::Resource::Link.new(rel: :next, uri: \"/foo/bar/3/4\")\n          ])\n        )\n      end\n    end\n\n    context 'with :if defined and resolving to true' do\n      let(:options) { {if: ->{ true }} }\n\n      it 'should render the link' do\n        expect(link.add_to_resource(Yaks::Resource.new, mapper, yaks_context)).to eql(\n          Yaks::Resource.new(links: [Yaks::Resource::Link.new(rel: :next, uri: \"/foo/bar/3/4\")])\n        )\n      end\n    end\n\n    context 'with :if defined and resolving to false' do\n      let(:options) { {if: ->{ false }} }\n\n      it 'should not render the link' do\n        expect(link.add_to_resource(Yaks::Resource.new, mapper, yaks_context)).to eql(\n          Yaks::Resource.new\n        )\n      end\n    end\n  end\n\n  describe '#rel?' do\n    it 'should return true if the relation matches' do\n      expect(link.rel?(:next)).to be true\n    end\n\n    it 'should return false if the relation does not match' do\n      expect(link.rel?(:previous)).to be false\n    end\n\n    context 'with URI rels' do\n      let(:rel) { 'http://foo/bar/rel' }\n\n      it 'should return true if the relation matches' do\n        expect(link.rel?('http://foo/bar/rel')).to be true\n      end\n\n      it 'should return false if the relation does not match' do\n        expect(link.rel?('http://foo/bar/other')).to be false\n      end\n    end\n  end\n\n  describe '#map_to_resource_link' do\n    subject(:resource_link) { link.map_to_resource_link(mapper) }\n\n    its(:rel) { should eq :next }\n\n    context 'with attributes' do\n      it 'should not have a title' do\n        expect(resource_link.options.key?(:title)).to be false\n      end\n\n      it 'should not be templated' do\n        expect(resource_link.options[:templated]).to be_falsey\n      end\n\n      context 'with extra options' do\n        let(:options) { {title: 'foo', expand: [:x], foo: :bar} }\n\n        it 'should pass on unknown options' do\n          expect(resource_link.options[:foo]).to eql :bar\n        end\n      end\n\n      it 'should create an instance of Yaks::Resource::Link' do\n        expect(resource_link).to be_a(Yaks::Resource::Link)\n      end\n\n      it 'should expand the URI template' do\n        expect(resource_link.uri).to eq '/foo/bar/3/4'\n      end\n    end\n\n    context 'with expansion turned off' do\n      let(:options) { {expand: false} }\n\n      it 'should be templated' do\n        expect(resource_link.options[:templated]).to be true\n      end\n\n      it 'should not propagate :expand' do\n        expect(resource_link.options.key?(:expand)).to be false\n      end\n\n      it 'should keep the link template intact' do\n        expect(resource_link.uri).to eql \"/foo/bar/{x}/{y}\"\n      end\n    end\n\n    context 'with partial expansion' do\n      let(:options) { {expand: [:x]} }\n\n      it 'should be templated' do\n        expect(resource_link.options[:templated]).to be true\n      end\n    end\n\n    context 'with a title set' do\n      let(:options) { {title: 'link title'} }\n\n      it 'should set the title on the resource link' do\n        expect(resource_link.title).to eq 'link title'\n      end\n    end\n\n    context 'with a title lambda' do\n      let(:options) { {title: -> { \"say #{mapper_method}\" }} }\n\n      before do\n        mapper_class.class_eval do\n          def mapper_method\n            'hello'\n          end\n        end\n      end\n\n      it 'should evaluate the lambda in the context of the mapper' do\n        expect(resource_link.title).to eq 'say hello'\n      end\n    end\n\n    context 'with a link generation method that returns nil' do\n      let(:template) { ->{ object.returns_nil } }\n\n      it 'should return nil' do\n        expect(resource_link).to be_nil\n      end\n    end\n\n    context 'with a if lambda resolving to true' do\n      let(:options) { {if: -> { add_link? }} }\n\n      before do\n        mapper_class.class_eval do\n          def add_link?\n            true\n          end\n        end\n      end\n\n      it 'should evaluate the lambda in the context of the mapper' do\n        expect(resource_link).to be_a(Yaks::Resource::Link)\n      end\n    end\n\n    context 'with a if lambda resolving to false' do\n      let(:options) { {if: -> { add_link? }} }\n\n      before do\n        mapper_class.class_eval do\n          def add_link?\n            false\n          end\n        end\n      end\n\n      it 'should evaluate the lambda in the context of the mapper' do\n        expect(resource_link).to be_nil\n      end\n    end\n  end\n\n  describe '.create' do\n    it 'should take positional arguments' do\n      expect(Yaks::Mapper::Link.create(:foo, :bar, baz: 3))\n        .to eql Yaks::Mapper::Link.new(rel: :foo, template: :bar, options: {baz: 3})\n    end\n\n    it 'should default options' do\n      expect(Yaks::Mapper::Link.create(:foo, :bar).options).to eql({})\n    end\n  end\n\n  describe '#templated?' do\n    context 'when no expansion behavior is set (defaults to expand fully)' do\n      let(:options) { {} }\n      its(:templated?) { should be false }\n    end\n\n    context 'when the link does not expand' do\n      let(:options) { {expand: false} }\n      its(:templated?) { should be true }\n    end\n\n    context 'when the link expands fully' do\n      let(:options) { {expand: true} }\n      its(:templated?) { should be false }\n    end\n\n    context 'when the link expands partially' do\n      let(:options) { {expand: [:x]} }\n      its(:templated?) { should be true }\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper_spec.rb",
    "content": "RSpec.describe Yaks::Mapper do\n  include_context 'yaks context'\n\n  subject(:mapper)   { mapper_class.new(yaks_context) }\n  let(:resource)     { mapper.call(instance) }\n\n  let(:mapper_class) { Class.new(Yaks::Mapper) { type 'foo' } }\n\n  let(:instance) { fake(foo: 'hello', bar: 'world') }\n\n  describe \"#initialize\" do\n    it \"should store the context\" do\n      expect(mapper.context).to equal yaks_context\n    end\n  end\n\n  describe \"#env\" do\n    its(:env) { should equal rack_env }\n  end\n\n  describe \"#policy\" do\n    it \"should pull it out of the context\" do\n      expect(mapper.policy).to equal policy\n    end\n  end\n\n  describe '#call' do\n    shared_examples \"a mapper with attributes\" do\n      it 'should make the configured attributes available on the instance' do\n        expect(mapper.attributes).to eq attributes\n      end\n\n      it 'should load attributes from the model' do\n        expect(resource.attributes).to eq(attributes_from_model)\n      end\n\n      context 'with attribute filtering' do\n        before do\n          mapper_class.class_eval do\n            def attributes\n              super.reject {|attr| attr.name == :foo}\n            end\n          end\n        end\n\n        it 'should only map the non-filtered attributes' do\n          expect(resource.attributes).to eq(non_filtered_attributes)\n        end\n      end\n    end\n\n    context \"with attribute\" do\n      let(:attributes) { [ Yaks::Mapper::Attribute.create(:foo) ] }\n      let(:attributes_from_model) { {foo: 'hello'} }\n      let(:non_filtered_attributes) { {} }\n\n      context \"called without block\" do\n        before do\n          mapper_class.attribute :foo\n        end\n\n        it_behaves_like \"a mapper with attributes\"\n      end\n\n      context \"called with a block\" do\n        let(:block) { proc { object.bar } }\n        let(:attributes) { [ Yaks::Mapper::Attribute.create(:foo, &block) ] }\n        let(:attributes_from_model) { {foo: 'world'} }\n\n        before do\n          mapper_class.attribute :foo, &block\n        end\n\n        it_behaves_like \"a mapper with attributes\"\n      end\n    end\n\n    context 'with attributes' do\n      let(:attributes) do\n        [\n          Yaks::Mapper::Attribute.create(:foo),\n          Yaks::Mapper::Attribute.create(:bar)\n        ]\n      end\n\n      let(:attributes_from_model) { {foo: 'hello', bar: 'world'} }\n      let(:non_filtered_attributes) { {bar: 'world'} }\n\n      before do\n        mapper_class.attributes :foo, :bar\n      end\n\n      it_behaves_like \"a mapper with attributes\"\n    end\n\n    context 'with links' do\n      before do\n        mapper_class.link :profile, 'http://foo/bar'\n      end\n\n      it 'should map the link' do\n        expect(resource.links).to eq [\n          Yaks::Resource::Link.new(rel: :profile, uri: 'http://foo/bar')\n        ]\n      end\n\n      it 'should use the link in the resource' do\n        expect(resource.links).to include Yaks::Resource::Link.new(rel: :profile, uri: 'http://foo/bar')\n      end\n\n      context 'with the same link rel defined multiple times' do\n        before do\n          mapper_class.class_eval do\n            link(:self, 'http://foo/bam')\n            link(:self, 'http://foo/baz')\n            link(:self, 'http://foo/baq')\n          end\n        end\n\n        it 'should map all the links' do\n          expect(resource.links).to eq [\n            Yaks::Resource::Link.new(rel: :profile, uri: 'http://foo/bar'),\n            Yaks::Resource::Link.new(rel: :self, uri: 'http://foo/bam'),\n            Yaks::Resource::Link.new(rel: :self, uri: 'http://foo/baz'),\n            Yaks::Resource::Link.new(rel: :self, uri: 'http://foo/baq')\n          ]\n        end\n      end\n    end\n\n    context 'with subresources' do\n      let(:widget)   { fake(type: 'super_widget') }\n      let(:instance) { fake(widget: widget) }\n      let(:widget_mapper) { Class.new(Yaks::Mapper) { type 'widget' } }\n      fake(:policy) { Yaks::DefaultPolicy }\n\n      describe 'has_one' do\n        let(:has_one_opts) do\n          {mapper: widget_mapper,\n           rel: 'http://foo.bar/rels/widgets'}\n        end\n\n        before do\n          widget_mapper.attributes :type\n          mapper_class.has_one(:widget, has_one_opts)\n        end\n\n        it 'should have the subresource in the resource' do\n          expect(resource.subresources).to eq([Yaks::Resource.new(type: 'widget', attributes: {type: 'super_widget'}, rels: ['http://foo.bar/rels/widgets'])])\n        end\n\n        context 'with explicit mapper and rel' do\n          it 'should delegate to the given mapper' do\n            expect(resource.subresources).to eq([\n              Yaks::Resource.new(type: 'widget', attributes: {type: 'super_widget'}, rels: ['http://foo.bar/rels/widgets'])\n            ])\n          end\n        end\n\n        context 'with unspecified mapper' do\n          let(:has_one_opts) do\n            {rel: 'http://foo.bar/rels/widgets'}\n          end\n\n          before do\n            stub(policy).derive_mapper_from_association(mapper.associations.first) do\n              widget_mapper\n            end\n          end\n\n          it 'should derive the mapper based on policy' do\n            expect(resource.subresources).to eq([\n              Yaks::Resource.new(type: 'widget', attributes: {type: 'super_widget'}, rels: ['http://foo.bar/rels/widgets'])\n            ])\n          end\n        end\n\n        context 'with unspecified rel' do\n          let(:has_one_opts) do\n            {mapper: widget_mapper}\n          end\n\n          before do\n            stub(policy).derive_rel_from_association(mapper.associations.first) do\n              'http://rel/rel'\n            end\n          end\n\n          it 'should derive the rel based on policy' do\n            expect(resource.subresources).to eq([\n              Yaks::Resource.new(type: 'widget', attributes: {type: 'super_widget'}, rels: ['http://rel/rel'])\n            ])\n          end\n        end\n\n        context 'with the association filtered out' do\n          before do\n            mapper_class.class_eval do\n              def associations\n                []\n              end\n            end\n          end\n\n          it 'should not map the resource' do\n            expect(resource.subresources).to eq([])\n          end\n        end\n      end\n    end\n\n    context 'when the mapper implements a method with the attribute name' do\n      before do\n        mapper_class.class_eval do\n          attributes :fooattr, :bar\n\n          def fooattr\n            \"#{object.foo} my friend\"\n          end\n        end\n      end\n\n      it 'should get the attribute from the mapper' do\n        expect(resource.attributes).to eq(fooattr: 'hello my friend', bar: 'world')\n      end\n    end\n\n    context 'with a nil subject' do\n      it 'should return a NullResource when the subject is nil' do\n        expect(mapper.call(nil)).to be_a Yaks::NullResource\n      end\n    end\n\n    context 'with a link generated by a method that returns nil' do\n      before do\n        mapper_class.class_eval do\n          attributes :id\n          link :bar_link, -> { link_generating_method }\n\n          def link_generating_method\n          end\n        end\n      end\n\n      it 'should not render the link' do\n        expect(mapper.call(fake(id: 123))).to eql Yaks::Resource.new(\n          type: 'foo',\n          attributes: {id: 123}\n        )\n      end\n    end\n\n    context 'with a form' do\n      before do\n        mapper_class.module_eval do\n          form :foo_form do\n            field :bar_field, label: 'a label', type: 'number', value: 7\n          end\n        end\n      end\n\n      it 'should render the form' do\n        expect(mapper.call(fake))\n          .to eql Yaks::Resource.new(\n                    type: 'foo',\n                    forms: [\n                      Yaks::Resource::Form.new(\n                        name: :foo_form,\n                        fields: [\n                          Yaks::Resource::Form::Field.new(\n                            name: :bar_field,\n                            label: 'a label',\n                            type: 'number',\n                            value: 7\n                          )])])\n      end\n    end\n\n    it \"should optionally take a rack env\" do\n      expect { mapper.call(fake, {}) }.to_not raise_error\n    end\n  end # describe '#call'\n\n  describe '.mapper_name' do\n    context 'with a type configured' do\n      it 'should use the type' do\n        expect(mapper_class.mapper_name(policy)).to eql 'foo'\n      end\n    end\n\n    context 'without a type configured' do\n      let(:mapper_class) { PetMapper }\n\n      it 'should infer the type from the name' do\n        expect(mapper_class.mapper_name(policy)).to eql 'pet'\n      end\n    end\n  end\n\n  describe '#mapper_name' do\n    context 'with a type configured' do\n      it 'should use the type' do\n        expect(mapper.mapper_name).to eql 'foo'\n      end\n    end\n\n    context 'without a type configured' do\n      let(:mapper_class) { PetMapper }\n\n      it 'should infer the type from the name' do\n        expect(mapper.mapper_name).to eql 'pet'\n      end\n    end\n  end\n\n  describe '#load_attribute' do\n    context 'with the attribute defined as a method on the mapper' do\n      it 'should call the local implementation' do\n        def mapper.foo\n          14\n        end\n\n        expect(mapper.load_attribute(:foo)).to eql 14\n      end\n    end\n\n    context 'without a mapper method with the attribute name' do\n      it 'should call the method on the object being mapped' do\n        mapper.call(instance) # set @object\n        expect(mapper.load_attribute(:foo)).to eql 'hello'\n      end\n    end\n  end\n\n  describe '#map_attributes' do\n    let(:attribute) { fake('Attribute') }\n\n    it 'should receive a context' do\n      stub(attribute).add_to_resource(any_args) {|_r, _, _| Yaks::Resource.new}\n\n      mapper.config.attributes[0..-1] = [attribute]\n      mapper.call(instance)\n\n      expect(attribute).to have_received.add_to_resource(any(Yaks::Resource), mapper, yaks_context)\n    end\n  end\n\n  shared_examples 'something that can be added to a resource' do\n    it 'should receive a context' do\n      stub(object).add_to_resource(any_args) {|_r, _, _| Yaks::Resource.new}\n\n      mapper.call(instance)\n\n      expect(object).to have_received.add_to_resource(any(Yaks::Resource), mapper, yaks_context)\n    end\n  end\n\n  describe '#map_links' do\n    let(:object) { fake('Link') }\n    before { mapper_class.config = mapper.config.append_to(:links, object) }\n    it_should_behave_like 'something that can be added to a resource'\n  end\n\n  describe '#map_subresources' do\n    let(:object) { fake('Association') }\n    before { mapper_class.config = mapper.config.append_to(:associations, object) }\n    it_should_behave_like 'something that can be added to a resource'\n  end\n\n  describe \"#map_forms\" do\n    let(:object) { fake('Form') }\n    before { mapper_class.config = mapper.config.append_to(:forms, object) }\n    it_should_behave_like \"something that can be added to a resource\"\n  end\n\n  describe '#mapper_stack' do\n    let(:yaks_context) { super().merge(mapper_stack: [:foo]) }\n\n    it 'should delegate to context' do\n      expect(mapper.mapper_stack).to eql [:foo]\n    end\n  end\n\n  describe \"#expand_value\" do\n    it \"should immediately return plain values\" do\n      expect(mapper.expand_value(:foo)).to equal :foo\n    end\n\n    it \"should resolve lambdas in the context of the mapper\" do\n      expect(mapper.expand_value(->{ self })).to be_a Yaks::Mapper\n    end\n  end\n\n  describe '#expand_uri' do\n    let(:template) { '/foo/bar/{x}/{y}' }\n    let(:expand) { true }\n    let(:args) { [template, expand] }\n\n    subject(:expanded) { mapper.expand_uri(*args) }\n\n    before do\n      mapper.call(Struct.new(:x, :y) {\n        def foo\n          '/foo/foo'\n        end\n      }.new(6, 7))\n    end\n\n    context \"with full expansion\" do\n      let(:args) { [template] }\n      it \"should expand all template variables\" do\n        expect(expanded).to eq \"/foo/bar/6/7\"\n      end\n    end\n\n    context 'with expansion turned off' do\n      let(:expand) { false }\n\n      it 'should keep the template in the response' do\n        expect(expanded).to eq '/foo/bar/{x}/{y}'\n      end\n    end\n\n    context 'with a URI without expansion variables' do\n      let(:args) { ['/orders'] }\n\n      it 'should return the link as is' do\n        expect(expanded).to eq '/orders'\n      end\n    end\n\n    context 'with partial expansion' do\n      let(:expand) { [:y] }\n\n      it 'should only expand the given variables' do\n        expect(expanded).to eql '/foo/bar/{x}/7'\n      end\n    end\n\n    context 'with a symbol for a template' do\n      let(:args) { [->{ object.foo }] }\n\n      it 'should use the lookup mechanism for finding the link' do\n        expect(expanded).to eq '/foo/foo'\n      end\n    end\n\n    context \"with a nil uri\" do\n      let(:args) { [nil] }\n\n      it \"should return nil\" do\n        expect(expanded).to be_nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/null_resource_spec.rb",
    "content": "RSpec.describe Yaks::NullResource do\n  subject(:null_resource) { described_class.new }\n\n  describe '#initialize' do\n    it 'should have defaults for everything' do\n      expect(described_class.new.to_h).to eql(\n        type: nil,\n        rels: [],\n        links: [],\n        attributes: {},\n        subresources: [],\n        forms: [],\n        collection: false\n      )\n    end\n\n    it 'should allow setting rels' do\n      expect(described_class.new(rels: [:self]).rels).to eql [:self]\n    end\n\n    it 'should allow setting the collection flag' do\n      expect(described_class.new(collection: true).collection).to be true\n    end\n\n    it 'should not allow attributes in the contstructor' do\n      expect(described_class.new(attributes: {foo: :bar}).attributes).to eql({})\n    end\n  end\n\n  describe \"#attributes\" do\n    its(:attributes) { should eql({}) }\n  end\n\n  describe \"#links\" do\n    its(:links) { should eql [] }\n  end\n\n  describe \"#rels\" do\n    its(:rels) { should eql [] }\n  end\n\n  describe \"#subresources\" do\n    its(:subresources) { should eql [] }\n  end\n\n  describe \"#collection?\" do\n    its(:collection?) { should be false }\n\n    context 'when a collection' do\n      subject(:null_resource) { described_class.new(collection: true) }\n      its(:collection?) { should be true }\n    end\n  end\n\n  describe \"#null_resource?\" do\n    its(:null_resource?) { should be true }\n  end\n\n  describe \"#seq\" do\n    its(:seq) { should eql [] }\n  end\n\n  describe \"#[]\" do\n    it 'should contain nothing' do\n      expect(null_resource[:key]).to be_nil\n    end\n  end\n\n  describe \"#type\" do\n    its(:type) { should be_nil }\n  end\n\n  describe '#each' do\n    its(:each) { should be_a Enumerator }\n\n    it 'should not yield anything' do\n      null_resource.each { raise }\n    end\n  end\n\n  describe \"#merge_attributes\" do\n    it 'should not allow updating attributes' do\n      expect { null_resource.merge_attributes({}) }.to raise_error(\n        Yaks::UnsupportedOperationError, \"Operation merge_attributes not supported on Yaks::NullResource\"\n      )\n    end\n  end\n\n  describe \"#add_link\" do\n    it 'should not allow adding links' do\n      expect { null_resource.add_link(nil) }.to raise_error(\n        Yaks::UnsupportedOperationError, \"Operation add_link not supported on Yaks::NullResource\"\n      )\n    end\n  end\n\n  describe \"#add_form\" do\n    it 'should not allow adding forms' do\n      expect { null_resource.add_form(nil) }.to raise_error(\n        Yaks::UnsupportedOperationError, \"Operation add_form not supported on Yaks::NullResource\"\n      )\n    end\n  end\n\n  describe \"#add_subresource\" do\n    it 'should not allow adding subresources' do\n      expect { null_resource.add_subresource(nil) }.to raise_error(\n        Yaks::UnsupportedOperationError, \"Operation add_subresource not supported on Yaks::NullResource\"\n      )\n    end\n  end\n\n  describe '#map' do\n    context 'when a collection' do\n      it 'should always return []' do\n        expect(described_class.new(collection: true).map{}).to eql []\n      end\n    end\n\n    context 'when not a collection' do\n      it 'should raise an error' do\n        expect { null_resource.map{} }.to raise_error(\n          Yaks::UnsupportedOperationError, \"Operation map not supported on Yaks::NullResource\"\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/pipeline_spec.rb",
    "content": "RSpec.describe Yaks::Pipeline do\n  subject(:pipeline) { described_class.new(steps) }\n  let(:steps) {\n    [\n      [:step1, ->(i, _e) { i + 1 }],\n      [:step2, ->(i, _e) { i + 10 }],\n      [:step3, ->(i, e) { i + e[:foo] }]\n    ]\n  }\n  let(:env) {{foo: 100}}\n\n  describe '#call' do\n    it 'should call steps in turn, passing in the last result and env' do\n      expect(pipeline.call(1000, env)).to equal 1111\n    end\n  end\n\n  describe '#insert_hooks' do\n    let(:hooks) { [] }\n\n    describe 'before' do\n      let(:hooks) { [[:before, :step2, :before_step2, ->(i, _e) { i - (i % 100) }]] }\n\n      it 'should insert a hook before the step' do\n        expect(pipeline.insert_hooks(hooks).call(1000, env)).to equal 1110\n      end\n    end\n\n    describe 'after' do\n      let(:hooks) { [[:after, :step2, :after_step2, ->(i, _e) { i - (i % 100) }]] }\n\n      it 'should insert a hook after the step' do\n        expect(pipeline.insert_hooks(hooks).call(1000, env)).to equal 1100\n      end\n    end\n\n    describe 'around' do\n      let(:hooks) { [[:around, :step2, :after_step2, ->(i, e, &b) { e[:foo] + b[i, e] + b[i, e] }]] }\n\n      it 'should insert a hook after the step' do\n        expect(pipeline.insert_hooks(hooks).call(1000, env)).to equal 2222\n      end\n    end\n\n    describe 'skip' do\n      let(:hooks) { [[:skip, :step2 ]] }\n\n      it 'should insert a hook after the step' do\n        expect(pipeline.insert_hooks(hooks).call(1000, env)).to equal 1101\n      end\n    end\n\n    describe 'multiple hooks' do\n      let(:hooks) {\n        [\n          [:after, :step2, :after_step2, ->(i, _e) { i % 10 }],\n          [:skip, :step3]\n        ]\n      }\n\n      it 'should insert the hooks' do\n        expect(pipeline.insert_hooks(hooks).call(1000, env)).to equal 1\n      end\n    end\n\n    it 'should return a pipeline with the right step names' do\n      expect(pipeline\n              .insert_hooks([[:before, :step2, :step1_1, ->(i, _e) { i + 1 }]])\n              .insert_hooks([[:before, :step1_1, :step1_0, ->(i, _e) { i + 10 }]])\n              .insert_hooks([[:after,  :step1_1, :step1_2, ->(i, _e) { i + 100 }]])\n              .insert_hooks([[:around, :step1_1, :step1_1_0, ->(i, e, &b) { b.call(i, e) + 1000 }]])\n              .insert_hooks([[:around, :step1_2, :step1_3, ->(i, e, &b) { b.call(i, e) + 1000 }]])\n              .insert_hooks([[:after,  :step1_3, :step1_4, ->(i, _e) { i + 10_000 }]])\n              .call(1000, env)\n            ).to equal 13_222\n    end\n  end\n\n  let(:fake_step) {\n    Class.new do\n      include Attribs.new(:transitive, :call, inverse: nil)\n      alias_method :transitive?, :transitive\n    end\n  }\n\n  let(:transitive_step) {\n    fake_step.new(transitive: true, inverse: ->(_x, _env) {}, call: \"t\")\n  }\n\n  let(:intransitive_step) {\n    fake_step.new(transitive: false, call: \"i\")\n  }\n\n  subject(:pipeline) { described_class.new(steps) }\n\n  describe '#transitive?' do\n    context 'with transitive steps' do\n      let(:steps) { [[:name1, transitive_step]] }\n      it 'should be transitive' do\n        expect(pipeline.transitive?).to be true\n      end\n    end\n  end\n\n  describe '#inverse' do\n  end\nend\n\n# describe 'after' do\n#   let(:hooks) { proc { after(:format) { :after_format_impl } } }\n\n#   it 'should insert a hook after the step' do\n#     expect(runner.steps.map(&:first)).to eql [\n#       :map, :format, :after_format, :primitivize, :serialize\n#     ]\n#     expect(runner.steps[1].last).to be runner.formatter\n#     expect(runner.steps[2].last.call).to be :after_format_impl\n#   end\n# end\n\n# describe 'around' do\n#   let(:hooks) do\n#     proc {\n#       around(:serialize) do |res, env, &block|\n#         \"serialized[#{env}][#{block.call(res, env)}]\"\n#       end\n#     }\n#   end\n\n#   it 'should insert a hook around the step' do\n#     expect(runner.steps.map(&:first)).to eql [\n#       :map, :format, :primitivize, :serialize\n#     ]\n#     expect(runner.steps.assoc(:serialize).last.call([\"res1\"], \"env1\")).to eql(\n#       \"serialized[env1][[\\n  \\\"res1\\\"\\n]]\"\n#     )\n#   end\n# end\n\n# describe 'around' do\n#   let(:hooks) { ->(*) { skip(:serialize) } }\n\n#   it 'should skip a certain step' do\n#     expect(runner.steps.map(&:first)).to eql [\n#       :map, :format, :primitivize\n#     ]\n#   end\n# end\n\n# describe 'multiple hooks' do\n#   let(:hooks) {\n#     proc {\n#       after(:format) { :after_format_impl }\n#       skip(:serialize)\n#     }\n#   }\n\n#   it 'should insert the hooks' do\n#     expect(runner.steps.map(&:first)).to eql [\n#       :map, :format, :after_format, :primitivize\n#     ]\n#   end\n\n#   it 'should pass on unchanged steps' do\n#     expect(runner.steps.assoc(:map)[1]).to eql runner.mapper\n#   end\n# end\n"
  },
  {
    "path": "yaks/spec/unit/yaks/primitivize_spec.rb",
    "content": "RSpec.describe Yaks::Primitivize do\n  subject(:primitivizer) { described_class.create }\n\n  describe \"#initialize\" do\n    it \"should not create any mappings\" do\n      expect(described_class.new.mappings).to eql Hash[]\n    end\n  end\n\n  describe '.create' do\n    it 'should map String, true, false, nil, numbers to themselves' do\n      [\n        'hello',\n        true,\n        false,\n        nil,\n        100,\n        99.99,\n        -95.33333\n      ].each do |object|\n        expect(primitivizer.call(object)).to eql object\n      end\n    end\n\n    it 'should stringify symbols' do\n      expect(primitivizer.call(:foo)).to eql 'foo'\n    end\n\n    it 'should recursively handle hashes' do\n      expect(primitivizer.call(\n          foo: {:wassup => :friends, 123 => '456'}\n      )).to eql('foo' => {'wassup' => 'friends', 123 => '456'})\n    end\n\n    it 'should handle arrays recursively' do\n      expect(primitivizer.call([:foo, [:wassup, :friends], 123, '456']))\n        .to eql(['foo', %w[wassup friends], 123, '456'])\n    end\n\n    it \"should handle URIs by turning them to strings\" do\n      expect(primitivizer.call(URI(\"http://foo.bar/baz\"))).to eql \"http://foo.bar/baz\"\n    end\n  end\n\n  describe '#call' do\n    require 'ostruct'\n\n    let(:funny_object) {\n      OpenStruct.new('a' => 'b')\n    }\n\n    it 'should raise an error when passed an unkown type' do\n      def funny_object.inspect\n        \"I am funny\"\n      end\n\n      expect { primitivizer.call(funny_object) }\n        .to raise_error Yaks::PrimitivizeError, \"don't know how to turn OpenStruct (I am funny) into a primitive\"\n    end\n\n    context 'with custom mapping' do\n      require 'matrix'\n\n      let(:primitivizer) do\n        described_class.new.tap do |p|\n          p.map Vector do |vec|\n            vec.map do |i|\n              call(i)\n            end.to_a\n          end\n\n          p.map Symbol do |sym|\n            sym.to_s.length\n          end\n        end\n      end\n\n      it 'should evaluate in the context of primitivize' do\n        expect(primitivizer.call(Vector[:foo, :baxxx, :bazz])).to eql([3, 5, 4])\n      end\n    end\n  end\n\n  describe \"#map\" do\n    let(:primitivizer) { described_class.new }\n\n    it \"should add new mappings\" do\n      primitivizer.map(String) {|s| s.upcase }\n      primitivizer.map(Numeric) {|n| n.next }\n\n      expect(primitivizer.call(\"foo\")).to eql \"FOO\"\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/resource/form/field_spec.rb",
    "content": "RSpec.describe Yaks::Resource::Form::Field do\n  subject do\n    described_class.new(type: 'text', name: 'foo', value: 123)\n  end\n\n  describe '#value' do\n    its(:value) { should eql 123 }\n\n    context 'with a select box - with selection' do\n      subject do\n        described_class.new(name: 'foo', type: :select, options: [\n          Yaks::Resource::Form::Field::Option.new(label: 'foo', selected: false, value: 1),\n          Yaks::Resource::Form::Field::Option.new(label: 'foo', selected: true, value: 2),\n          Yaks::Resource::Form::Field::Option.new(label: 'foo', selected: false, value: 3)\n        ])\n      end\n\n      it 'should return the selected value' do\n        expect(subject.value).to eql 2\n      end\n    end\n\n    context 'with a select box - no selection' do\n      subject do\n        described_class.new(name: 'foo', type: :select, options: [\n          Yaks::Resource::Form::Field::Option.new(label: 'foo', selected: false, value: 1),\n          Yaks::Resource::Form::Field::Option.new(label: 'foo', selected: false, value: 2),\n          Yaks::Resource::Form::Field::Option.new(label: 'foo', selected: false, value: 3)\n        ])\n      end\n\n      it 'should return nothing' do\n        expect(subject.value).to be nil\n      end\n    end\n  end\n\n  describe '#with_value' do\n    context 'with a regular field' do\n      it 'should update the given attributes' do\n        expect(subject.with_value(321).value).to eql 321\n      end\n    end\n\n    context 'with a select field' do\n      subject do\n        described_class.new(name: 'foo', type: :select, options: [\n          Yaks::Resource::Form::Field::Option.new(label: 'f', selected: true,  value: \"1\"),\n          Yaks::Resource::Form::Field::Option.new(label: 'f', selected: false, value: \"2\"),\n          Yaks::Resource::Form::Field::Option.new(label: 'f', selected: true,  value: \"3\"),\n          Yaks::Resource::Form::Field::Option.new(label: 'f', selected: false, value: \"4\")\n        ])\n      end\n\n      let(:updated) { subject.with_value(\"2\") }\n\n      it 'should keep existing attributes' do\n        expect([updated.name, updated.type]).to eq ['foo', :select]\n      end\n\n      context 'when changing from a previous value' do\n        it 'should update the affected option' do\n          expect(updated.value).to eq \"2\"\n        end\n\n        it 'should reuse existing Option instances' do\n          expect(updated.options.last).to equal subject.options.last\n        end\n\n        it 'should unset all selected options' do\n          expect(updated.options.map(&:selected)).to eq [false, true, false, false]\n        end\n      end\n\n      context 'when keeping the old value value' do\n        let(:updated) { subject.with_value(\"1\") }\n\n        it 'should not change the value' do\n          expect(updated.value).to eq \"1\"\n        end\n\n        it 'should reuse existing Option instances' do\n          expect(updated.options.first).to equal subject.options.first\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/resource/form/fieldset_spec.rb",
    "content": "RSpec.describe Yaks::Resource::Form::Fieldset do\n  subject(:fieldset) { described_class.new(fields: []) }\n\n  describe '#type' do\n    its(:type) { should equal :fieldset }\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/resource/form/legend_spec.rb",
    "content": "RSpec.describe Yaks::Resource::Form::Legend do\n  subject(:legend) { described_class.new(label: 'a legend') }\n\n  describe '#initialize' do\n    its(:type) { should equal :legend }\n    its(:label) { should eql 'a legend' }\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/resource/form_spec.rb",
    "content": "RSpec.describe Yaks::Resource::Form do\n  let(:fields) {\n    [\n      Yaks::Resource::Form::Field.new(name: :foo, value: '123', type: 'text'),\n      Yaks::Resource::Form::Field.new(name: :bar, value: '+32 477 123 123', type: 'tel')\n    ]\n  }\n\n  subject(:form) {\n    described_class.new(name: :create_foo, fields: fields)\n  }\n\n  describe '#[]' do\n    it 'should find a field value by field name' do\n      expect(subject[:bar])\n        .to eq '+32 477 123 123'\n    end\n  end\n\n  describe '#values' do\n    it 'should return all field values in a hash' do\n      expect(subject.values).to eql(foo: '123', bar: '+32 477 123 123')\n    end\n  end\n\n  describe \"#method?\" do\n    it 'should return true if method matches' do\n      form_sym = Yaks::Resource::Form.new(name: :foo, method: :get)\n      form_str = Yaks::Resource::Form.new(name: :foo, method: 'GET')\n\n      expect(form_sym.method?(:get)).to be true\n      expect(form_sym.method?('get')).to be true\n      expect(form_str.method?(:get)).to be true\n      expect(form_str.method?('GET')).to be true\n    end\n\n    it 'should return false if method does not match' do\n      form_sym = Yaks::Resource::Form.new(name: :foo, method: :get)\n      form_str = Yaks::Resource::Form.new(name: :foo, method: 'GET')\n\n      expect(form_sym.method?(:post)).to be false\n      expect(form_sym.method?('patch')).to be false\n      expect(form_str.method?(:delete)).to be false\n      expect(form_str.method?('PUT')).to be false\n    end\n\n    it 'should always be false when method is not specified' do\n      form = Yaks::Resource::Form.new(name: :foo)\n\n      expect(form.method?(:post)).to be false\n    end\n  end\n\n  describe \"#has_action?\" do\n    it 'should return true if form has an action url' do\n      form = Yaks::Resource::Form.new(name: :foo, action: \"/my-action\")\n\n      expect(form.has_action?).to be true\n    end\n\n    it 'should return false if form has not an action url' do\n      form = Yaks::Resource::Form.new(name: :foo)\n\n      expect(form.has_action?).to be false\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/resource/has_fields_spec.rb",
    "content": "RSpec.describe Yaks::Resource::HasFields do\n  let(:class_with_fields) do\n    Class.new do\n      include Yaks::Resource::HasFields\n      include Attribs.new(:fields)\n    end\n  end\n\n  let(:fields) do\n    [\n      Yaks::Resource::Form::Fieldset.new(fields: [\n        Yaks::Resource::Form::Field.new(name: :foo, value: '123', type: 'text'),\n        Yaks::Resource::Form::Field.new(name: :bar, value: '+32 477 123 123', type: 'tel')\n      ]),\n      Yaks::Resource::Form::Fieldset.new(fields: [\n        Yaks::Resource::Form::Fieldset.new(fields: [\n          Yaks::Resource::Form::Field.new(name: :quux, value: '999', type: 'tel')\n        ]),\n        Yaks::Resource::Form::Field.new(name: :qux, value: '777', type: 'text')\n      ])\n    ]\n  end\n\n  subject(:with_fields) { class_with_fields.new(fields: fields) }\n\n  describe '#map_fields' do\n    let(:update_fields) do\n      ->(field) do\n        field.with(value: \"updated\")\n      end\n    end\n\n    it 'will map over nested fieldsets' do\n      expect(subject.map_fields(&update_fields)).to eql class_with_fields.new(fields: [\n        Yaks::Resource::Form::Fieldset.new(fields: [\n          Yaks::Resource::Form::Field.new(name: :foo, value: 'updated', type: 'text'),\n          Yaks::Resource::Form::Field.new(name: :bar, value: 'updated', type: 'tel')\n        ]),\n        Yaks::Resource::Form::Fieldset.new(fields: [\n          Yaks::Resource::Form::Fieldset.new(fields: [\n            Yaks::Resource::Form::Field.new(name: :quux, value: 'updated', type: 'tel')\n          ]),\n          Yaks::Resource::Form::Field.new(name: :qux, value: 'updated', type: 'text')\n        ])\n      ])\n    end\n  end\n\n  describe '#fields_flat' do\n    let(:fields) do\n      [\n        Yaks::Resource::Form::Fieldset.new(fields: [\n          Yaks::Resource::Form::Legend.new(label: 'a legend'),\n          Yaks::Resource::Form::Field.new(name: :foo, value: '123', type: 'text'),\n          Yaks::Resource::Form::Field.new(name: :bar, value: '+32 477 123 123', type: 'tel')\n        ]),\n        Yaks::Resource::Form::Fieldset.new(fields: [\n          Yaks::Resource::Form::Fieldset.new(fields: [\n            Yaks::Resource::Form::Field.new(name: :qux, value: '777', type: 'text')\n          ]),\n          Yaks::Resource::Form::Field.new(name: :quux, value: '999', type: 'tel')\n        ])\n      ]\n    end\n\n    it 'should flatten fieldsets, skipping legends' do\n      expect(subject.fields_flat.to_a).to eql [\n        Yaks::Resource::Form::Field.new(name: :foo, value: '123', type: 'text'),\n        Yaks::Resource::Form::Field.new(name: :bar, value: '+32 477 123 123', type: 'tel'),\n        Yaks::Resource::Form::Field.new(name: :qux, value: '777', type: 'text'),\n        Yaks::Resource::Form::Field.new(name: :quux, value: '999', type: 'tel')\n      ]\n    end\n\n    it 'should work like #map, collecting block return values, maintaining nesting' do\n      expect(subject.fields_flat {|f| f.with(required: true) }).to eql [\n        Yaks::Resource::Form::Fieldset.new(fields: [\n          Yaks::Resource::Form::Legend.new(label: 'a legend'),\n          Yaks::Resource::Form::Field.new(name: :foo, value: '123', type: 'text', required: true),\n          Yaks::Resource::Form::Field.new(name: :bar, value: '+32 477 123 123', type: 'tel', required: true)\n        ]),\n        Yaks::Resource::Form::Fieldset.new(fields: [\n          Yaks::Resource::Form::Fieldset.new(fields: [\n            Yaks::Resource::Form::Field.new(name: :qux, value: '777', type: 'text', required: true)\n          ]),\n          Yaks::Resource::Form::Field.new(name: :quux, value: '999', type: 'tel', required: true)\n        ])\n      ]\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/resource/link_spec.rb",
    "content": "RSpec.describe Yaks::Resource::Link do\n  subject(:link) { described_class.new(rel: rel, uri: uri, options: options) }\n\n  let(:rel)      { :foo_rel }\n  let(:uri)      { 'http://api.example.org/rel/foo' }\n  let(:options)  { {title: 'mr. spectacular'} }\n\n  its(:rel)     { should eql :foo_rel }\n  its(:uri)     { should eql 'http://api.example.org/rel/foo' }\n  its(:options) { should eql(title: 'mr. spectacular') }\n\n  describe '#title' do\n    its(:title) { should eql('mr. spectacular') }\n  end\n\n  describe '#templated?' do\n    its(:templated?) { should be false }\n\n    context 'with explicit templated option' do\n      let(:options) { super().merge(templated: true) }\n      its(:templated?) { should be true }\n    end\n  end\n\n  describe '#rel?' do\n    let(:rel) { \"/rels/foo\" }\n\n    it 'should be true if the rel matches' do\n      expect(link.rel?(\"/rels/foo\")).to be true\n    end\n\n    it 'should be false if the rel does not match' do\n      expect(link.rel?(:foo)).to be false\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/resource_spec.rb",
    "content": "RSpec.describe Yaks::Resource do\n  subject(:resource) { described_class.new(init_opts) }\n  let(:init_opts) { {} }\n\n  describe '#initialize' do\n    context 'with a zero-arg constructor' do\n      subject(:resource) { described_class.new }\n\n      its(:type)           { should be_nil }\n      its(:attributes)     { should eql({}) }\n      its(:links)          { should eql [] }\n      its(:subresources)   { should eql [] }\n      its(:self_link)      { should be_nil }\n      its(:null_resource?) { should be false }\n    end\n\n    it 'should verify subresources is an array' do\n      expect { Yaks::Resource.new(subresources: {'/rel/comments' => []}) }\n        .to raise_exception /comments/\n    end\n\n    it 'should verify subresources is an array' do\n      expect { Yaks::Resource.new(subresources: []) }\n        .to_not raise_exception\n    end\n\n    it 'should work without args' do\n      expect(Yaks::Resource.new).to be_a Yaks::Resource\n    end\n\n    it 'should take defaults when no args are passed' do\n      expect(Yaks::Resource.new.rels).to eq []\n    end\n  end\n\n  describe \"#[]\" do\n    it \"should access attributes\" do\n      expect(described_class.new(attributes: {foo: :bar})[:foo]).to eql :bar\n    end\n  end\n\n  describe '#find_form' do\n    it 'should find a form by name' do\n      expect(resource\n              .add_form(Yaks::Resource::Form.new(name: :a_form))\n              .add_form(Yaks::Resource::Form.new(name: :b_form))\n              .find_form(:b_form))\n        .to eq Yaks::Resource::Form.new(name: :b_form)\n    end\n  end\n\n  describe \"#seq\" do\n    it \"should provide an enumerable that yields this resource once\" do\n      expect(resource.seq.each.to_a).to eql [resource]\n    end\n  end\n\n  describe '#self_link' do\n    let(:init_opts) {\n      {links:\n        [\n          Yaks::Resource::Link.new(rel: :self, uri: 'foo'),\n          Yaks::Resource::Link.new(rel: :self, uri: 'bar'),\n          Yaks::Resource::Link.new(rel: :profile, uri: 'baz')\n        ]\n      }\n    }\n    it 'should return the last self link' do\n      expect(resource.self_link).to eql Yaks::Resource::Link.new(rel: :self, uri: 'bar')\n    end\n  end\n\n  describe \"#collection?\" do\n    it \"should be false\" do\n      expect(resource.collection?).to be false\n    end\n  end\n\n  describe \"#null_resource?\" do\n    it \"should be false\" do\n      expect(resource.null_resource?).to be false\n    end\n  end\n\n  describe '#add_rel' do\n    it 'should add to the rels' do\n      expect(resource.add_rel(:foo).add_rel(:bar))\n        .to eql Yaks::Resource.new(rels: [:foo, :bar])\n    end\n  end\n\n  describe \"#add_link\" do\n    let(:init_opts) {\n      {\n        links: [Yaks::Resource::Link.new(rel: :next, uri: '/next')]\n      }\n    }\n\n    it \"should append to the links list\" do\n      expect(resource.add_link(Yaks::Resource::Link.new(rel: :previous, uri: '/previous')))\n        .to eql Yaks::Resource.new(links: [\n          Yaks::Resource::Link.new(rel: :next, uri: '/next'),\n          Yaks::Resource::Link.new(rel: :previous, uri: '/previous')\n        ])\n    end\n  end\n\n  describe '#add_form' do\n    it 'should append to the forms' do\n      expect(resource.add_form(Yaks::Resource::Form.new(name: :a_form)))\n        .to eq Yaks::Resource.new(forms: [Yaks::Resource::Form.new(name: :a_form)])\n    end\n  end\n\n  describe \"#add_subresource\" do\n    let(:init_opts) {\n      {\n        subresources: [Yaks::Resource.new(attributes: {foo: 1})]\n      }\n    }\n    it \"should append to the subresources list\" do\n      expect(resource.add_subresource(Yaks::Resource.new(attributes: {bar: 2})))\n        .to eql Yaks::Resource.new(\n          subresources: [\n            Yaks::Resource.new(attributes: {foo: 1}),\n            Yaks::Resource.new(attributes: {bar: 2})\n          ]\n        )\n    end\n  end\n\n  describe '#members' do\n    it 'should raise unsupported operation error' do\n      expect { resource.members }.to raise_error(\n        Yaks::UnsupportedOperationError, \"Only Yaks::CollectionResource has members\"\n      )\n    end\n  end\n\n  describe \"#merge_attributes\" do\n    let(:init_opts) {{attributes: {foo: 1, bar: 2}}}\n    it \"should merge attributes into any existing attributes\" do\n      expect(resource.merge_attributes(bar: 3, baz: 4))\n        .to eql Yaks::Resource.new(attributes: {foo: 1, bar: 3, baz: 4})\n    end\n  end\n\n  describe '#with_collection' do\n    it 'should be a no-op' do\n      expect(described_class.new.with_collection([:foo])).to eql Yaks::Resource.new\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/runner_spec.rb",
    "content": "RSpec.describe Yaks::Runner do\n  subject(:runner) {\n    described_class.new(object: object, config: config, options: options)\n  }\n\n  let(:object) { Object.new }\n  let(:config) { Yaks.new }\n  let(:options) { {} }\n\n  shared_examples 'high-level runner test' do\n    let(:object) { 7 }\n\n    let(:options) {\n      {\n        env: {foo: \"from_env\"},\n        hooks: [[:after, :step2, :upcase, ->(x, _env) { x.upcase }]]\n      }\n    }\n\n    let(:runner) {\n      Class.new(described_class) do\n        def steps\n          [ [:step1, proc { |x| x + 35 }],\n            [:step2, proc { |x, env| \"#{env[:foo]}[#{x} #{x}]\" }] ]\n        end\n      end.new(object: object, config: config, options: options)\n    }\n\n    it 'should go through all the steps' do\n      expect(runner.call).to eql \"FROM_ENV[42 42]\"\n    end\n  end\n\n  describe '#call' do\n    include_examples 'high-level runner test'\n  end\n\n  describe '#process' do\n    include_examples 'high-level runner test'\n  end\n\n  describe '#format' do\n    let(:object) {\n      Yaks::Resource.new(attributes: {ronny: :jonny})\n    }\n\n    let(:options) {\n      {\n        env: {\n          'api.key_prefix' => 'pre_'\n        },\n        hooks: [\n          [:before,\n           :format,\n           :add_kristel,\n           ->(resource, _env) {\n             resource.merge_attributes(kristel: :christa)\n           },\n          ],\n          [:after,\n           :primitivize,\n           :add_prefix,\n           ->(hsh, env) {\n             hsh.each_with_object({}) do |(k, v), h|\n               h[env['api.key_prefix'] + k] = v\n             end\n           }\n          ]\n        ]\n      }\n    }\n\n    it 'should run the formatter and primitivizer plus hooks' do\n      expect(runner.format).to eql(\"pre_kristel\" => \"christa\", \"pre_ronny\" => \"jonny\")\n    end\n  end\n\n  describe '#read' do\n    let(:object) {\n      '{\"pre_ronny\": \"jonny\"}'\n    }\n\n    let(:options) {\n      {\n        env: {\n          'api.key_prefix' => 'pre_'\n        },\n        hooks: [\n          [:after,\n           :read,\n           :add_kristel,\n           ->(resource, _env) {\n             resource.merge_attributes(kristel: 'christa')\n           },\n          ],\n          [:before,\n           :parse,\n           :strip_prefix,\n           ->(json, env) {\n             json.gsub(env['api.key_prefix'], '')\n           }\n          ]\n        ]\n      }\n    }\n\n    it 'should run the parser and reader plus hooks' do\n      expect(runner.read)\n        .to eql Yaks::Resource.new(attributes: {ronny: \"jonny\", kristel: \"christa\"})\n    end\n  end\n\n  describe '#context' do\n    it 'should contain the policy, env, and an empty mapper_stack' do\n      expect(runner.context).to eql(policy: config.policy, env: {}, mapper_stack: [])\n    end\n\n    context 'with an item mapper' do\n      let(:options) { {item_mapper: :foo} }\n\n      it 'should contain the item_mapper' do\n        expect(runner.context).to eql(policy: config.policy, env: {}, mapper_stack: [], item_mapper: :foo)\n      end\n    end\n  end\n\n  describe '#format_class' do\n    let(:config) do\n      Yaks.new do\n        default_format :collection_json\n      end\n    end\n\n    let(:rack_env) {\n      {'HTTP_ACCEPT' => 'application/hal+json;q=0.8, application/vnd.api+json'}\n    }\n\n    it 'should fall back to the default when no HTTP_ACCEPT key is present' do\n      runner = described_class.new(object: nil, config: config, options: {env: {}})\n      expect(runner.format_class).to equal Yaks::Format::CollectionJson\n    end\n\n    it 'should detect format based on accept header' do\n      rack_env = {'HTTP_ACCEPT' => 'application/hal+json;q=0.8, application/vnd.api+json'}\n      runner = described_class.new(object: nil, config: config, options: {env: rack_env})\n      expect(runner.format_class).to equal Yaks::Format::JsonAPI\n    end\n\n    it 'should know to pick the best match' do\n      rack_env = {'HTTP_ACCEPT' => 'application/hal+json;q=0.8, application/vnd.api+json;q=0.7'}\n      runner = described_class.new(object: nil, config: config, options: {env: rack_env})\n      expect(runner.format_class).to equal Yaks::Format::Hal\n    end\n\n    it 'should pick the one given in the options if no header matches' do\n      rack_env = {'HTTP_ACCEPT' => 'text/xml, application/json'}\n      runner = described_class.new(object: nil, config: config, options: {format: :hal, env: rack_env})\n      expect(runner.format_class).to equal Yaks::Format::Hal\n    end\n\n    it 'should fall back to the default when no mime type is recognized' do\n      rack_env = {'HTTP_ACCEPT' => 'text/xml, application/json'}\n      runner = described_class.new(object: nil, config: config, options: {env: rack_env})\n      expect(runner.format_class).to equal Yaks::Format::CollectionJson\n    end\n  end\n\n  describe '#format_name' do\n    context 'with no format specified' do\n      it 'should default to :hal' do\n        expect(runner.format_name).to eql :hal\n      end\n    end\n\n    context 'with a default format specified' do\n      let(:config) { Yaks.new { default_format :collection_json } }\n\n      context 'with a format in the options' do\n        let(:options) { {format: :json_api} }\n        it 'should give preference to that one' do\n          expect(runner.format_name).to eql :json_api\n        end\n      end\n\n      context 'without a format in the options' do\n        it 'should take the specified default' do\n          expect(runner.format_name).to eql :collection_json\n        end\n      end\n    end\n  end\n\n  describe '#formatter' do\n    let(:config) {\n      Yaks.new do\n        default_format :json_api\n        format_options :json_api, format_option: [:foo]\n      end\n    }\n\n    let(:formatter) { runner.formatter }\n\n    it 'should create a formatter based on class and options' do\n      expect(formatter).to be_a Yaks::Format::JsonAPI\n      expect(formatter.send(:options)).to eql(format_option: [:foo])\n    end\n\n    it 'should memoize' do\n      expect(runner.formatter).to be runner.formatter\n    end\n  end\n\n  describe '#env' do\n    describe 'when env is set in the options' do\n      let(:options) { {env: 123} }\n\n      it 'returns the env passed in' do\n        expect(runner.env).to be 123\n      end\n    end\n\n    describe 'when no env is given' do\n      it 'falls back to an empty hash' do\n        expect(runner.env).to eql({})\n      end\n    end\n  end\n\n  describe '#mapper' do\n    context 'with an explicit mapper in the options' do\n      let(:mapper_class) { Class.new(Yaks::Mapper) }\n      let(:options) { {mapper: mapper_class} }\n\n      it 'should take the mapper from options' do\n        expect(runner.mapper).to be_a mapper_class\n      end\n    end\n\n    context 'without a mapper specified' do\n      let(:object) { Pet.new(id: 7, name: 'fifi', species: 'cat') }\n\n      it 'should infer one from the object to be mapped' do\n        expect(runner.mapper).to be_a PetMapper\n      end\n\n      it 'should pass the context to the mapper' do\n        expect(runner.mapper.context).to be runner.context\n      end\n    end\n  end\n\n  describe '#serializer' do\n    context 'with a serializer configured' do\n      let(:config) {\n        Yaks.new do\n          serializer(:json) do |input|\n            \"serialized #{input}\"\n          end\n        end\n      }\n\n      it 'should try to find an explicitly configured serializer' do\n        expect(runner.serializer.call('42', {})).to eql 'serialized 42'\n      end\n    end\n\n    it 'should fall back to the policy' do\n      expect(runner.serializer.call([1, 2, 3], {})).to eql \"[\\n  1,\\n  2,\\n  3\\n]\"\n    end\n  end\n\n  describe '#steps' do\n    let(:options) {{mapper: Yaks::Mapper}}\n\n    it 'should have all four steps' do\n      expect(runner.steps).to eql [\n        [ :map, runner.mapper ],\n        [ :format, runner.formatter ],\n        [ :primitivize, runner.primitivizer],\n        [ :serialize, runner.serializer ]\n      ]\n    end\n  end\n\n  describe '#primitivizer' do\n    describe 'with a non json based format' do\n      let(:config) do\n        Yaks.new do\n          default_format :html\n        end\n      end\n\n      it 'should return the identity function' do\n        expect(runner.primitivizer.call(:foo)).to eql :foo\n      end\n    end\n\n    describe 'with a json based format' do\n      it 'should return the primitivizer' do\n        expect(runner.primitivizer.call(:foo)).to eql \"foo\"\n      end\n    end\n  end\n\n  describe '#hooks' do\n    let(:config) {\n      super().after(:map, :this_happens_after_map)\n    }\n\n    it 'should contain the hooks from the config' do\n      expect(runner.hooks).to eql [[:after, :map, :this_happens_after_map, nil]]\n    end\n\n    context 'with extra blocks in the options' do\n      let(:options) { {hooks: [[:foo]]} }\n\n      it 'should combine the hooks' do\n        expect(runner.hooks).to eql [[:after, :map, :this_happens_after_map, nil], [:foo]]\n      end\n    end\n  end\n\n  describe '#map' do\n    let(:mapper_class) do\n      Struct.new(:options) do\n        include Yaks::FP::Callable\n        def call(obj, _env)\n          \"mapped[#{obj}]\"\n        end\n      end\n    end\n\n    let(:options) {\n      {\n        mapper: mapper_class,\n        env: {'api.prefix' => 'pre_'}\n      }\n    }\n\n    let(:object) { \"foo\" }\n\n    it 'should only run the mapper' do\n      expect(runner.map).to eql \"mapped[foo]\"\n    end\n\n    context 'with a hook on the :map step' do\n      let(:config)  do\n        Yaks.new do\n          around(:map) do |res, env, &block|\n            \"#{env['api.prefix']}around[#{block.call(res, env)}]\"\n          end\n        end\n      end\n\n      it 'should invoke the hook and pass in the env' do\n        expect(runner.map).to eql \"pre_around[mapped[foo]]\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/serializer_spec.rb",
    "content": "RSpec.describe Yaks::Serializer do\n  after do\n    Yaks::Serializer.instance_variable_set(\"@serializers\", nil)\n  end\n\n  it 'allows registering serializers' do\n    Yaks::Serializer.register(:some_format, :some_serializer)\n    expect(Yaks::Serializer.all[:some_format]).to equal :some_serializer\n  end\n\n  it 'should by default have a serializer for JSON' do\n    expect(Yaks::Serializer.all[:json].call([1, 2, 3], {})).to eql \"[\\n  1,\\n  2,\\n  3\\n]\"\n  end\n\n  it 'should warn when registering a key again' do\n    expect { Yaks::Serializer.register(:json, :foo) }.to raise_exception /Serializer for json already registered/\n  end\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/util_spec.rb",
    "content": "RSpec.describe Yaks::Util do\n  include Yaks::Util\n\n  describe '#Resolve' do\n    it 'should return non-proc-values' do\n      expect(Resolve('foo')).to eql 'foo'\n    end\n\n    it 'should resolve a proc' do\n      expect(Resolve(->{ 123 })).to eql 123\n    end\n\n    it 'should resolve the proc in the given context' do\n      expect(Resolve(->{ upcase }, 'foo')).to eql 'FOO'\n    end\n\n    it 'should resolve a proc without context in the context it was lexically defined' do\n      expect(Resolve(->{ self })).to be_a RSpec::Core::ExampleGroup\n    end\n\n    it 'should receive the context as an argument when it has an arity > 0' do\n      expect(Resolve(->(s){ s.upcase }, 'foo')).to eql 'FOO'\n    end\n\n    it 'should work with method objects' do\n      expect(Resolve('foo'.method(:upcase))).to eql 'FOO'\n    end\n\n    it 'should resolve a symbol to itself' do\n      expect(Resolve(:foo)).to eql :foo\n    end\n\n    it 'should resolve custom callables by calling to_proc first' do\n      expect(Resolve(fake(to_proc: ->{->{3}}))).to eql 3\n    end\n  end\n\n  describe '#camelize' do\n    it 'should camelize' do\n      expect(camelize('foo_bar_moo/baz/booz')).to eql 'FooBarMoo::Baz::Booz'\n    end\n  end\n\n  describe '#underscore' do\n    it 'should underscorize' do\n      expect(underscore('FooBar::Baz-Quz::Quux')).to eql 'foo_bar/baz__quz/quux'\n    end\n  end\n\n  describe '#slice_hash' do\n    it 'should retain the given keys from a hash' do\n      expect(slice_hash({a: 1, b: 2, c: 3}, :a, :c, :d)).to eql(a: 1, c: 3)\n    end\n  end\n\n  describe '#reject_keys' do\n    it 'should reject specific keys from a hash' do\n      expect(reject_keys({foo: 1, bar: 2}, :foo)).to eql(bar: 2)\n    end\n  end\n\n  describe \"#symbolize_keys\" do\n    it \"should turn string keys into symbols\" do\n      expect(symbolize_keys('foo' => 1, 'bar' => 2)).to eql(foo: 1, bar: 2)\n    end\n  end\n\n  describe '#extract_options' do\n    it 'should extract a final hash - one arg given' do\n      args, opts = extract_options([:a, {hello: :world}])\n      expect([args, opts]).to eql [[:a], {hello: :world}]\n    end\n\n    it 'should extract a final hash - multi arg given' do\n      args, opts = extract_options([:a, :b, :c, {hello: :world}])\n      expect([args, opts]).to eql [[:a, :b, :c], {hello: :world}]\n    end\n\n    it 'should provide an empty hash if none was given' do\n      args, opts = extract_options([:a, :b])\n      expect([args, opts]).to eql [[:a, :b], {}]\n    end\n  end\nend\n\nRSpec.describe Yaks::Util::Deprecated, '#deprecated_alias' do\n  let(:klass) {\n    Class.new do\n      extend Yaks::Util::Deprecated\n\n      def self.to_s\n        'FancyClass'\n      end\n      def foo(x)\n        \"#{x}yz#{yield}\"\n      end\n      deprecated_alias :bar, :foo\n    end\n  }\n\n  def capture_stderr\n    stderr, $stderr = $stderr, StringIO.new\n    yield\n    io, $stderr = $stderr, stderr\n    io.string\n  end\n\n  it 'should set up an alias' do\n    capture_stderr do\n      expect(klass.new.bar('x') { 'a' }).to eql 'xyza'\n    end\n  end\n\n  it 'should output a warning' do\n    expect(\n      capture_stderr do\n        expect(klass.new.bar('x') {'a'}).to eql 'xyza'\n      end\n    ).to match %r{WARNING: FancyClass#bar is deprecated, use `foo'\\. at /.*util_spec.rb:#{__LINE__ - 2}:in}\n  end\nend\n"
  },
  {
    "path": "yaks/spec/yaml/confucius.yaml",
    "content": "--- !ruby/object:Scholar\nid: 9\nname: \"孔子\"\npinyin: \"Kongzi\"\nlatinized: \"Confucius\"\nworks:\n  - !ruby/object:Work\n    id: 11\n    chinese_name: \"論語\"\n    english_name: \"Analects\"\n    era: !ruby/object:Era\n      id: 99\n      name: \"Zhou Dynasty\"\n    quotes:\n      - !ruby/object:Quote\n        id: 17\n        chinese: \"廄焚。子退朝，曰：“傷人乎？” 不問馬。\"\n        english: \"When the stables were burnt down, on returning from court Confucius said, “Was anyone hurt?” He did not ask about the horses.\"\n        sources: \"Analects X.11 (tr. Waley), 10–13 (tr. Legge), or X-17 (tr. Lau)\"\n      - !ruby/object:Quote\n        id: 18\n        chinese: \"子曰：“其恕乎！己所不欲、勿施於人。”\"\n        english: \"The Master replied: “How about 'reciprocity'! Never impose on others what you would not choose for yourself.”\"\n        sources: \"Analects XV.24, tr. David Hinton\"\n  - !ruby/object:Work\n    id: 12\n    chinese_name: \"易經\"\n    english_name: \"Commentaries to the Yi-jing\"\n"
  },
  {
    "path": "yaks/spec/yaml/list_of_quotes.yaml",
    "content": "- !ruby/object:Quote\n  id: 17\n  chinese: \"廄焚。子退朝，曰：“傷人乎？” 不問馬。\"\n  english: \"When the stables were burnt down, on returning from court Confucius said, “Was anyone hurt?” He did not ask about the horses.\"\n  sources: \"Analects X.11 (tr. Waley), 10–13 (tr. Legge), or X-17 (tr. Lau)\"\n- !ruby/object:Quote\n  id: 18\n  chinese: \"子曰：“其恕乎！己所不欲、勿施於人。”\"\n  english: \"The Master replied: “How about 'reciprocity'! Never impose on others what you would not choose for yourself.”\"\n  sources: \"Analects XV.24, tr. David Hinton\"\n- !ruby/object:Quote\n  id: 99\n  english: \"How do you know they are happy? You are not a fish.\""
  },
  {
    "path": "yaks/spec/yaml/youtypeitwepostit.yaml",
    "content": "---\n- !ruby/object:Youtypeitwepostit::Message\n  id: 12091295723803341\n  text: \"massage\"\n  date_posted: \"2014-05-29T07:56:58.035Z\"\n- !ruby/object:Youtypeitwepostit::Message\n  id: 613856331910938\n  text: \"Squid!\"\n  date_posted: \"2013-03-28T21:51:08.406Z\"\n"
  },
  {
    "path": "yaks/yaks.gemspec",
    "content": "# encoding: utf-8\n\nrequire 'English'\nrequire File.expand_path('../lib/yaks/version', __FILE__)\nrequire File.expand_path('../lib/yaks/breaking_changes', __FILE__)\n\nGem::Specification.new do |gem|\n  gem.name        = 'yaks'\n  gem.version     = Yaks::VERSION\n  gem.authors     = [ 'Arne Brasseur' ]\n  gem.email       = [ 'arne@arnebrasseur.net' ]\n  gem.description = 'Serialize to hypermedia. HAL, JSON-API, etc.'\n  gem.summary     = gem.description\n  gem.homepage    = 'https://github.com/plexus/yaks'\n  gem.license     = 'MIT'\n\n  gem.require_paths    = %w[lib]\n  gem.files            = `git ls-files`.split($INPUT_RECORD_SEPARATOR)\n  gem.test_files       = gem.files.grep(/^spec/)\n  gem.extra_rdoc_files = %w[README.md]\n\n  if Yaks::BreakingChanges.key? Yaks::VERSION\n    gem.post_install_message = Yaks::BreakingChanges[Yaks::VERSION]\n  end\n\n  gem.required_ruby_version = '>= 1.9.3'\n\n  gem.add_runtime_dependency 'abstract_type', '~> 0.0.7'\n  gem.add_runtime_dependency 'adamantium',    '~> 0.2.0'\n  gem.add_runtime_dependency 'anima',         '~> 0.3.0'\n  gem.add_runtime_dependency 'attribs',       '~> 1.0'\n  gem.add_runtime_dependency 'concord',       '~> 0.1.4'\n  gem.add_runtime_dependency 'inflection',    '~> 1.0'\n  gem.add_runtime_dependency 'rack-accept',   '~> 0.4.5'\n  gem.add_runtime_dependency 'uri_template',  '~> 0.6.0'\n\n  gem.add_development_dependency 'ataru'\n  gem.add_development_dependency 'benchmark-ips'\n  gem.add_development_dependency 'bogus'\n  gem.add_development_dependency 'hamster'\n  if RUBY_VERSION >= \"2.1.0\"\n    gem.add_development_dependency 'mutant'\n    gem.add_development_dependency 'mutant-rspec'\n  end\n  gem.add_development_dependency 'rake'\n  gem.add_development_dependency 'rubocop'\n  gem.add_development_dependency 'rspec', '~> 3.0'\n  gem.add_development_dependency 'rspec-its'\n  gem.add_development_dependency 'virtus'\n  gem.add_development_dependency 'yaks-html'\n  gem.add_development_dependency 'yard'\nend\n"
  },
  {
    "path": "yaks-html/README.md",
    "content": "A HTML output format for Yaks.\n\nBrowse your hypermedia API like a good old fashioned web site.\n\nYou can see an example output at\n[https://myticketsireland.ticketsolve.com/api/](https://myticketsireland.ticketsolve.com/api/)\n(visit the [front page](https://myticketsireland.ticketsolve.com/)\nfirst to make sure you have some necessary cookies).\n\nFor APIs that make good use of links and forms this provides a great\nhelp during development. It also makes it possible to write\nintegration tests against the HTML output using Capybara.\n\nWe currently provide a small DSL and RSpec integration to make this easy. For example:\n\n```\nrequire 'yaks-html/rspec'\n\nRSpec.describe Yaks::Format::HTML, type: :yaks_integration do\n  before do\n    Capybara.app = YourRackApp\n  end\n\n  let(:rel_prefix) { 'http://myapi.example.com/rel/' }\n\n  it 'should allow browsing the api' do\n    visit '/'\n\n    click_rel('friends') # => clicks http://myapi.example.com/rel/friends\n\n    expect(current_path).to eql '/friends'\n\n    expect(page).to have_content 'Matt'\n\n    submit_form(:poke) do\n      fill_in :message, with: 'Would you fancy some tea?'\n    end\n\n    expect(current_path).to eql '/poke/Matt'\n\n    expect(page).to have_content 'You poked Matt: Would you fancy some tea?'\n  end\nend\n```\n"
  },
  {
    "path": "yaks-html/Rakefile",
    "content": "load '../shared/rake_tasks.rb'\n\ngem_tasks(:\"yaks-html\")\n"
  },
  {
    "path": "yaks-html/lib/yaks/format/html.rb",
    "content": "# -*- coding: utf-8 -*-\n\nmodule Yaks\n  class Format\n    class HTML < self\n      include Util\n\n      register :html, :html, 'text/html'\n\n      def template\n        @template ||= Hexp.parse(File.read(File.expand_path('../template.html', __FILE__)))\n      end\n\n      def section(name)\n        template.select(\".#{name}\").first\n      end\n\n      def serialize_resource(resource)\n        template.replace('.resource') do |_|\n          render(resource)\n        end.replace('.yaks-version') do |ver|\n          ver.content(Yaks::VERSION)\n        end.replace('.request-info') do |req|\n          if env['REQUEST_METHOD'] && env['PATH_INFO']\n            req.content(env['REQUEST_METHOD'], ' ', env['PATH_INFO'])\n          else\n            req\n          end\n        end\n      end\n\n      def render(*args)\n        object = args.first\n        type = object.class.name.split('::').last\n        send(\"render_#{underscore(type)}\", *args)\n      end\n\n      def render_resource(resource, templ = section('resource'))\n        templ\n          .replace('.type') { |header| header.content(resource.type.to_s + (resource.collection? ? ' collection' : '')) }\n          .replace('.attribute', &render_attributes(resource.attributes))\n          .replace('.links') {|links| resource.links.empty? ? [] : links.replace('.link', &render_links(resource.links)) }\n          .replace('.forms') {|div| render_forms(resource.forms).call(div) }\n          .replace('.subresource') {|sub_templ| render_subresources(resource, templ, sub_templ) }\n      end\n      alias_method :render_collection_resource, :render_resource\n      alias_method :render_null_resource, :render_resource\n\n      def render_attributes(attributes)\n        ->(templ) do\n          attributes.map do |key, value|\n            templ\n              .replace('.name')  {|x| x.content(key.to_s) }\n              .replace('.value') {|x| x.content(value.inspect) }\n          end\n        end\n      end\n\n      def rel_href(rel)\n        if rel.is_a?(Symbol)\n          \"http://www.iana.org/assignments/link-relations/link-relations.xhtml\"\n        else\n          rel.to_s\n        end\n      end\n\n      def render_links(links)\n        ->(templ) do\n          links.map do |link|\n            templ\n              .replace('.rel a') {|a|\n                a.attr('href', rel_href(link.rel)).content(link.rel.to_s)\n              }\n              .replace('.uri a') {|a|\n                a.attr('href', link.uri).content(link.uri)\n                  .attr('rel', link.rel.to_s)\n              }\n              .replace('.title') {|x| x.content(link.title.to_s) }\n              .replace('.templated') {|x| x.content(link.templated?.inspect) }\n          end\n        end\n      end\n\n      def render_subresources(resource, templ, sub_templ)\n        templ = templ\n                .replace('h1,h2,h3,h4') {|h| h.set_tag(\"h#{h.tag[/\\d/].to_i.next}\") }\n                .add_class('collapsed')\n        if resource.collection?\n          resource.seq.map do |r|\n            render(r, templ)\n          end\n        else\n          resource.subresources.map do |resources|\n            rel = resources.rels.first\n            sub_templ\n              .replace('.rel a') {|a| a.attr('href', rel_href(rel)).content(rel.to_s) }\n              .replace('.value') {|x| x.content(resources.seq.map { |res| render(res, templ) })}\n              .attr('rel', rel.to_s)\n          end\n        end\n      end\n\n      def render_forms(forms)\n        ->(div) do\n          div.content(\n            forms.map(&method(:render))\n          )\n        end\n      end\n\n      def render_form(form_control)\n        form = H[:form]\n        form = form.attr('name', form_control.name)          if form_control.name\n        form = form.attr('method', form_control.method)      if form_control.method\n        form = form.attr('action', form_control.action)      if form_control.action\n        form = form.attr('enctype', form_control.media_type) if form_control.media_type\n\n        rows = form_control.fields.map(&method(:render))\n        rows << H[:tr, H[:td], H[:td, H[:input, {type: 'submit'}]]]\n\n        H[:div,\n          H[:h4, form_control.title || form_control.name.to_s],\n          form.content(H[:table, rows])]\n      end\n\n      def render_field(field)\n        attrs = field.to_h_compact\n\n        if attrs.key? :checked\n          if attrs[:checked]\n            attrs[:checked] = 'checked'\n          else\n            attrs.delete(:checked)\n          end\n        end\n\n        extra_info = reject_keys(attrs, :type, :name, :value, :label, :options)\n        H[:tr,\n          H[:td,\n            H[:label, {for: field.name}, [field.label || field.name.to_s, field.required ? '*' : ''].join]],\n          H[:td,\n            case field.type\n            when /select/\n              H[:select, reject_keys(attrs, :options), render_select_options(field.options)]\n            when /textarea/\n              H[:textarea, reject_keys(attrs, :value), field.value || '']\n            when /hidden/\n              [ field.value.inspect,\n                H[:input, attrs]\n              ]\n            else\n              H[:input, attrs]\n            end],\n          H[:td, extra_info.empty? ? '' : extra_info.inspect]\n         ]\n      end\n\n      def render_fieldset(fieldset)\n        legend = fieldset.fields.find {|field| field.type == :legend}\n        fields = fieldset.fields.reject {|field| field.type == :legend}\n        legend = legend ? legend.label : ''\n\n        H[:tr,\n          H[:th, legend],\n          H[:td, H[:fieldset, H[:table, fields.map(&method(:render))]]]]\n      end\n\n      def render_select_options(options)\n        options.map do |o|\n          H[:option, reject_keys(o.to_h_compact, :label), o.label]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks-html/lib/yaks/format/template.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <style type=\"text/css\">\n      * {\n        box-sizing: border-box;\n      }\n\n      body {\n        background-color: #F9E9DE;\n        margin: 0;\n        font-family: sans-serif;\n      }\n\n      .resource {\n        margin-left: 0.3em;\n        padding-left: 0.3em;\n      }\n\n      body .resource {\n        padding-right: 0.5em;\n      }\n\n      .resource .resource {\n        border-left: 4px solid #bababa;\n      }\n\n      h1,h2,h3,h4,h5 {\n        background-color: #FFB662;\n        padding: 0.2em;\n        margin-bottom: 0.2em;\n      }\n\n      .attributes {\n        margin-bottom: 0.2em;\n      }\n\n      th { background-color: #FFD19D; }\n      td { background-color: #E2ECBA; }\n\n      .rel a {\n        color: black;\n        text-decoration: none;\n      }\n\n      .subresource {\n        padding-top: 1em;\n      }\n\n      .subresource-rel {\n        background-color: #FFB662;\n        padding: 0.5em;\n        margin-bottom: 0.5em;\n      }\n\n      .header, .footer {\n        width: 100%;\n        background-color: black;\n        color: white;\n        padding: 0.3em;\n      }\n\n      .footer a {\n        text-decoration: none;\n        color: white;\n      }\n\n      .footer {\n        margin-top: 1em;\n      }\n\n      .collapsed .heading::before, .expanded .heading::before {\n        font-size: 0.6em;\n        display: block;\n        float: left;\n        margin-top: 0.4em;\n        margin-right: 0.5em;\n      }\n\n      .collapsed .heading::before {\n        content: \"▶\";\n      }\n\n      .expanded .heading::before {\n        content: \"▼\";\n      }\n\n      .collapsed .body {\n        display: none;\n      }\n    </style>\n  </head>\n  <body>\n    <div class=\"header request-info\">\n    </div>\n\n    <div class=\"resource\">\n      <h1 class=\"heading type\">Resource Type</h1>\n\n      <div class=\"resource-data body\">\n        <table class=\"attributes\">\n          <tr class=\"attribute\"><td class=\"name\">name</td><td class=\"value\">value</td></tr>\n        </table>\n\n        <table class=\"links\">\n          <tr>\n            <th>Rel</th>\n            <th>URI</th>\n            <th>Title</th>\n            <th>Templated</th>\n          </tr>\n\n          <tr class=\"link\">\n            <td class=\"rel\"><a href=\"\">rel</a></td>\n            <td class=\"uri\"><a href=\"\">uri</a></td>\n            <td class=\"title\">title</td>\n            <td class=\"templated\">false</td>\n          </tr>\n        </table>\n\n        <div class=\"forms\"></div>\n\n        <div class=\"subresource collapsed\">\n          <div class=\"subresource-rel rel heading\"><a></a></div>\n          <div class=\"value body\" class=\"collapsed\"></div>\n        </div>\n\n      </div>\n    </div>\n\n    <div class=\"footer\">\n      generated with <a href=\"https://github.com/plexus/yaks\">Yaks <span class=\"yaks-version\"></span></a>\n    </div>\n\n    <script>\n      function setupCollapseExpand() {\n        var nodes = document.getElementsByClassName(\"heading\");\n\n        for (var i=0; i < nodes.length; i++) {\n          var node = nodes[i];\n          node.onclick = function() {\n            var parent = this.parentNode;\n            if (parent.className.indexOf(\"collapsed\") >= 0) {\n              parent.className = parent.className.replace(\"collapsed\", \"expanded\");\n            } else {\n              parent.className = parent.className.replace(\"expanded\", \"collapsed\");\n            }\n          };\n        }\n      };\n\n      if (window.addEventListener) {\n        window.addEventListener(\"load\", setupCollapseExpand, false);\n      } else if (window.attachEvent) {\n        window.attachEvent(\"onload\", setupCollapseExpand);\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "yaks-html/lib/yaks-html/rspec.rb",
    "content": "require 'yaks-html'\nrequire 'capybara/rspec'\n\nmodule YaksHTML\n  # This is a bit of a hack. The only way to add custom rack env\n  # entries to Capybara/Rack::Test is through the driver options (see\n  # Capybara.register_driver below). However this is only executed\n  # once, so throughout the whole test suite the same instance of the\n  # hash we pass will be used. To make it a bit easier to set/reset\n  # this we add this layer of indirection. Now what we pass the driver\n  # looks like a hash, but we can point it to a new hash with\n  # RACK_ENV.__setobj__({})\n\n  RACK_ENV = SimpleDelegator.new({})\n\n  module CapybaraDSL\n    def self.included(base)\n      base.class_eval do\n        let(:rel_prefix) { '' }\n      end\n    end\n\n    def click_rel(rel)\n      rel = [rel_prefix, rel].join unless rel.is_a? Symbol\n      find(\"a[rel=\\\"#{rel}\\\"]\").click\n    end\n\n    def click_first_rel(rel)\n      rel = [rel_prefix, rel].join unless rel.is_a? Symbol\n      all(\"a[rel=\\\"#{rel}\\\"]\").first.click\n    end\n\n    def submit!\n      find('input[type=\"submit\"]').click\n    end\n\n    def within_form(name, &block)\n      within(find_form(name), &block)\n    end\n\n    def submit_form(name, &block)\n      within(find_form(name)) do\n        yield block\n        submit!\n      end\n    end\n\n    def refresh\n      visit current_path\n    end\n\n    def env\n      YaksHTML::RACK_ENV\n    end\n\n    private\n\n    def find_form(name)\n      forms = all(\"form[@name=\\\"#{name}\\\"]\")\n\n      if forms.empty?\n        fname = \"/tmp/page-#{rand(999999999999999)}.html\"\n        File.write(fname, page.body)\n        raise \"No form found with name #{name}. Page saved as #{fname}\"\n      end\n\n      forms.first\n    end\n  end\nend\n\nRSpec.configure do |config|\n  select = {type: :yaks_integration}\n\n  config.include Capybara::DSL, select\n  config.include Capybara::RSpecMatchers, select\n\n  config.include YaksHTML::CapybaraDSL, select\n\n  config.before(select) do\n    YaksHTML::RACK_ENV.__setobj__('HTTP_ACCEPT' => 'text/html')\n  end\nend\n\nCapybara.register_driver :rack_test do |app|\n  Capybara::RackTest::Driver.new(app, headers: YaksHTML::RACK_ENV)\nend\n"
  },
  {
    "path": "yaks-html/lib/yaks-html.rb",
    "content": "require 'hexp'\nrequire 'yaks'\nrequire 'yaks/format/html'\n\n-> do\n  unparser = Hexp::Unparser.new(no_escape: [:script, :style])\n  Yaks::Serializer.register(:html, ->(data, _env) { unparser.call(data) })\nend.call\n"
  },
  {
    "path": "yaks-html/spec/smoke_test_spec.rb",
    "content": "require 'spec_helper'\nrequire_relative 'support/test_app'\n\nRSpec.describe Yaks::Format::HTML, type: :yaks_integration do\n  before do\n    Capybara.app = TestApp\n  end\n\n  let(:rel_prefix) { 'http://myapi.example.com/rel/' }\n\n  it 'should allow browsing the api' do\n    visit '/'\n\n    expect(page).to have_content 'GET /'\n    expect(page).to have_content 'generated with Yaks'\n\n    click_rel('friends')\n\n    expect(current_path).to eql '/friends'\n\n    expect(page).to have_content 'GET /friends'\n    expect(page).to have_content 'Matt'\n\n    submit_form(:poke) do\n      fill_in :message, with: 'Free the means of production'\n    end\n\n    expect(current_path).to eql '/poke/Matt'\n\n    expect(page).to have_content 'You poked Matt: Free the means of production'\n  end\nend\n"
  },
  {
    "path": "yaks-html/spec/spec_helper.rb",
    "content": "require 'yaks'\nrequire 'yaks-html/rspec'\nrequire 'yaks-sinatra'\n\nrequire_relative '../../shared/rspec_config'\n"
  },
  {
    "path": "yaks-html/spec/support/test_app.rb",
    "content": "class TestApp < Sinatra::Base\n  register Yaks::Sinatra\n\n  class HomeMapper < Yaks::Mapper\n    link 'http://myapi.example.com/rel/friends', '/friends'\n  end\n\n  class FriendMapper < Yaks::Mapper\n    attribute :name\n\n    def name\n      object[:name]\n    end\n\n    form :poke do\n      action '/poke/{name}'\n      method 'POST'\n      text :message\n    end\n  end\n\n  class MessageMapper < Yaks::Mapper\n    attribute :message do\n      object[:message]\n    end\n  end\n\n  configure_yaks do\n    mapper_for :home, HomeMapper\n    rel_template 'http://myapi.example.com/rel/{rel}'\n  end\n\n  get '/' do\n    yaks :home\n  end\n\n  get '/friends' do\n    yaks [{name: 'Matt'}, {name: 'Yohan'}, {name: 'Janko'}], item_mapper: FriendMapper\n  end\n\n  post '/poke/:name' do\n    yaks({message: \"You poked #{params[:name]}: #{params[:message]}\"}, mapper: MessageMapper)\n  end\nend\n"
  },
  {
    "path": "yaks-html/yaks-html.gemspec",
    "content": "# encoding: utf-8\n\nrequire 'English'\nrequire File.expand_path('../../yaks/lib/yaks/version', __FILE__)\n\nGem::Specification.new do |gem|\n  gem.name        = 'yaks-html'\n  gem.version     = Yaks::VERSION\n  gem.authors     = [ 'Arne Brasseur' ]\n  gem.email       = [ 'arne@arnebrasseur.net' ]\n  gem.description = 'HTML output format for Yaks'\n  gem.summary     = gem.description\n  gem.homepage    = 'https://github.com/plexus/yaks'\n  gem.license     = 'MIT'\n\n  gem.require_paths    = %w[lib]\n  gem.files            = `git ls-files`.split($INPUT_RECORD_SEPARATOR)\n  gem.test_files       = gem.files.grep(/^spec/)\n  gem.extra_rdoc_files = %w[README.md]\n\n  gem.required_ruby_version = '>= 1.9.3'\n\n  gem.add_runtime_dependency 'yaks', Yaks::VERSION\n  gem.add_runtime_dependency 'hexp', '>= 0.4'\n\n  gem.add_development_dependency 'yaks-sinatra'\n  gem.add_development_dependency 'rspec', '~> 3.0'\n  gem.add_development_dependency 'rack-test'\n  gem.add_development_dependency 'capybara'\nend\n"
  },
  {
    "path": "yaks-sinatra/.rspec",
    "content": "-r spec_helper"
  },
  {
    "path": "yaks-sinatra/README.md",
    "content": "Provide basic integration for using Yaks in sinatra. It gives you a top level `configure_yaks` method, and a `yaks` helper for use in routes.\n\nThis will register all media types known to Yaks, make sure the right one is picked based on the `Accept` header, and it will put the correct `Content-Type` header on the response.\n\n\nIn a classic Sinatra app, you use it like so:\n\n```ruby\nrequire 'sinatra'\nrequire 'yaks-sinatra'\n\nconfigure_yaks do\n  # Yaks configuration as used in Yaks.new do ; ... ; end\nend\n\nclass RootMapper < Yaks::Mapper\n  link :self, '/'\n  link :posts, '/posts'\nend\n\nclass PostMapper < Yaks::Mapper\n  attributes :title, :body, :date\n  has_one :author\nend\n\nget '/' do\n  yaks nil, mapper: RootMapper\nend\n\nget '/posts' do\n  yaks Post.all\nend\n```\n\nIn a modular Sinatra app, you need to register the extension explicitly:\n\n```ruby\nrequire 'sinatra/base'\nrequire 'yaks-sinatra'\n\nclass MyApp < Sinatra::Base\n  register Yaks::Sinatra\n\n  configure_yaks do\n    # ...\n  end\n\n  # ...\nend\n```\n"
  },
  {
    "path": "yaks-sinatra/Rakefile",
    "content": "load '../shared/rake_tasks.rb'\n\ngem_tasks(:\"yaks-sinatra\")\n"
  },
  {
    "path": "yaks-sinatra/lib/yaks-sinatra.rb",
    "content": "require 'sinatra/base'\nrequire 'yaks'\n\nmodule Yaks\n  module Sinatra\n    class << self\n      attr_accessor :yaks_config\n    end\n\n    module Helpers\n      def yaks(object, opts = {})\n        runner = Yaks::Sinatra.yaks_config.runner(object, {env: env}.merge(opts))\n        content_type runner.format_name\n        runner.call\n      end\n    end\n\n    def configure_yaks(&block)\n      Yaks::Sinatra.yaks_config = ::Yaks.new(&block)\n\n      configure do\n        ::Yaks::Format.all.each do |format|\n          mime_type format.format_name, format.media_type\n        end\n      end\n    end\n\n    def self.registered(app)\n      app.helpers(Yaks::Sinatra::Helpers)\n\n      ::Yaks::Format.all.each do |format|\n        app.settings.add_charset << format.media_type\n      end\n\n      # app.configure_yaks\n    end\n  end\nend\n\n# For classic apps\nmodule Sinatra\n  register Yaks::Sinatra\nend\n"
  },
  {
    "path": "yaks-sinatra/spec/integration/classic_app.rb",
    "content": "require 'sinatra'\nrequire 'yaks-sinatra'\n\nRoot = Class.new(Struct.new(:name))\n\nclass RootMapper < Yaks::Mapper\n  link :self, '/'\nend\n\nset :default_charset, 'utf-8'\n\nconfigure_yaks\n\nget '/' do\n  yaks Root.new('root'), mapper: RootMapper\nend\n"
  },
  {
    "path": "yaks-sinatra/spec/integration/classic_spec.rb",
    "content": "require 'integration_helper'\nrequire_relative 'classic_app.rb'\n\nRSpec.describe 'Sinatra Classic app integration', type: :integration do\n  include Yaks::Sinatra::Test::ClassicApp::Helpers\n\n  ::Yaks::Format.all.each do |format|\n    context \"For #{format.format_name}\" do\n      it \"returns 200\" do\n        make_req(format.media_type)\n        expect(last_response).to be_ok\n      end\n\n      it \"respects the Accept header\" do\n        make_req(format.media_type)\n        expect(last_content_type.type).to eq(format.media_type)\n      end\n\n      it \"returns an explicit charset\" do\n        make_req(format.media_type)\n        expect(last_content_type.charset).to eq('utf-8')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks-sinatra/spec/integration/modular_spec.rb",
    "content": "require 'integration_helper'\n\nRSpec.describe 'Sinatra Modular app integration', type: :integration do\n  include Yaks::Sinatra::Test::ModularApp::Helpers\n\n  ::Yaks::Format.all.each do |format|\n    context \"For #{format.format_name}\" do\n      it \"returns 200\" do\n        make_req(format.media_type)\n        expect(last_response).to be_ok\n      end\n\n      it \"respects the Accept header\" do\n        make_req(format.media_type)\n        expect(last_content_type.type).to eq(format.media_type)\n      end\n\n      it \"returns an explicit charset\" do\n        make_req(format.media_type)\n        expect(last_content_type.charset).to eq('utf-8')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks-sinatra/spec/integration_helper.rb",
    "content": "# encoding: utf-8\n\nrequire 'spec_helper'\nrequire 'yaks-sinatra'\nrequire 'rack/test'\n\nENV['RACK_ENV'] = 'test'\n\nclass MediaType\n  QUOTED_STRING = '[\\w!#$%&\\'()*+,-./:;<=>?@\\[\\]\\\\\\\\^`{|}~ \\t\\r\\n]+'\n  TOKEN = '[\\w!\\#$%&\\'*+-.^`|~]+'\n  TYPE_AND_SUBTYPE = \"(?<maintype>#{TOKEN})/(?<subtype>#{TOKEN})\"\n  PARAMETER_VALUE = \"(?:(?<value>#{TOKEN})|\\\"(?<q_value>#{QUOTED_STRING})\\\")\"\n  PARAMETER_RE = /\\s*;\\s*(?<name>#{TOKEN})\\s*=\\s*#{PARAMETER_VALUE}/m\n  MEDIA_TYPE_RE = /^#{TYPE_AND_SUBTYPE}/\n\n  def initialize(header_string)\n    @header_string = header_string\n  end\n\n  def type_and_rest\n    @type_and_rest ||= begin\n      type_match = MEDIA_TYPE_RE.match(@header_string)\n      return [\"\", \"\"] if type_match.nil?\n      @main_type = type_match['maintype']\n      @sub_type = type_match['subtype']\n      type = type_match.to_s\n      rest = @header_string[type.length..-1]\n      [type, rest]\n    end\n  end\n\n  def parameters\n    @parameters ||= begin\n      _, rest = type_and_rest\n      parameters = {}\n      rest.scan(PARAMETER_RE) do |name, value, q_value|\n        parameters[name] = value || q_value\n      end\n      parameters\n    end\n  end\n\n  def type\n    @type ||= type_and_rest[0]\n  end\n\n  def main_type\n    @main_type || type_and_rest && @main_type\n  end\n\n  def sub_type\n    @sub_type || type_and_rest && @sub_type\n  end\n\n  def charset\n    parameters['charset']\n  end\nend\n\nmodule Yaks\n  module Sinatra\n    module Test\n      module Helpers\n        def make_req(mime_type = 'application/hal+json')\n          header 'Accept', mime_type\n          get '/'\n        end\n\n        def last_content_type\n          MediaType.new(last_response.content_type)\n        end\n      end\n\n      class ModularApp < ::Sinatra::Base\n        module Helpers\n          include Yaks::Sinatra::Test::Helpers\n\n          def app\n            Yaks::Sinatra::Test::ModularApp\n          end\n        end\n\n        Root = Class.new(Struct.new(:name))\n\n        class RootMapper < Yaks::Mapper\n          link :self, '/'\n        end\n\n        register Yaks::Sinatra\n\n        set :default_charset, 'utf-8'\n\n        configure_yaks\n\n        get '/' do\n          yaks Root.new('root'), mapper: RootMapper\n        end\n      end\n\n      module ClassicApp\n        module Helpers\n          include Yaks::Sinatra::Test::Helpers\n\n          def app\n            ::Sinatra::Application\n          end\n        end\n      end\n    end\n  end\nend\n\nRSpec.configure do |config|\n  config.include Rack::Test::Methods, type: :integration\nend\n"
  },
  {
    "path": "yaks-sinatra/spec/spec_helper.rb",
    "content": "require_relative '../../shared/rspec_config'\n"
  },
  {
    "path": "yaks-sinatra/yaks-sinatra.gemspec",
    "content": "# encoding: utf-8\n\nrequire 'English'\nrequire File.expand_path('../../yaks/lib/yaks/version', __FILE__)\n\nGem::Specification.new do |gem|\n  gem.name        = 'yaks-sinatra'\n  gem.version     = Yaks::VERSION\n  gem.authors     = [ 'Arne Brasseur' ]\n  gem.email       = [ 'arne@arnebrasseur.net' ]\n  gem.description = 'Sinatra integration for Yaks'\n  gem.summary     = gem.description\n  gem.homepage    = 'https://github.com/plexus/yaks'\n  gem.license     = 'MIT'\n\n  gem.require_paths    = %w[lib]\n  gem.files            = `git ls-files`.split($INPUT_RECORD_SEPARATOR)\n  gem.test_files       = gem.files.grep(/^spec/)\n  gem.extra_rdoc_files = %w[README.md]\n\n  gem.required_ruby_version = '>= 1.9.3'\n\n  gem.add_runtime_dependency 'yaks', Yaks::VERSION\n  gem.add_runtime_dependency 'sinatra', '>= 1.4', \"< 2.1\"\n  gem.add_development_dependency 'rack-test', '~> 0.6'\nend\n"
  },
  {
    "path": "yaks-transit/README.md",
    "content": "# Yaks Transit\n\nMap the Yaks data model directly to Transit.\n\nTransit is a bit different than other formats like HAL,\nCollection+JSON in that it's intended as a general serialization\nformat, not as a hypermedia format. It does however have a standard\nextension to support links, based on the Collection+JSON links.\n"
  },
  {
    "path": "yaks-transit/lib/yaks-transit.rb",
    "content": "require 'yaks'\nrequire 'transit'\n\nYaks::Serializer.register(:transit, ->(i, _env = {}) {i})\n\nmodule Yaks\n  class Format\n    class Transit < self\n      register :transit, :transit, 'application/transit+json'\n\n      class WriteHandler\n        def initialize(klass)\n          @klass = klass\n        end\n\n        def tag(_o)\n          Util.underscore(@klass.name.gsub(/.*::/, ''))\n        end\n\n        def rep(_o)\n        end\n\n        def string_rep(_)\n          nil\n        end\n      end\n\n      class ReadHandler\n        def initialize(klass)\n          @klass = klass\n        end\n\n        def from_rep(rep)\n          @klass.new(rep)\n        end\n      end\n\n      HANDLERS = {\n        Resource              => WriteHandler.new(Resource),\n        Resource::Link        => WriteHandler.new(Resource::Link),\n        Resource::Form        => WriteHandler.new(Resource::Form),\n        Resource::Form::Field => WriteHandler.new(Resource::Form::Field)\n      }\n\n      def call(resource, _env = {})\n        StringIO.new.tap do |io|\n          ::Transit::Writer.new(:json, io, handlers: HANDLERS).write(resource)\n        end.string\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "yaks-transit/yaks-transit.gemspec",
    "content": "# encoding: utf-8\n\nrequire 'English'\nrequire File.expand_path('../../yaks/lib/yaks/version', __FILE__)\n\nGem::Specification.new do |gem|\n  gem.name        = 'yaks-transit'\n  gem.version     = Yaks::VERSION\n  gem.authors     = [ 'Arne Brasseur' ]\n  gem.email       = [ 'arne@arnebrasseur.net' ]\n  gem.description = 'Transit output format for Yaks'\n  gem.summary     = gem.description\n  gem.homepage    = 'https://github.com/plexus/yaks'\n  gem.license     = 'MIT'\n\n  gem.require_paths    = %w[lib]\n  gem.files            = `git ls-files`.split($INPUT_RECORD_SEPARATOR)\n  gem.test_files       = gem.files.grep(/^spec/)\n  gem.extra_rdoc_files = %w[README.md]\n\n  gem.required_ruby_version = '>= 1.9.3'\n\n  gem.add_runtime_dependency 'yaks', Yaks::VERSION\n  gem.add_runtime_dependency 'transit-ruby', '~> 0.8'\nend\n"
  }
]