Full Code of plexus/yaks for AI

master 75c41e5d9c56 cached
174 files
364.3 KB
103.2k tokens
576 symbols
1 requests
Download .txt
Showing preview only (408K chars total). Download the full file or copy to clipboard to get everything.
Repository: plexus/yaks
Branch: master
Commit: 75c41e5d9c56
Files: 174
Total size: 364.3 KB

Directory structure:
gitextract_hj5iupwf/

├── .gitignore
├── .rubocop.yml
├── .travis.yml
├── ADDING_FORMATS.md
├── CHANGELOG.md
├── COOKBOOK.md
├── DEVELOPERS.md
├── FORMATS.org
├── Gemfile
├── IDENTIFIERS.md
├── LICENSE
├── Rakefile
├── bench/
│   ├── bench.rb
│   └── bench_1000.rb
├── code_of_conduct.md
├── notes.org
├── shared/
│   ├── rake_tasks.rb
│   └── rspec_config.rb
├── yaks/
│   ├── .rspec
│   ├── README.md
│   ├── Rakefile
│   ├── ataru_setup.rb
│   ├── find_missing_tests.rb
│   ├── lib/
│   │   ├── yaks/
│   │   │   ├── behaviour/
│   │   │   │   └── optional_includes.rb
│   │   │   ├── breaking_changes.rb
│   │   │   ├── builder.rb
│   │   │   ├── changelog.rb
│   │   │   ├── collection_mapper.rb
│   │   │   ├── collection_resource.rb
│   │   │   ├── config.rb
│   │   │   ├── configurable.rb
│   │   │   ├── default_policy.rb
│   │   │   ├── errors.rb
│   │   │   ├── format/
│   │   │   │   ├── collection_json.rb
│   │   │   │   ├── hal.rb
│   │   │   │   ├── halo.rb
│   │   │   │   └── json_api.rb
│   │   │   ├── format.rb
│   │   │   ├── fp/
│   │   │   │   └── callable.rb
│   │   │   ├── html5_forms.rb
│   │   │   ├── mapper/
│   │   │   │   ├── association.rb
│   │   │   │   ├── association_mapper.rb
│   │   │   │   ├── attribute.rb
│   │   │   │   ├── config.rb
│   │   │   │   ├── form/
│   │   │   │   │   ├── config.rb
│   │   │   │   │   ├── dynamic_field.rb
│   │   │   │   │   ├── field/
│   │   │   │   │   │   └── option.rb
│   │   │   │   │   ├── field.rb
│   │   │   │   │   ├── fieldset.rb
│   │   │   │   │   └── legend.rb
│   │   │   │   ├── form.rb
│   │   │   │   ├── has_many.rb
│   │   │   │   ├── has_one.rb
│   │   │   │   └── link.rb
│   │   │   ├── mapper.rb
│   │   │   ├── null_resource.rb
│   │   │   ├── pipeline.rb
│   │   │   ├── primitivize.rb
│   │   │   ├── reader/
│   │   │   │   ├── hal.rb
│   │   │   │   └── json_api.rb
│   │   │   ├── resource/
│   │   │   │   ├── form/
│   │   │   │   │   ├── field/
│   │   │   │   │   │   └── option.rb
│   │   │   │   │   ├── field.rb
│   │   │   │   │   ├── fieldset.rb
│   │   │   │   │   └── legend.rb
│   │   │   │   ├── form.rb
│   │   │   │   ├── has_fields.rb
│   │   │   │   └── link.rb
│   │   │   ├── resource.rb
│   │   │   ├── runner.rb
│   │   │   ├── serializer.rb
│   │   │   ├── util.rb
│   │   │   └── version.rb
│   │   └── yaks.rb
│   ├── spec/
│   │   ├── acceptance/
│   │   │   ├── acceptance_spec.rb
│   │   │   ├── json_shared_examples.rb
│   │   │   └── models.rb
│   │   ├── fixture_helpers.rb
│   │   ├── integration/
│   │   │   ├── dynamic_form_fields_spec.rb
│   │   │   ├── fieldset_spec.rb
│   │   │   └── map_to_resource_spec.rb
│   │   ├── json/
│   │   │   ├── confucius.collection_json.json
│   │   │   ├── confucius.hal.json
│   │   │   ├── confucius.halo.json
│   │   │   ├── confucius.json_api.json
│   │   │   ├── john.hal.json
│   │   │   ├── list_of_quotes.collection_json.json
│   │   │   ├── list_of_quotes.hal.json
│   │   │   ├── list_of_quotes.json_api.json
│   │   │   ├── plant_collection.collection.json
│   │   │   ├── plant_collection.hal.json
│   │   │   └── youtypeitwepostit.collection_json.json
│   │   ├── sanity_spec.rb
│   │   ├── spec_helper.rb
│   │   ├── support/
│   │   │   ├── classes_for_policy_testing.rb
│   │   │   ├── deep_eql.rb
│   │   │   ├── fixtures.rb
│   │   │   ├── friends_mapper.rb
│   │   │   ├── models.rb
│   │   │   ├── pet_mapper.rb
│   │   │   ├── pet_peeve_mapper.rb
│   │   │   ├── shared_contexts.rb
│   │   │   └── youtypeit_models_mappers.rb
│   │   ├── unit/
│   │   │   └── yaks/
│   │   │       ├── behaviour/
│   │   │       │   └── optional_includes_spec.rb
│   │   │       ├── builder_spec.rb
│   │   │       ├── collection_mapper_spec.rb
│   │   │       ├── collection_resource_spec.rb
│   │   │       ├── config_spec.rb
│   │   │       ├── configurable_spec.rb
│   │   │       ├── default_policy/
│   │   │       │   ├── derive_mapper_from_collection_spec.rb
│   │   │       │   ├── derive_mapper_from_item_spec.rb
│   │   │       │   └── derive_mapper_from_object_spec.rb
│   │   │       ├── default_policy_spec.rb
│   │   │       ├── format/
│   │   │       │   ├── collection_json_spec.rb
│   │   │       │   ├── hal_spec.rb
│   │   │       │   ├── halo_spec.rb
│   │   │       │   ├── html_spec.rb
│   │   │       │   └── json_api_spec.rb
│   │   │       ├── format_spec.rb
│   │   │       ├── fp/
│   │   │       │   └── callable_spec.rb
│   │   │       ├── mapper/
│   │   │       │   ├── association_mapper_spec.rb
│   │   │       │   ├── association_spec.rb
│   │   │       │   ├── attribute_spec.rb
│   │   │       │   ├── config_spec.rb
│   │   │       │   ├── form/
│   │   │       │   │   ├── config_spec.rb
│   │   │       │   │   ├── dynamic_field_spec.rb
│   │   │       │   │   ├── field/
│   │   │       │   │   │   └── option_spec.rb
│   │   │       │   │   ├── field_spec.rb
│   │   │       │   │   ├── fieldset_spec.rb
│   │   │       │   │   └── legend_spec.rb
│   │   │       │   ├── form_spec.rb
│   │   │       │   ├── has_many_spec.rb
│   │   │       │   ├── has_one_spec.rb
│   │   │       │   └── link_spec.rb
│   │   │       ├── mapper_spec.rb
│   │   │       ├── null_resource_spec.rb
│   │   │       ├── pipeline_spec.rb
│   │   │       ├── primitivize_spec.rb
│   │   │       ├── resource/
│   │   │       │   ├── form/
│   │   │       │   │   ├── field_spec.rb
│   │   │       │   │   ├── fieldset_spec.rb
│   │   │       │   │   └── legend_spec.rb
│   │   │       │   ├── form_spec.rb
│   │   │       │   ├── has_fields_spec.rb
│   │   │       │   └── link_spec.rb
│   │   │       ├── resource_spec.rb
│   │   │       ├── runner_spec.rb
│   │   │       ├── serializer_spec.rb
│   │   │       └── util_spec.rb
│   │   └── yaml/
│   │       ├── confucius.yaml
│   │       ├── list_of_quotes.yaml
│   │       └── youtypeitwepostit.yaml
│   └── yaks.gemspec
├── yaks-html/
│   ├── README.md
│   ├── Rakefile
│   ├── lib/
│   │   ├── yaks/
│   │   │   └── format/
│   │   │       ├── html.rb
│   │   │       └── template.html
│   │   ├── yaks-html/
│   │   │   └── rspec.rb
│   │   └── yaks-html.rb
│   ├── spec/
│   │   ├── smoke_test_spec.rb
│   │   ├── spec_helper.rb
│   │   └── support/
│   │       └── test_app.rb
│   └── yaks-html.gemspec
├── yaks-sinatra/
│   ├── .rspec
│   ├── README.md
│   ├── Rakefile
│   ├── lib/
│   │   └── yaks-sinatra.rb
│   ├── spec/
│   │   ├── integration/
│   │   │   ├── classic_app.rb
│   │   │   ├── classic_spec.rb
│   │   │   └── modular_spec.rb
│   │   ├── integration_helper.rb
│   │   └── spec_helper.rb
│   └── yaks-sinatra.gemspec
└── yaks-transit/
    ├── README.md
    ├── lib/
    │   └── yaks-transit.rb
    └── yaks-transit.gemspec

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
pkg
.bundle
coverage
Gemfile.lock
*~
.yardoc
doc
FORMATS.html
scratch
.ruby-version
.ruby-gemset
Jarfile.lock


================================================
FILE: .rubocop.yml
================================================
AllCops:
  Exclude:
    - 'pkg/**/*'
    - 'vendor/**/*'
    - 'bench/**/*'

Lint/AmbiguousRegexpLiteral:
  Enabled: false

Lint/AmbiguousOperator:
  Enabled: false

Lint/AssignmentInCondition:
  Enabled: false

Metrics/AbcSize:
  Enabled: false

Metrics/ClassLength:
  Enabled: false

Metrics/CyclomaticComplexity:
  Enabled: false

# FIXME: lower by fixing the biggest offenders
Metrics/LineLength:
  Max: 184

Metrics/MethodLength:
  Enabled: false

Metrics/PerceivedComplexity:
  Enabled: false

# def_delegators’ first symbol is the target, the rest are calls
Style/AlignParameters:
  Enabled: false

# FIXME: should this be normalised to `EnforcedStyle: semantic`?
Style/BlockDelimiters:
  Enabled: false

# including parametrised modules looks much better without this alignment
Style/ClosingParenthesisIndentation:
  Enabled: false

Style/ConstantName:
  Exclude:
    - yaks/lib/yaks/breaking_changes.rb

Style/Documentation:
  Enabled: false

Style/EmptyLineBetweenDefs:
  AllowAdjacentOneLineDefs: true

Style/FileName:
  Exclude:
    - yaks-html/lib/yaks-html.rb
    - yaks-sinatra/lib/yaks-sinatra.rb
    - yaks-transit/lib/yaks-transit.rb

# including parametrised modules looks much better without this indentation
Style/FirstParameterIndentation:
  Enabled: false

Style/FormatString:
  EnforcedStyle: percent

Style/GlobalVars:
  Exclude:
    - bench/bench.rb
    - bench/bench_1000.rb

Style/HashSyntax:
  Exclude:
    - Rakefile

# some arrays need deeper indenting for readability
Style/IndentArray:
  Enabled: false

Style/IndentationWidth:
  Exclude:
    - yaks/lib/yaks/breaking_changes.rb

# the codebase uses -> consistently
Style/Lambda:
  Enabled: false

# FIXME: figure out why fixing this blows tests up
Style/MethodCallParentheses:
  Enabled: false

Style/ModuleFunction:
  Exclude:
    - yaks/lib/yaks/util.rb

Style/MultilineBlockChain:
  Enabled: false

Style/PercentLiteralDelimiters:
  Exclude:
    - yaks/lib/yaks/breaking_changes.rb
    - yaks/spec/unit/yaks/config_spec.rb
  PreferredDelimiters:
    '%i': '[]'
    '%w': '[]'
    '%W': '[]'

Style/PerlBackrefs:
  Enabled: false

Style/Semicolon:
  AllowAsExpressionSeparator: true

Style/SignalException:
  EnforcedStyle: only_raise

Style/SpaceBeforeSemicolon:
  Enabled: false

# FIXME: this should be enforced to either space or no_space
Style/SpaceBeforeBlockBraces:
  Enabled: false

# FIXME: this should be enforced to either space or no_space
Style/SpaceInsideBlockBraces:
  Enabled: false

# FIXME: make a call whether to fix this one or not
Style/SpaceInsideBrackets:
  Enabled: false

Style/SpaceInsideHashLiteralBraces:
  EnforcedStyle: no_space

Style/SpaceInsideStringInterpolation:
  Enabled: false

# FIXME: enforce either single_quotes (fewer fixes) or double_quotes
Style/StringLiterals:
  Enabled: false

Style/UnneededPercentQ:
  Exclude:
    - yaks/lib/yaks/breaking_changes.rb

Style/TrailingComma:
  Enabled: false

# Allow redundant braces in foo.bar({"qux" => "quz"}), when writing
# e.g. JSON tests this can be more explicit and clear
Style/BracesAroundHashParameters:
  Exclude:
    - yaks/spec/**/*

Style/SpaceInsideStringInterpolation:
  Enabled: false

Style/LambdaCall:
  Enabled: false


================================================
FILE: .travis.yml
================================================
language: ruby

rvm:
  - 2.2
  - 2.3.4
  - 2.4.2
  - jruby
  - jruby-head
  - ruby-head

cache: bundler

sudo: false

script: bundle exec rake $TASK

env:
  - TASK=yaks:rspec
  - TASK=yaks-html:rspec
  - TASK=yaks-sinatra:rspec

matrix:
  allow_failures:
    - rvm: ruby-head
    - rvm: jruby-head
    - rvm: rbx
    - env: TASK=mutant
  fast_finish: true
  include:
    - rvm: rbx
      env: TASK="yaks:rspec --trace"
    - rvm: rbx
      env: TASK="yaks-html:rspec --trace"
    - rvm: rbx
      env: TASK="yaks-sinatra:rspec --trace"
    - rvm: 2.2
      env: TASK=ataru
    - rvm: 2.2
      env: TASK=mutant


================================================
FILE: ADDING_FORMATS.md
================================================
# Adding Extra Output Formats to Yaks

Individual 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.

A `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`.

Different 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.

This is already the case, JSON-API ignores links for example.

So 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.

If 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.


================================================
FILE: CHANGELOG.md
================================================
### master
[all changes](http://github.com/plexus/yaks/compare/v0.13.0...master)

### v0.13.0 / 2017-11-13
[all changes](http://github.com/plexus/yaks/compare/v0.12.0...v0.13.0)

* Maintenance release to upgrade dependencies

### v0.12.0 / 2016-03-28
[all changes](http://github.com/plexus/yaks/compare/v0.11.0...v0.12.0)

* `attribute` can now take an `if` parameter, just like links and forms

### v0.11.0 / 2015-07-09
[all changes](http://github.com/plexus/yaks/compare/v0.10.0...v0.11.0)

* Updated JSON-API to match 1.0 format. ([Yohan Robert](https://github.com/groyoh) and [Janko Marohnić](https://github.com/janko-m))
* Added Yaks::Behaviour::OptionalIncludes, to support JSON-API style
  optional associations ([Janko Marohnić](https://github.com/janko-m))
* Renamed Sinatra::Yaks to Yaks::Sinatra ([Matt Patterson](https://github.com/fidothe))
* Correctly handle 'charset' in Yaks::Sinatra ([Matt Patterson](https://github.com/fidothe))
* Fix rendering of checkboxes in yaks-html
* Add integration for testing through RSpec/Capybara/Rack::Test

### v0.10.0 / 2015-05-19
[all changes](http://github.com/plexus/yaks/compare/v0.9.0...v0.10.0)

* Updated JSON-API Reader to handle collections

* Further changes to bring JSONAPI formatting more in line with 1.0
  format

  - Changed `linked` to `included`
  - Change format of `links` to include 'linkages'
  - `included` no longer contains duplicates
  - Render top level collection links
  - Don't include "rel" in links output

* yaks-html: Make nested resources expand/collapse, various small
  improvements

* yaks-html: render "rel" attributes, making the HTML output more suitable
  for use in integration tests

* In mapper/form declarations: make methods that take a lambda to also
  accept a block

* Instead of representing form fieldset legends as form fields, give
  them their own class

* Introduce `Yaks::Form#fields_flat`, an enumerator to traverse all
  input fields in a form linearly, e.g. for validation. Will skip over
  legend elements

* Introduce `Yaks::Form#map_fields`, a way to map over fields and fieldsets
  recursively, yielding a new nested object

* Bug fix: `json_serializer` configuration method not working as intended

* Improved mapper lookup to deal with model inside namespace

* Introduce `mapper_for`, a configuration option for setting up one-off
  mapper derivation rules

* Reify Form::Legend, making it easier to handle form objects with field sets

* Improve test coverage. We are now at 97.30% mutation coverage

* Improve documentation. Code examples in the README are now verified with
  Ataru

* Make code warning-free


### v0.9.0 / 2015-03-17

Make dynamic form fields respect the order in which they were declared
in the form relative to other form fields.

Some changes to bring JSONAPI formatting more in line with 1.0 format

 - Top level key must be named 'data' rather than the resource type
 - The resource name myst be included in a 'type' attribute

Started a Reader for JSONAPI, which can build a resource from JSONAPI input.

Add if: options to Form::Field, Form::Fieldset, and Form::Field
option, just as on links, associations, and forms.

Allow form field details to be expressed in a block, and allow
Configurable "setters" to take a block instead of a direct argument.

``` ruby
text :first_name,
  label: 'views.checkout.first_name',
  required: true,
  value: ->{ customer_attribute(:first_name) }
```

becomes

```
text :first_name do
  required true
  label 'views.checkout.first_name'
  value { customer_attribute(:first_name) }
end
```

This makes the DSL more consistent, since e.g. `label` could already
be set in this way, but not `value` or `required`.

Prevent `:if` on a form field to be rendered as a form element
attribute.

### v0.8.3 / 2015-03-09

The default policy for resolving mappers will now look up superclass
names of the object being serialized, so you can define a single
mapper to handle a class hierarchy.

### v0.8.2 / 2015-03-02

Various improvements to the HTML formatter

- use the form name as a title if there's no title
- remove the link styling on rels to indicate they are purely
  identifiers
- link IANA registered rels (indicated by using a symbol) to the IANA
  list
- style the hierarchy in a cleaner way by using a gray left border
  rather than complete boxes
- Add a header that shows the current request method/path
- Add a footer that shows the yaks version
- show the name/value of hidden form fields
- get rid of the all the border-radius, try a new color scheme

### v0.8.1 / 2015-02-20

Add `disabled` as a possible attribute of a select option, so you can
render form select controls with disabled options.

### v0.8.0 / 2015-02-18

Allow to use procs for dynamic values in "option" form elements (as
used inside a "select"). This makes the form API more consistent.

Add an `:if` option to links, to only render them upon a certain
condition.

Add an `:if` option to forms, and a corresponding `condition` method
(it's tricky to have a method called `if`), to only render them upon a
certain condition.

Add an `:if` option to associtions, to only render them upon a certain
condition.

### 0.8.0.beta2 / 2015-01-14

In form select fields, allow the attributes of options to be generated
dynamically by passing procs, in line with other form related
attributes

### 0.8.0.beta1 / 2015-01-09

Improved form support, HTML form rendering, CJ support.

### 0.8.0.alpha / 2014-12-17

Improved Collection+JSON support, dynamically generated form fields.

#### Collection+JSON

Carles Jove i Buxeda has done some great work to improve support for Collection+JSON, GET forms are now rendered as CJ queries.

#### Dynamic Form Fields

A 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.

```
form :checkout do
  text :name
  text :lastname

  dynamic do |object|
    object.shipping_options.each do |shipping|
      radio shipping.type_name, title: shipping.description
    end
  end
end
```

#### Fieldset and Legend

Support for the fieldset element type has been added, which works as you would expect

```
form :foo do
  fieldset do
    legend "Hello"
    text :field_1
  end
end
```

#### Remove links

A 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.

```
class BaseMapper
  link :self, "/api/{mapper_name}/{id}"
end

class FooMapper < BaseMapper
  link :self, remove: true
end
```

#### Deprecations

Internally 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.

Because of this work, two methods on Yaks::Config are considered deprecated. You will get a warning when using the old name.

* json_serializer, use serializer(:json, &...)
* namespace, use mapper_namespace

#### Experimental read/write support

Some work has happened on read/write support, but this is not considered stable yet.

### 0.7.7 / 2014-12-02

General extension and improvements to form handling.

Add top level links in Collection+JSON (Carles Jove i Buxeda)

The mapper DSL method "control" has been renamed to "form". There is a
deprecated alias available.

Add Yaks::Resource#find_form for querying a resource for an embedded
form by name.

Introduce yaks.map() so you can only call the mapping step without
running the whole pipeline.

### 0.7.6 / 2014-11-18

Much expanded form support, simplified link DSL, pretty-print objects
to Ruby code.

Breaking change: using a symbol instead of link template no longer
works, use a lambda.

    link :foo, :bar

Becomes

    link :foo, ->{ bar }

Strictly speaking the equivalent version would be `link :foo, ->{
load_attribute(:bar) }`. Depending on if `bar` is implemented on the
mapper or is an attribute of the object, this would simplify to `link
:foo, ->{ bar }` or `link :foo, ->{ object.bar }` respectively.

The form control DSL has been expanded, instead of `field type:
'text'` and similar there are now aliases, e.g. `text :name, value:
'foo'`.

All attributes on the form control itself, and on fields, now
optionally take a lambda (any `#to_proc`-able) for dynamic
content. e.g.

    control :add_product do
      method 'POST'
      action ->{ '/cart/#{cart.id}/line_items' }
      hidden :product_id, value: -> { product.id }
      number :quantity, value: 0
    end

As with lambdas used for links, in case of a zero-arity lambda these
evaluate with `self` being the mapper. If the lambda takes an argument
the argument will be the mapper, and the lambda is evaluated as a
closure.

The `href` attribute of a control has been renamed `action`, in line
with the attribute name in HTML. An alias is available but will output
a deprecation warning.

The Yaks::Resource#pp method has been lifted into Attributes so it's
available on most immutable Yaks objects. It has also been adapted to
produce, in most cases, output that is valid Ruby code.

### 0.7.5 / 2014-11-17

Add the :replace option to link specifications. When used on a link
when another link of the same rel was specified previously, then the
current link will replace the one (and any other) that was specified
earlier.

Use case:

    class BaseMapper < Yaks::Mapper
      link :self, '/api/{mapper_name}/{id}'
    end

    class CartMapper < BaseMapper
      link :self, '/api/cart', :replace => true
    end


### 0.7.4 / 2014-11-17

Fix a regression in around hooks introduced in 0.7.0.

Improve pretty printing (Yaks::Resource#pp)

### 0.7.3 / 2014-11-11

yaks-sinatra: Allow passing extra Yaks options to the helper method

### 0.7.2 / 2014-11-10

Allow controls to use the same expansion mechanisms that are available
in links, i.e. URI templates, symbol referring to a method. Added
procs to that list as well.

### 0.7.1 / 2014-11-10

Bugfix in CollectionMapper.

### 0.7.0 / 2014-11-10

#### Introduces yaks-sinatra

For easier Sinatra integration. See the respective README for more info.

#### Move the rel of subresource into a resource itself

Before the subresources in a Yaks::Resource were stored in a hash,
keyed by rel. Now the rel is stored as a property of the resource, and
the subresources are a simple array. This opens the door to formats
that support multiple rels on a resource, and simplifies things as a
preparatory step towards bi-directional mapping.

This change is mostly transparent to the user, but when implementing
custom output formats or doing testing on the resulting Resource
instances, you might have to update your code.

#### Pass the rack env to steps and hooks

Yaks is a pipeline where each step implements the `call`
method. Before `call` always received one argument, the previous
transformation step's result. Now it receives the Rack env as a second
argument.

This also applies to before/after/around hooks, although if they are
specified as ruby blocks then no change is needed, the second argument
will be ignored.

#### Handle URI instances

After formatting for a JSON output format (e.g. HAL), but before
actually serializing to JSON, all data needs to be of a type that has
a JSON equivalent, or needs to be handled explicitly with a conversion
(known as "primitivizing"). instances of `URI` have been added to this
list, they will automatically be represented as JSON strings.

### 0.6.2 / 2014-11-05

Improvements to yaks-html: render form controls, make output prettier.

### 0.6.1 / 2014-10-30

Make sure Resource, NullResource, and CollectionResource have
identical public APIs.

Create a base Yaks::Error class, and derived classes for specific
error categories. This should make it easier to handle errors
originating in Yaks. Note that not all code makes use of these yet, so
you might still get a StandardError in some cases.

### 0.6.0 / 2014-10-30

v0.6.0 saw some big internal overhaul to make things cleaner and more
consistent. It also introduced some new features.

#### Form controls

We already had templated links which form a limited way of generating
parameterized requests. Form controls are more like full HTML forms,
e.g.

``` ruby
class UserMapper < Yaks::Mapper
  control :create do
    href         "/foo"
    method       "POST"
    content_type "application/x-www-form-urlencoded"

    field :first_name, label: "First name"
    field :last_name,  label: "Last name"
  end
end
```

These are also called actions in some formats. At the moment only one
format renders these, a new format called HALO which is en extension
of HAL, loosely based on an example by Mike Kelly on how HAL could be
extended for this purpose.

#### Introduce a HTML output format

Provided as a separate gem, `yaks-html` allows Yaks to generate a
version of your API that can be browsed from any web browser. This is
still very rough around the edges.

### 0.5.0 / 2014-09-18

* Yaks now serializes (returns a string), instead of returning a data
  structure. This is a preparatory step for supporting non-JSON
  formats. To get the old behavior back, do this

``` ruby
yaks = Yaks.new do
  skip :serialize
end
```

* The old `after` hook has been removed, instead there are now generic hooks for all steps: `before`, `after`, `around`, `skip`; `:map`, `:format`, `:primitivize`, `:serialize`.

* By default Yaks uses `JSON.pretty_generate` as a JSON unparser. To use something else, for example `Oj.dump`, do this

``` ruby
yaks = Yaks.new do
  json_serializer &Oj.method(:dump)
end
```

* 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.

* Collection+JSON uses a link's "title" attribute to output a link's "name", to better correspond with other formats

* When registering a custom format (Yaks::Format subclass), the signature has changed

``` ruby
# 0.4.3
Format.register self, :collection_json, 'application/vnd.collection+json'

# 0.5.0
register :collection_json, :json, 'application/vnd.collection+json'
```

* `yaks.call` is now the preferred interface, rather than `yaks.serialize`, although there are no plans yet to remove the alias.

* 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`

* Improved YARD documentation

* 100% mutation coverage :trumpet: :tada:

### 0.4.3 / 2014-08-25

* 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.

```ruby
Yaks.new do
  rel_template 'http://my-api/docs/relationships/{rel}'
end
```

* Yaks::Serializer has been renamed to Yaks::Format

* Yaks::Mapper#{map_attributes,map_links,map_subresource} signature has changed, they now are responsible for adding themselves to a resource instance.

```ruby
class FooMapper < Yaks::Mapper
  def map_attributes(resource)
    resource.update_attributes(:example => 'attribute')
  end
end
```

* Conditionally turn associations into links

```ruby
class ShowMapper < Yaks::Mapper
  has_many :events, href: '/show/{id}/events', link_if: ->{ events.count > 50 }
end
```

* Reify `Yaks::Mapper::Attribute`

* Remove `Yaks::Mapper#filter`, instead override `#attributes` or `#associations` to filter things out, for example:

```ruby
class SongMapper
  attributes :title, :duration, :lyrics
  has_one :artist
  has_one :album

  def minimal?
    env['HTTP_PREFER'] =~ /minimal/
  end

  def attributes
    if minimal?
      super.reject {|attr| attr.name.equal? :lyrics } # These are instances of Yaks::Mapper::Attribute
    else
      super
    end
  end

  def associations
    return [] if minimal?
    super
  end
end
```

* Give Attribute, Link, Association a common interface : `add_to_resource(resource, mapper, context)`
* Add persistent update methods to `Yaks::Resource`

### v0.4.2 / 2014-06-24

* JSON-API: render self links as href attributes
* HAL: render has_one returning nil as null, not as {}
* Keep track of the mapper stack, useful for figuring out if mapping the top level response or not, or for accessing parent
* Change Serializer.new(resource, options).serialize to Serializer.new(options).call(resource) for cosistency of "pipeline" interface
* Make Yaks::CollectionMapper#collection overridable for pagination
* Don't render links from custom link methods (link :foo, :method_that_generates_url) that return nil

### v0.4.1 / 2014-06-18

* Change how env is passed to yaks.serialize to match docs
* Fix JSON-API bug (#18 reported by Nicolas Blanco)
* Don't pluralize has_one association names in JSON-API

### v0.4.0  / 2014-06-17

* Introduce after {} post-processing hook
* Streamline interfaces and variable names, especially the use of `call`
* Improve deriving mappers automatically, even with Rails style autoloading
* Give CollectionResource a members_rel, for HAL-like formats with no top-level collection concept
* Switch back to using `src` and `dest` as the rel-template keys, instead of `association_name`
* deprecate `mapper_namespace` in favor of `namespace`

### v0.4.0.rc1 / 2014-06-11

* Introduce Yaks.new as the main public interface
* Fix JsonApiSerializer and make it compliant with current spec
* Remove Hamster dependency, Yaks new uses plain old Ruby arrays and hashes
* Remove `RelRegistry` and `ProfileRegistry` in favor of a simpler explicit syntax + policy based fallback
* Add more policy derivation hooks, plus make `DefaultPolicy` template for rel urls configurable
* Optionally take a Rack env hash, pass it around so mappers can inspect it
* Honor the HTTP Accept header if it is present in the rack env
* Add map_to_primitive configuration option

### v0.3.0 / 2014-05-15

* Allow partial expansion of templates, expand certain fields, leave others as URI template in the result.

### v0.2.0 / 2014-03-31

* links can now take a simple for a template to compute a link just like an attribute

### v0.1.0 / 2014-03-07

### v0.0.0 / 2013-12-09


================================================
FILE: COOKBOOK.md
================================================
# Yaks Cookbook

## Represent Date/Time objects as iso8601

``` ruby
$yaks = Yaks.new do
  map_to_primitive Date, Time, DateTime, ActiveSupport::TimeWithZone, &:iso8601
end
```

## Make Yaks' HTML format play nice with CSRF protection

Minimum version when using Rack::Protection

``` ruby
$yaks = Yaks.new do
  after :format, :add_csrf_token do |result, env|
    next result unless result.is_a?(Hexp::Node) && env.key?('rack.session')

    session = env['rack.session']
    session[:csrf] ||= SecureRandom.hex(32)
    token = session[:csrf]

    result.replace 'form' do |form|
    form.append(H[:input, type: :hidden, name: 'authenticity_token', value: token])
    end
  end
end
```

Version that covers all cases when using a Rack::Protection protected
API mounted inside a Rails app.

``` ruby
$yaks = Yaks.new do
  after :format, :add_csrf_token do |result, env|
    next result unless result.is_a?(Hexp::Node) && env.key?('rack.session')

    # Rails uses '_csrf_token' as a key. Rack::Protection uses
    # :csrf, but will detect and use '_csrf_token' if :csrf is
    # absent. This works fine as long as a call to Rails is made
    # before a call to the API is made. When using the HTML
    # rendering of the API on an empty session and afterwards
    # switching to Rails though, the '_csrf_token' and :csrf
    # values will differ, causing Rack::Protection to reject
    # valid API calls. Hence this little dance to prevent that.

    session = env['rack.session']
    session[:csrf] ||= session['_csrf_token'] || SecureRandom.hex(32)
    session['_csrf_token'] ||= session[:csrf]
    token = session[:csrf]

    result.replace 'form' do |form|
    form.append(H[:input, type: :hidden, name: 'authenticity_token', value: token])
    end
  end
```

## Make Yaks' HTML format work with PUT/DELETE/etc.

If you're using `Rack::MethodOverride` or something similar, you could
drop this in your Yaks config to convert forms so they will work in a
browser.

``` ruby
after :format, :html_form_methods do |result, env|
  next result unless result.is_a?(Hexp::Node)
  result.replace('[method="PUT"],[method="DELETE"],[method="PATCH"]') do |form|
    form
      .append(H[:input, type: "hidden", name: "_method", value: form[:method]])
      .attr("method", "POST")
  end
end
```

## Implement Pagination

In a hypermedia API the typical way to provide pagination is by adding
"previous" and "next" links on a collection. You can do this by
implementing your own CollectionMapper

```
module Mappers
  class CollectionMapper < Yaks::CollectionMapper
    PAGE_SIZE = 50

    link :previous, -> { previous_link }
    link :next,     -> { next_link     }

    def params
      Rack::Request.new(env).params
    end

    def offset
      params.fetch('offset') { 0 }.to_i
    end

    alias full_collection collection

    def collection
      # You can implement more efficient page slicing based on DB
      # layer you're using
      full_collection.drop(offset).take(PAGE_SIZE)
    end

    def count
      full_collection.count
    end

    def previous_link
      if offset > 0
        URITemplate.new("#{env['PATH_INFO']}{?offset}").expand(offset: [offset - PAGE_SIZE, 0].max)
      end
    end

    def next_link
      if offset + page_size < count
        URITemplate.new("#{env['PATH_INFO']}{?offset}").expand(offset: offset + PAGE_SIZE)
      end
    end
  end
end
```

You can pass this mapper explicitly when calling yaks:
`yaks.call(collection, mapper: MyCollectionMapper)`, or leverage the
default policy which gives you several options for hooking into mapper
resolution.

* When implementing a `CollectionMapper` inside your configured mapper
  namespace, or at the top level if no namespace is confgured, Yaks
  will use that instead of its vanilla collection mapper

* 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`

* 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.


================================================
FILE: DEVELOPERS.md
================================================
# Yaks Dev Docs

This document is for when you want to hack on Yaks itself, or better
understand its internals. To simply use it, consult the README.

## Attribs

You'll find that most classes in Yaks include an instance of
`Attribs`, for example

``` ruby
class Yaks::Resource::Link
  include Attribs.new(:rel, :uri, options: {})
end
```

You can think of this (as a starting point) as replacing
`attr_reader`, by adding this line instances of `Link` will have
getter methods for `rel`, `uri`, and `options`. But that's really just
scratching the surface.

`Attribs` relies on Anima, so you get the same things as
using `include Anima.new`

* a hash-based constructor
* getters
* equality checks
* `to_h`

``` ruby
link = Yaks::Resource::Link.new(rel: :self, uri: '/api/cart', options: {templated: false})

link.rel # => :self
link.to_h # => {:rel=>:self, :uri=>"/api/cart", :options=>{:templated=>false}}

link == Yaks::Resource::Link.new(link.to_h) # => true
```

These last two are important because they make these objects behave
like "value objects". They are fully defined by their properties, not
by their (object) identity.

Note that there are no setters, these objects are immutable.

There are some other things that `Attribs` adds that make it
a pleasure to work with these objects.

* default values
* `with` method to create updates
* `with_x` convenience methods
* `pp` method for representing instances as valid Ruby code
* `append_to` method
* `to_h_compact` method

You can include default values for properties in `Attribs.new(...)`, for example the options of a `Link` default to `{}`.

`with` (see
[this discussion](https://gist.github.com/plexus/42c6c9c63212182ee440)
about why that name was chosen), will create a new object, with
certain properties replaced.

``` ruby
link2 = link.with(uri: '/foo/bar')

link  # => #<Yaks::Resource::Link rel=:self uri="/api/cart" options={:templated=>false}>
link2 # => #<Yaks::Resource::Link rel=:self uri="/foo/bar" options={:templated=>false}>
```

For each property `foo` there's also `with_foo`, so `x.with(foo: 'bar')` is the same as `x.with_foo('bar')`

`pp` recursively turns nested `Attribs` based objects into nicely
format, valid Ruby code. This is great for debugging, and very helpful
when writing test cases.

<a id="mapper_config_example"></a>

``` ruby
class FooMapper < Yaks::Mapper
  attributes :a, :b
  link :self, '/api/foo'
  has_many :baz
  form :bar do
    text :name
    text :age
  end
end

puts FooMapper.config.pp

# -- output --
Yaks::Mapper::Config.new(
  attributes: [
    Yaks::Mapper::Attribute.new(name: :a),
    Yaks::Mapper::Attribute.new(name: :b)
  ],
  links: [
    Yaks::Mapper::Link.new(rel: :self, template: "/api/foo", options: {})
  ],
  associations: [
    Yaks::Mapper::HasMany.new(name: :baz, collection_mapper: nil)
  ],
  forms: [
    Yaks::Mapper::Form.new(
      config: Yaks::Mapper::Form::Config.new(
        name: :bar,
        fields: [
          Yaks::Mapper::Form::Field.new(name: :name, type: :text),
          Yaks::Mapper::Form::Field.new(name: :age, type: :text)
        ]
      )
    )
  ]
)
```

Because of the common case where new objects need to be added to a
list, e.g. a new link, association, form, to the respective property,
there's a `append_to` convenience method for that.

``` ruby
config = Yaks::Mapper::Config.new
config = config.append_to(:attributes, Yaks::Mapper::Attribute.new(name: :a))
config = config.append_to(:attributes, Yaks::Mapper::Attribute.new(name: :b))
puts config.pp

# -- output --
Yaks::Mapper::Config.new(
  attributes: [
    Yaks::Mapper::Attribute.new(name: {:name=>:a}),
    Yaks::Mapper::Attribute.new(name: {:name=>:b})
  ]
)
```

Finally `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.

## The Mapper DSL

Now that we know that most objects in Yaks behave in a uniform way, we
can leverage that to create the Yaks mapper DSL.

As demonstrated in the [example above](#mapper_config_example), most
methods like `link`, `has_many`, or `fieldset` simply instantiate an
object of a certain type, and add it to a "config" object. For a form
`text` input field, the config object is a `Form::Config`, held by the
form instance. At the top-level where we have attributes, links, and
associations, this config object is an instance of
`Yaks::Mapper::Config` held by the mapper subclass. When configuring
Yaks itself (through `Yaks.new do ...`), you are creating a
`Yaks::Config`, etc.

Because the objects created by the DSL all use `Attribs`,
their constructor takes a Hash. For the DSL we often prefer positional
arguments, however. E.g. `form :create` instead of `form name:
:create`. To bridge this gap classes like `Form` implement a class
method `create`, with the same signature as the DSL method.

Because all these classes implement `create`, we can now generate the
DSL methods in a generic way. This is where `Yaks::Configurable` comes
in.

## Yaks::Configurable

Here's how `Yaks::Mapper` starts

``` ruby
module Yaks
  class Mapper
    extend Configurable

    def_add :link,      create: Link,      append_to: :links
    def_add :has_one,   create: HasOne,    append_to: :associations
    def_add :has_many,  create: HasMany,   append_to: :associations
    def_add :attribute, create: Attribute, append_to: :attributes
    def_add :form,      create: Form,      append_to: :forms

    def_set :type

    def_forward :attributes => :add_attributes
    def_forward :append_to
```

The `def_add` "macro"[[1](#macro_footnote)] provided by `Yaks::Configurable` will generate a
method which

* creates an instance of certain class by calling `KlassName.create(...)`
* update `config` to a new config which has the instance appended to
  `config.links`

For the case where a DSL method simply needs to overwrite a certain
config attribute, use `def_set`.

For more involved cases you can implement methods on the Config object
that will "update" it in a specific way, returning the updated
instance (remember these are all immutable). In that case you generate
a DSL method which "forwards" to the config object, hence `def_forward`

## Builder

In the case of `Yaks::Mapper`, the config object is stored on each
mapper subclass. In other cases the configuration isn't class based
though, but instance based. For example, both a `Yaks::Form` and a
`Yaks::Form::Fieldset` both have a `Yaks::Form::Config` instance as an
attribute. Creating form fields will add them to this config.

The block passed to the `form` DSL method will be passed on to
`Form.create`. Inside the block a very similar DSL is used as that on
a Mapper, but we don't have a class level evaluation context.

Instead we create a `Yaks::Builder` and use the `Yaks::Configurable`
"macros" to declare how the DSL in this context functions. Finally we
ask the builder to evaluate the block, updating the form's config.

``` ruby
module Yaks::Mapper::Form

  ConfigBuilder = Builder.new(Config) do
    def_set :action, :title, :method, :media_type
    def_add :field, create: Field::Builder, append_to: :fields
    def_add :fieldset, create: Fieldset, append_to: :fields
    # ...
  end

  def self.create(*args, &block)
    args, options = extract_options(args)
    options[:name] = args.first if args.first.is_a? Symbol

    config = Config.new(options)
    config = ConfigBuilder.build(config, &block)

    new(config: config)
  end

  # ...
end
```

The builder takes an initial config object, and then evaluates the
block, keeping track of the updated config as it evaluates DSL
methods. Finally you get the updated config object back. [[2](#state_monad_footnote)]


### footnotes

<a id="macro_footnote">[1]</a> I strongly dislike calling all Ruby
class-level methods "macros", especially when they have little to
nothing in common with "real" (i.e. syntax tranforming read-time
functions) macros. In this case what they achieve is very similar to
what you would do with a "real" macro, so I'm rolling with it, adding
sarcastic "quotes" to express my self-loathing in doing so.

<a id="state_monad_footnote">[2]</a> You can think of the Builder as a
state monad. I'm sure that helps.


================================================
FILE: FORMATS.org
================================================
#+TITLE:Comparison of Hypermedia Message Formats
#+AUTHOR: Arne Brasseur
#+email: arne@arnebrasseur.net
#+INFOJS_OPT: view:info toc:nil
#+BABEL: :session *ruby* :cache yes :results output graphics :exports both :tangle yes

* Form Controls / Actions

| Format          | href                            | name/id  | title/caption | method    | media-type      | fields                           | schema      | string template | structured template |
|-----------------+---------------------------------+----------+---------------+-----------+-----------------+----------------------------------+-------------+-----------------+---------------------|
| HTML5           | action=""                       |          |               | method="" | enctype=""      | yes                              |             |                 |                     |
| halo            | "href"                          | json key |               | "method"  | "content-type"  |                                  | "schema"    | "template"      |                     |
| siren           | "href"                          | "name"   | "title"       | "method"  | "type"          | "fields"                         |             |                 |                     |
| mason           | "href"                          | json key | "title"       | "method"  | depends on type |                                  | "schemaUrl" |                 | "template"          |
| Collection+JSON | "href" (query)/current resource |          | "prompt"      |           |                 |                                  |             |                 |                     |
| Hydra           | current resource                | "@type"  |               | "method"  |                 | "expects": {"supportedProperty"} |             |                 |                     |

| Format          | Name       | Title    | Type    | Value   |
|-----------------+------------+----------+---------+---------|
| Siren           | "name"     |          | "type"  | "value" |
| Collection+JSON | "name"     | "prompt" |         | "value" |
| Hydra           | "property" |          | "range" |         |

** HTML

   [[http://www.w3.org/TR/html5/forms.html][W3C: HTML5 Forms]]

** halo+json

   [[https://gist.github.com/mikekelly/893552][Gist: A sketch of application/halo+json and application/halo+xml]]

   #+BEGIN_SRC json
     {
       "_controls": {
         "widgetate": {
           "href": "/widget/{newID}",
           "method": "PUT",
           "content-type": "application/xml",
           "schema": null,
           "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"
         }
       }
     }
   #+END_SRC

** Siren

   [[https://github.com/kevinswiber/siren][Siren Home page]]


   #+BEGIN_SRC json
     {
       "actions": [
         {
           "name": "add-item",
           "title": "Add Item",
           "method": "POST",
           "href": "http://api.x.io/orders/42/items",
           "type": "application/x-www-form-urlencoded",
           "fields": [
             { "name": "orderNumber", "type": "hidden", "value": "42" },
             { "name": "productCode", "type": "text" },
             { "name": "quantity", "type": "number" }
           ]
         }
       ]
     }
   #+END_SRC

** Mason

   #+BEGIN_SRC json
     {
       "@actions": {
         "is:delete-issue": {
           "type": "void",
           "href": "...",
           "method": "DELETE",
           "title": "Delete issue"
         }
       }
     }
   #+END_SRC

   #+BEGIN_SRC json
     {
       "@actions": {
         // JSON action with schema reference
         "is:project-create": {
           "type": "json",
           "href": "...",
           "title": "Create new project",
           "schemaUrl": "..."
         },
         // JSON action with default template
         "is:update-project": {
           "type": "json",
           "href": "...",
           "title": "Update project details",
           "template": {
             "Code": "SHOP",
             "Title": "Webshop",
             "Description": "All issues related to the webshop."
           }
         }
       }
     }
   #+END_SRC


https://github.com/JornWildt/Mason/blob/master/Documentation/Mason-draft-1.md#actions
** Collection+JSON

   CJ does not have a form-like representation. It does allow
   resources to contain a "template", which is really a list of form
   fields, and a client can use HTTP methods against the same endpoint
   to perform CRUD operations. In addition CJ provides "queries" for
   basic GET based operations.

   #+BEGIN_SRC json
     {
       "template" :
       {
         "data" :
         [
           {"prompt" : STRING, "name" : STRING, "value" : VALUE},
           {"prompt" : STRING, "name" : STRING, "value" : VALUE},
           ...
           {"prompt" : STRING, "name" : STRING, "value" : VALUE}
         ]
       }
     }
   #+END_SRC

   #+BEGIN_SRC json
     {
       "queries" :
       [
         {
           "href" : "http://example.org/search",
           "rel" : "search",
           "prompt" : "Enter search string",
           "data" :
           [
             {"name" : "search", "value" : ""}
           ]
         }
       ]
     }
   #+END_SRC
** JSON-LD + Hydra

   Example taken from [[http://sookocheff.com/posts/2014-03-11-on-choosing-a-hypermedia-format/][this blog post]]

   JSON-LD itself does not have form like controls, only
   linking. Hydra introduces an "operation" property for this purpose.

   #+BEGIN_SRC json
     {
       "@context": [
         "http://www.w3.org/ns/hydra/core",
         {
           "@vocab": "https://schema.org/",
           "image": { "@type": "@id" },
           "friends": { "@type": "@id" }
         }
       ],
       "@id": "https://api.example.com/player/1234567890/friends",
       "operation": {
         "@type": "BefriendAction",
         "method": "POST",
         "expects": {
           "@id": "http://schema.org/Person",
           "supportedProperty": [
             { "property": "name", "range": "Text" },
             { "property": "alternateName", "range": "Text" },
             { "property": "image", "range": "URL" }
           ]
         }
       }
     }
   #+END_SRC


================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'

gemspec path: 'yaks'
gemspec path: 'yaks-html'
gemspec path: 'yaks-sinatra'

# Transit depends on Oj, which is not available for JRuby
unless defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
  gemspec path: 'yaks-transit'
end

if RUBY_VERSION < '2'
  gem 'mime-types', [ '>= 2.6.2', '< 3' ]
end

# gem 'mutant', github: 'mbj/mutant'
# gem 'mutant-rspec', github: 'mbj/mutant'


================================================
FILE: IDENTIFIERS.md
================================================
# Identifiers

In Yaks, and Hypermedia message formats in general, a number of
different types of identifiers are used. Some are full URIs and
correspond with well defined specs. Some are just short identifers
that are easy to program with.

Understanding these types of identifiers is key to creating a unifying
model of a "Resource" that can be shared across output formats. We
want to unify as much as possible across formats, without conflating
things that are really not the same.

This document reflects my current limited understanding of things,
based on possibly incorrect assumptions. Feedback is more than
welcome.

## rels

As used in HTML and Atom, these identifiers say what the relationship
is between a resource and another resource it links to. There is a
[registry of names](http://www.iana.org/assignments/link-relations/link-relations.xhtml),
e.g. self, next, profile, stylesheet. Custom rels need to be fully
qualified URLs. Keep in mind that these are simply opaque identifiers,
but by using a known protocol like http they can be used to point at
documentation.

Some examples

```
copyright
stylesheet
http://api.example.com/rel/author
http://api.example.com/api-docs/relationships#comment
custom_scheme:foo
/order
```

The last example is a relative URL, which would have to be expanded against the source URL of the document it is mentioned in.

In Yaks both links and subresources are specified with their rel(ationship).

```ruby
class PersonMapper < Yaks::Mapper
  link :self, '/people/{id}'
  link 'http://api.example.com/rels#friends', '/people/{id}/friends'

  has_one :address, rel: 'http://api.example.com/rels#address'
end
```

For subresources the rel can be omitted, in which case it will be inferred based on the rel_template:

```ruby
$yaks = Yaks.new do
  rel_template 'http://api.example.com/rels/{dest}'
end
```

Links and subresources are rendered keyed by rel in HAL and Collection+JSON. JSON-API renders `self` links as the `href` of a resource.

## profiles

A specific IANA registered rel type is profile.

> Profile: Identifying that a resource representation conforms to a certain profile, without affecting the non-profile semantics of the resource representation.

Profile 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.

## "type"

Despite 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.

The type of a mapper is inferred from its class name, but can be set explicitly as well.

```ruby
class CatMapper < Yaks::Mapper
end

# type = "cat"
```

```ruby
class CatMapper < Yaks::Mapper
  type 'feline'
end

# type => "feline"
```

## rdf class

RDF (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

> http://www.w3.org/TR/2004/REC-owl-guide-20040210/wine#Merlot

(source [wikipedia](http://en.wikipedia.org/wiki/Resource_Description_Framework))

Not currently used by Yaks, but might become important when implementing support for JSON-LD or other RDF serialization formats.

## CURIES

CURIES 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.

From the HAL spec:

```json
{
    "_links": {
        "self": { "href": "/orders" },
        "curies": [{ "name": "ea", "href": "http://example.com/docs/rels/{rel}", "templated": true }],
        "next": { "href": "/orders?page=2" },
        "ea:find": {
            "href": "/orders{?id}",
            "templated": true
        },
        "ea:admin": [{
            "href": "/admins/2",
            "title": "Fred"
        }, {
            "href": "/admins/5",
            "title": "Kate"
        }]
    }
}
```

In this case "ea:find" is just a shorthand for "http://example.com/docs/rels/find".


================================================
FILE: LICENSE
================================================
Copyright (c) 2013-2014 Arne Brasseur

Permission 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:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE 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.


================================================
FILE: Rakefile
================================================
require 'yaks'
require 'yaks-html'
require 'yaks-sinatra'
require 'yaks-transit'

require 'rspec/core/rake_task'
require 'rubocop/rake_task'
require 'rubygems/package_task'
require 'yard'

def delegate_task(gem, task_name)
  task task_name do
    chdir gem.to_s do
      sh "rake", task_name.to_s
    end
  end
end

[:yaks, :"yaks-html", :"yaks-sinatra"].each do |gem|
  namespace gem do
    desc 'Run rspec'
    delegate_task gem, :rspec

    desc 'Build gem'
    delegate_task gem, :gem

    desc 'Generate YARD docs'
    delegate_task gem, :yard

    desc 'push gem to rubygems'
    task :push => "#{gem}:gem" do
      sh "gem push pkg/#{gem}-#{Yaks::VERSION}.gem"
    end
  end
end

desc "Tag current release and push to Github"
task :tag do
  sh "git tag v#{Yaks::VERSION}"
  sh "git push --tags"
end

desc "Tag, build, and push all gems to rubygems.org"
task :push_all => [
       :tag,
       "yaks:gem",
       "yaks-html:gem",
       "yaks-sinatra:gem",
       "yaks:push",
       "yaks-html:push",
       "yaks-sinatra:push"
     ]
task :push => :push_all

desc "Run all the tests"
task :rspec => ["yaks:rspec", "yaks-html:rspec", "yaks-sinatra:rspec"]

desc 'Run mutation tests'
delegate_task :yaks, :mutant

desc "Start a console"
task :console do
  require 'irb'
  require 'irb/completion'
  ARGV.clear
  IRB.start
end

task :ataru do
  require "ataru"
  Dir.chdir("yaks")
  Ataru::CLI::Application.start(["check", "README.md"])
end

RuboCop::RakeTask.new do |task|
  task.options << '--display-cop-names'
end

task :default => [:rspec, :rubocop]


================================================
FILE: bench/bench.rb
================================================

require 'benchmark/ips'
require 'yaks'

require_relative '../spec/acceptance/models'
require_relative '../spec/fixture_helpers'

Benchmark.ips do |x|
  $yaks = Yaks.new

  input = FixtureHelpers.load_yaml_fixture 'confucius'

  x.report "Simple HAL mapping" do
    $yaks.serialize(input)
  end
end


================================================
FILE: bench/bench_1000.rb
================================================
#!/usr/bin/env ruby

require 'English'
require 'benchmark/ips'
require 'ruby-prof'
require 'yaks'

SIZE = 20
$timestamp = Time.now.utc.iso8601.gsub('-', '').gsub(':', '')
$yaks = Yaks.new

FlatModel = Struct.new(:field1, :field2)
DeepModel = Struct.new(:field, :next)

flat = SIZE.times.map do |i|
  FlatModel.new(i, 'x' * (i % 50))
end

deep = nil
SIZE.times do |i|
  deep = DeepModel.new(i, deep)
end

class FlatMapper < Yaks::Mapper
  attributes :field1, :field2
  link :self, '/model/{field1}'
end

class DeepMapper < Yaks::Mapper
  attributes :field
  link :self, '/model/{field}'
  has_one :next, mapper: DeepMapper
end

def profile!(name)
  RubyProf.start
  yield
  results = RubyProf.stop
  File.open "/tmp/#{name}-#{$timestamp}.out.#{$PROCESS_ID}", 'w' do |file|
    RubyProf::CallTreePrinter.new(results).print(file)
  end
end

do_flat = ->(format) { -> { $yaks.serialize(flat, item_mapper: FlatMapper, format: format) } }
do_deep = ->(format) { -> { $yaks.serialize(deep, mapper: DeepMapper, format: format) } }

10.times { do_flat[:hal][] }
10.times { do_deep[:hal][] }

profile!('flat', &do_flat.(:hal))
profile!('deep', &do_deep.(:hal))
exit

Benchmark.ips(10) do |job|
  Yaks::Format.names.each do |format|
    job.report "#{format} ; #{SIZE} objects in a list ; no nesting", &do_flat.(format)
    job.report "#{format} ; #{SIZE} objects nested", &do_deep.(format)
  end
end


================================================
FILE: code_of_conduct.md
================================================
# Contributor Code of Conduct

As contributors and maintainers of this project, we pledge to respect
all people who contribute through reporting issues, posting feature
requests, updating documentation, submitting pull requests or patches,
and other activities.

We are committed to making participation in this project a
harassment-free experience for everyone, regardless of level of
experience, gender, gender identity and expression, sexual
orientation, disability, personal appearance, body size, race, age, or
religion.

Examples of unacceptable behavior by participants include the use of
sexual language or imagery, derogatory comments or personal attacks,
trolling, public or private harassment, insults, or other
unprofessional conduct.

Project maintainers have the right and responsibility to remove, edit,
or reject comments, commits, code, wiki edits, issues, and other
contributions that are not aligned to this Code of Conduct. Project
maintainers who do not follow the Code of Conduct may be removed from
the project team.

Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported by opening an issue or contacting one or more of the
project maintainers.

This Code of Conduct is adapted from the
[Contributor Covenant](http:contributor-covenant.org), version 1.0.0,
available at
[http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)

================================================
FILE: notes.org
================================================
0.4

* DONE Get rid of profile/rel registry, use policy instead
* DONE pass around policy explicitly instead of through options
* DONE introduce name/type separate from profile
** DONE mapper
** DONE Resource
* DONE allow setting rel types directly on associations, with fallback to policy
* DONE switch to hash-based init of Resource to make it more extensible
* DONE add Resource#type
* DONE not 100% happy yet about nameing of mapper#mapper_name / config#name. Maybe use `type` across the board?
* DONE Fix JsonAPISerializer
* top-level automatic links, e.g. for self and profile
* make HAL plural/singular links configurable from the Yaks.new
* make primitivize configuration instance based, not global
* Have JsonApi add self links as href: attributes
* Move examples to acceptance tests
* Select mapper based on content type
* move to 100% mutcov

pre 0.5

* CURIES/namespaces

Ticketsolve::Api::Yaks = ::Yaks.new do
  policy do
    def derive...
  end

  hal_options.plural_link '...'

  primitivize Date, Time do |o|
    o.iso8601
  end

  rel_template "http://literature.example.com/rel/#{association_name}"

  link :self, "http://api.com/{key}/{id}"
  link :profile, "http://api.com/profile/{key}"

  # and/or
  derive_rel_from_association do |mapper, association|
    "http://literature.example.com/rel/#{association.name}"
  end
end



* DONE 59 lib/yaks/mapper.rb
* DONE 92 lib/yaks/mapper/link.rb
* DONE 37 lib/yaks/mapper/association.rb
* DONE 3 lib/yaks/version.rb
* DONE 13 lib/yaks/mapper/has_many.rb
* DONE 9 lib/yaks/mapper/has_one.rb
* DONE 79 lib/yaks/config.rb
*   79 lib/yaks/mapper/config.rb
*   73 lib/yaks.rb
* DONE 72 lib/yaks/util.rb
* DONE 65 lib/yaks/collection_resource.rb
*   59 lib/yaks/json_api_serializer.rb
*   59 lib/yaks/hal_serializer.rb
*   43 lib/yaks/primitivize.rb
*   37 lib/yaks/mapper/class_methods.rb
* DONE 33 lib/yaks/collection_mapper.rb
*   28 lib/yaks/null_resource.rb
* DONE 27 lib/yaks/resource.rb
*   25 lib/yaks/resource/link.rb
* DONE 23 lib/yaks/fp.rb
*   22 lib/yaks/serializer.rb
*   15 lib/yaks/shared_options.rb
*   15 lib/yaks/default_policy.rb
*   10 lib/yaks/mapper/map_links.rb

http://www.dragoart.com/tuts/4344/1/1/how-to-draw-a-yak.htm
https://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
https://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
https://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
https://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
https://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


* JSON-LD / RDF
** Useful protocols used in RDF.rb

   - to_rdf, to_uri
   - RDF::URI pname, qname
   - RDF::Vocabulary::Term

http://ruby-rdf.github.io/
https://github.com/ruby-rdf/json-ld/
http://schema.org/MusicEvent
https://developers.google.com/structured-data/events/venues
http://json-ld.org/playground/index.html


    http://www.iana.org/assignments/relation/
    http://microformats.org/wiki/rel- and http://microformats.org/profile/


================================================
FILE: shared/rake_tasks.rb
================================================
require 'yaks'
require 'yaks-html'
require 'rubygems/package_task'
require 'rspec/core/rake_task'
require 'yard'

def mutant_task(_gem)
  require 'mutant'
  task :mutant do
    pattern  = ENV.fetch('PATTERN', 'Yaks*')
    opts     = ENV.fetch('MUTANT_OPTS', '').split(' ')
    requires = %w[-ryaks -ryaks/behaviour/optional_includes]
    args     = %w[-Ilib --use rspec --score 100] + requires + opts + [pattern]
    result   = Mutant::CLI.run(args)
    raise unless result == Mutant::CLI::EXIT_SUCCESS
  end
end

def gem_tasks(gem)
  Gem::PackageTask.new(Gem::Specification.load("#{gem}.gemspec")) do |task|
    task.package_dir = '../pkg'
  end

  mutant_task(gem) if RUBY_ENGINE == 'ruby' && RUBY_VERSION >= "2.1.0"

  RSpec::Core::RakeTask.new(:rspec) do |t, _task_args|
    t.rspec_opts = "-Ispec"
    t.pattern = "spec"
  end

  YARD::Rake::YardocTask.new do |t|
    t.files   = ["lib/**/*.rb" "**/*.md"]
    t.options = %w[--output-dir ../doc]
  end
end


================================================
FILE: shared/rspec_config.rb
================================================
require 'rspec/its'
require 'bogus/rspec'
require 'timeout'

# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration

RSpec.configure do |rspec|
  # Set the FULLSTACK environment variable to prevent RSpec from
  # filtering stack traces. This can be useful to debug errors that
  # happen inside third party libraries
  rspec.backtrace_exclusion_patterns = [] if ENV['FULLSTACK']

  # Limits the available syntax to the non-monkey patched syntax that is
  # recommended. For more details, see:
  #   - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
  #   - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
  #   - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
  rspec.disable_monkey_patching!

  # Make sure we stay up to date
  rspec.raise_errors_for_deprecations!

  rspec.expect_with :rspec do |expectations|
    # This option will default to `true` in RSpec 4. It makes the `description`
    # and `failure_message` of custom matchers include text for helper methods
    # defined using `chain`, e.g.:
    #     be_bigger_than(2).and_smaller_than(4).description
    #     # => "be bigger than 2 and smaller than 4"
    # ...rather than:
    #     # => "be bigger than 2"
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end

  # This is configured for us by including bogus/rspec. We do not include rspec-mocks.
  # rspec.mock_with :bogus

  # Mutated code can lead to infinite loops. Consider tests that run
  # too long as having failed
  if defined?(Mutant)
    rspec.around(:each) do |example|
      Timeout.timeout(1, &example)
    end
  end
end


================================================
FILE: yaks/.rspec
================================================
-r spec_helper

================================================
FILE: yaks/README.md
================================================
[![Gem Version](https://badge.fury.io/rb/yaks.png)][gem]
[![Build Status](https://secure.travis-ci.org/plexus/yaks.png?branch=master)][travis]
[![Code Climate](https://codeclimate.com/github/plexus/yaks.png)][codeclimate]
[![Gitter](https://badges.gitter.im/Join Chat.svg)][gitter]

[gem]: https://rubygems.org/gems/yaks
[travis]: https://travis-ci.org/plexus/yaks
[codeclimate]: https://codeclimate.com/github/plexus/yaks
[gitter]: https://gitter.im/plexus/yaks?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge

# Yaks

<img align="left" src="https://raw.githubusercontent.com/plexus/yaks/master/graphics/logo_small.png">

The library that understands hypermedia.

**If you use Yaks please help out by filling out the [Yaks Users Survey](https://docs.google.com/forms/d/1sZB03Vf32igmNmJ7RP8mo8H4VZHcVIpSrUSbvx2xD8s/viewform)**

Yaks takes your data and transforms it into hypermedia formats such as
HAL, JSON-API, or HTML. It allows you to build APIs that are
discoverable and browsable. It is built from the ground up around
linked resources, a concept central to the architecture of the web.

Yaks consists of a resource representation that is independent of any
output type. A Yaks mapper transforms an object into a resource, which
can then be serialized into whichever output format the client
requested. These formats are presently supported:

* HAL
* JSON API
* Collection+JSON
* HTML
* HALO
* Transit

## Table of Contents

- [State of Development](#user-content-state-of-development)
- [Concepts](#user-content-concepts)
- [Mappers](#user-content-mappers)
  - [Attributes](#user-content-attributes)
  - [Forms](#user-content-forms)
    - [Filtering](#user-content-filtering)
  - [Links](#user-content-links)
  - [Associations](#user-content-associations)
  - [Behaviours](#user-content-behaviours)
- [Calling Yaks](#user-content-calling-yaks)
  - [Rack env](#user-content-rack-env)
- [Namespace](#user-content-namespace)
- [Custom attribute/link/subresource handling](#user-content-custom-attributelinksubresource-handling)
- [Resources, Formatters, Serializers](#user-content-resources-formatters-serializers)
- [Formats](#user-content-formats)
  - [HAL](#user-content-hal)
  - [HTML](#user-content-html)
  - [JSON-API](#user-content-json-api)
  - [Collection+JSON](#user-content-collection-json)
  - [Transit](#user-content-transit)
- [Hooks](#user-content-hooks)
- [Policy over Configuration](#user-content-policy-over-configuration)
  - [derive_mapper_from_object](#user-content-derive_mapper_from_object)
  - [derive_mapper_from_association](#user-content-derive_mapper_from_association)
  - [derive_rel_from_association](#user-content-derive_rel_from_association)
- [Primitivizer](#user-content-primitivizer)
- [Integration](#user-content-integration)
- [Real World Usage](#user-content-real-world-usage)
- [Demo](#user-content-demo)
- [Cookbook](#user-content-cookbook)
- [Standards Based](#user-content-standards-based)
- [How to contribute](#user-content-how-to-contribute)
- [License](#user-content-license)

## Packages

- [yaks-sinatra](yaks-sinatra/README.md)
- [yaks-html](yaks-html/README.md)
- [yaks-transit](yaks-transit/README.md)

## State of Development

Recent focus has been on stabilizing the core classes, improving
format support, and increasing test (mutation) coverage. We are
committed to a stable public API and semantic version. On the 0.x line
the minor version is bumped when non-backwards compatible changes are
introduced. After 1.x regular semver conventions will be used.

## Concepts

Yaks is a processing pipeline, you create and configure the pipeline,
then feed data through it.

``` ruby
yaks = Yaks.new do
  default_format :hal
  rel_template 'http://api.example.com/rels/{rel}'
  format_options(:hal, plural_links: [:copyright])
  mapper_namespace ::MyAPI
  json_serializer do |data|
    JSON.dump(data)
  end
end

yaks.call(product)
```

Yaks performs this serialization in three steps

* It *maps* your data to a `Yaks::Resource`
* It *formats* the resource to a syntax tree representation
* It *serializes* to get the final output

For JSON types, the "syntax tree" is just a combination of Ruby primitives, nested arrays and hashes with strings, numbers, booleans, nils.

A Resource is an abstraction shared by all output formats. It can contain key-value attributes, RFC5988 style links, and embedded sub-resources.

To build an API you create a "mapper" for each type of object you want to represent. Yaks takes care of the rest.

For all configuration options see [Yaks::Config::DSL](http://rdoc.info/gems/yaks/frames/Yaks/Config/DSL).

See also the [API Docs on rdoc.info](http://rdoc.info/gems/yaks/frames/file/README.md)

## Mappers

Say your app has a `Post` object for blog posts. To serve posts over your API, define a `PostMapper`

```ruby
class PostMapper < Yaks::Mapper
  link :self, '/api/posts/{id}'

  attributes :id, :title

  has_one :author
  has_many :comments
end
```

Configure a Yaks instance and start serializing!

```ruby
yaks = Yaks.new
yaks.call(post)
```

or a bit more elaborate

```ruby
yaks = Yaks.new do
  default_format :json_api
  rel_template 'http://api.example.com/rels/{rel}'
  format_options(:hal, plural_links: [:copyright])
end

yaks.call(post, mapper: ::PostMapper, format: :hal)
```

### Attributes

Use 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.

For example, if you are representing data that is stored in a Hash, you could do

```ruby
class PostHashMapper < Yaks::Mapper
  attributes :id, :body

  # @param name [Symbol]
  def load_attribute(name)
    object[name]
  end
end
```
The `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 :

```ruby
class CommentMapper < Yaks::Mapper
  attributes :body, :date
  attribute :id do
    "Id-#{object.id}"
  end

  def date
    object.created_at.strftime("at %I:%M%p")
  end
end
```

### Forms

Mapper can contain form defintions, for formats that support them. The
form DSL mimics the HTML5 field and attribute names.

```ruby
class PostMapper < Yaks::Mapper
  attributes :id, :body, :date

  form :add_comment do
    action '/api/comments'
    method 'POST'
    media_type 'application/json'

    text :body
    hidden :post_id, value: -> { object.id }
  end
end
```

TODO: add more info on form element types, attributes, conditional
rendering of forms, dynamic form sections, ...


#### Filtering

You can override `#attributes`, or `#associations`.

```ruby
class SongMapper < Yaks::Mapper
  attributes :title, :duration, :lyrics

  has_one :artist
  has_one :album

  def minimal?
    env['HTTP_PREFER'] =~ /minimal/
  end

  # @return Array<Yaks::Mapper::Attribute>
  def attributes
    return super.reject {|attr| attr.name.equal? :lyrics } if minimal?
    super
  end

  # @return Array<Yaks::Mapper::Association>
  def associations
    return [] if minimal?
    super
  end
end
```

### Links

You 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).

```ruby
class FooMapper < Yaks::Mapper
  link :self, '/api/foo/{id}'
  link 'http://api.foo.com/rels/comments', '/api/foo/{id}/comments'
end
```

To 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.

To partially expand the template, pass an array with field names to expand. e.g.

```ruby
class ProductMapper < Yaks::Mapper
  link 'http://api.foo.com/rels/line_item', '/api/line_items?product_id={product_id}&quantity={quantity}', expand: [:product_id]
end

# "_links": {
#    "http://api.foo.com/rels/line_item": {
#      "href": "/api/line_items?product_id=273&quantity={quantity}",
#      "templated": true
#    }
# }

```

You can pass a proc instead of a template, in that case the proc will
be resolved in the context of the mapper. What this means is that, if
the proc takes no arguments, it will be evaluated with the mapper
instance as the value of `self`. If the proc does take an argument,
then it will receive the mapper instance, and will be evaluated as a
closure, i.e. with access to the scope in which it was defined.

```ruby
class FooMapper < Yaks::Mapper
  link 'http://api.foo.com/rels/go_home', -> { home_url }
  # by default calls object.home_url

  def home_url
    object.setting('home_url')
  end
end
```


To only include links based on certain conditions, add an `:if`
option, passing it a block. The block will be resolved in the context
of the mapper, as explained before.

For example, say you want to notify the consumer of your API that upon
confirming an order, the previously held cart is no longer valid, you
could use the IANA standard `invalidates` rel to communicate this.

``` ruby
class OrderMapper < Yaks::Mapper
  link :invalidates, '/api/cart', if: ->{ env['api.invalidate_cart'] }
end
```

### Associations

Use `has_one` for an association that returns a single object, or `has_many` for embedding a collection.

Options

* `:mapper` : Use a specific for each instance, will be derived from the class name if omitted (see Policy vs Configuration)
* `: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
* `: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
* `:if`: Only render the association if a condition holds
* `:link_if`: Conditionally render the association as a link. A `:href` option is required

```ruby
class ShowMapper < Yaks::Mapper
  has_many :events, href: '/show/{id}/events', link_if: ->{ events.count > 50 }
end
```

### Behaviours

Yaks provides mixins to change how your mappers work. These need to be
required separately, they are not loaded by default.

#### OptionalIncludes

You may choose to not render associations by default, but to only do
so when the client explicitly asks for them. This can be done by
including `Yaks::Behaviour::OptionalIncludes`.

Which associations to load is specified with the the `include` query
parameter. You can use dots to load nested associated.

```ruby
require "yaks/behaviour/optional_includes"

class PostMapper < Yaks::Mapper
  include Yaks::Behaviour::OptionalIncludes

  has_one :author
  has_many :comments
end

class AuthorMapper < Yaks::Mapper
  include Yaks::Behaviour::OptionalIncludes

  has_one :profile
end
```

```
GET /post/42?include=comments,author.profile
```

Note that this will only work when Yaks has access to the Rack
environment. When using an existing integration like `yaks-sinatra`
this will be handled for you.

To force an association to always be included, override its `if`
condition to always return true.

```ruby
require "yaks/behaviour/optional_includes"

class PostMapper < Yaks::Mapper
  include Yaks::Behaviour::OptionalIncludes

  has_one :author
  has_many :comments, if: ->{ true }
end
```

## Calling Yaks

Once you have a Yaks instance, you can call it with `call`
(`serialize` also works but might be deprecated in the future.) Pass
it the data to be serialized, plus options.

* `:env` a Rack environment, see next section
* `: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
* `:mapper` the mapper to be used. Will be inferred if omitted
* `: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.

### Rack env

When serializing, Yaks lets you pass in an `env` hash, which will be made available to all mappers.

```ruby
class FooMapper < Yaks::Mapper
  attributes :bar

  def bar
    if env['something']
      #...
    end
  end
end

yaks = Yaks.new
yaks.call(foo, env: my_env)
```

The env hash will be available to all mappers, so you can use this to
pass around context. In particular context related to the current HTTP
request, e.g. the current logged in user, which is why the recommended
use is to pass in the Rack environment.

If `env` contains a `HTTP_ACCEPT` key (Rack's way of representing the
`Accept` header), Yaks will return the format that most closely
matches what was requested.

<a id="namespace"></a>

## Namespace

Yaks by default will find your mappers for you if they follow the
naming convention of appending 'Mapper' to the model class name. This
(and all other "conventions") can be easily redefined though, see the
<a href="#policy">policy</a> section. If you have your mappers inside a
module, use `mapper_namespace`.

```ruby
module API
  module Mappers
    class PostMapper < Yaks::Mapper
      #...
    end
  end
end

yaks = Yaks.new do
  mapper_namespace API::Mappers
end
```

If your namespace contains a `CollectionMapper`, Yaks will use that
instead of `Yaks::CollectionMapper`, e.g.

```ruby
module API
  module Mappers
    class CollectionMapper < Yaks::CollectionMapper
      link :profile, 'http://api.example.com/profiles/collection'
    end
  end
end
```

You can also have collection mappers based on the type of members the
collection holds, e.g.

```ruby
module API
  module Mappers
    class LineItemCollectionMapper < Yaks::CollectionMapper
      link :profile, 'http://api.example.com/profiles/line_items'
      attributes :total

      def total
        collection.inject(0) do |memo, line_item|
          memo + line_item.price * line_item.quantity
        end
      end
    end
  end
end
```

Yaks will automatically detect and use this collection when
serializing an array of `LineItem` objects. See <a
href="#derive_mapper_from_object">derive_mapper_from_object</a> for
details.


## Custom attribute/link/subresource handling

When inheriting from `Yaks::Mapper`, you can override
`map_attributes`, `map_links` and `map_resources` to skip (or augment)
above methods, and instead implement your own custom mechanism. These
methods take a `Yaks::Resource` instance, and should return an updated
resource. They should not alter the resource instance in-place. For
example

```ruby
class ErrorMapper < Yaks::Mapper
  link :profile, '/api/error'

  def map_attributes(resource)
    attrs = {
      http_code: 500,
      message: object.to_s,
      type: object.class.name.underscore
    }

    case object
    when AllocationException
      attrs[:http_code] = 422
    when ActiveRecord::RecordNotFound
      attrs[:http_code] = 404
      attrs[:type] = "record_not_found"
    end

    resource.update_attributes(attrs)
  end
end
```

## Resources, Formatters, Serializers

Yaks uses an intermediate "Resource" representation to support
multiple output formats. A mapper turns a domain model into a
`Yaks::Resource`. A formatter (e.g. `Yaks::Format::Hal`) takes
the resource and outputs the structure of the target format.

Finally a serializer will take this document structure and turn it
into a string. For JSON documents the intermediate format consists of
Ruby primitives like arrays and hashes. HTML/XML based formats on the
other hand return a [Hexp::Node](https://github.com/plexus/hexp).

For JSON based format there's an extra step between `format` and
`serialize` called `primitivize`, this way Ruby objects which don't
have an equivalent in the JSON spec, like `Symbol` or `Date`, can be
turned into objects that are representable in JSON. See
[Primitiver](#primitivizer).

## Formats

Below follows a brief overview of formats that are available in
Yaks. The maturity of these formats varies, since we depend on people
that use a certain format actively to contribute. Implementing formats
is in generally straightforward, and consists mostly of deciding how
the attributes, links, forms, of a `Yaks::Resource` should be
represented. Depending on the format this might be a subject for
debate. We welcome these discussions, and if your opinion differs from
what ends up in Yaks, it should be trivial to change these
representations for your use case.

### HAL

This is the default. In HAL one decides when building an API which
links can only be singular (e.g. self), and which are always
represented as an array. Yaks defaults to singular as I've found it to
be the most common case. If you want specific links to be plural, then
configure their rel href as such.

```ruby
hal = Yaks.new do
  format_options :hal, plural_links: ['http://api.example.com/rels/foo']
end
```

CURIEs are not explicitly supported (yet), but it's possible to use
them with some manual effort.

The line between a singular resource and a collection is fuzzy in
HAL. To stick close to the spec you're best to create your own
singular types that represent collections, rather than rendering a top
level CollectionResource.

Yaks also has a derived format called HALO, which is a non-standard
extension to HAL which includes form elements.

### HTML

The hypermedia format *par excellence*. Yaks can generate a version of
your API, including links and forms, that is usable straight from a
standard web browser. This allows API interactions to be developed and
tested independent from any client application.

If you let Yaks handle your content type negotiation (i.e. pass it the
rack env, and honour the content type it detects, see
[integration](#integration), simply opening a browser and pointing it
at your API entry point should do the trick.

### JSON-API

```ruby
Yaks.new do
  default_format :json_api
end
```

JSON-API has no concept of outbound links, so these will not be
rendered. Instead the key will be inferred from the mapper class name
by default. This can be changed per mapper:

```ruby
class AnimalMapper < Yaks::Mapper
  type :pet
end
```

Or the policy can be overridden:

```ruby
yaks = Yaks.new do
  derive_type_from_mapper_class do |mapper_class|
    piglatinize(mapper_class.to_s.sub(/Mapper$/, ''))
  end
end
```

For optional includes, see [`Yaks::Behaviour::OptionalIncludes`](#user-content-behaviours).

### Collection+JSON

Collection+JSON has support for write templates. To use them, the `:template`
option can be used. It will map the specified form to a CJ template. Please
notice that CJ only allows one template per representation.

```ruby
Yaks.new do
  default_format :collection_json

  collection_json = Yaks.new do
    format_options :collection_json, template: :my_template_form
  end
end

class PostMapper < Yaks::Mapper
  form :my_template_form do
    # This will be used for template
  end

  form :not_my_template do
    # This won't be used for template
  end
end
```

Subresources aren't mapped because Collection+JSON doesn't really have
that concept.

### Transit

There is experimental support for Transit. The transit gem handles
serialization internally, so there is no intermediate document. The
`format` step already returns the serialized string.

## Hooks

It is possible to hook into the Yaks pipeline to perform extra
processing steps before, after, or around each step. It also possible
to skip a step.

``` ruby
yaks = Yaks.new do
  # Automatically give every resource a self link
  after :map, :add_self_link do |resource|
    resource.add_link(Yaks::Resource::Link.new(:self, "/#{resource.type}/#{resource.attributes[:id]}"))
  end

  # Skip serialization, so Ruby primitives come back instead of JSON
  # This was the default before versions < 0.5.0
  skip :serialize
end
```

<a id="policy"></a>

## Policy over Configuration

It'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.

This 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.

What's worse, is that often the Configuration part is skipped entirely, making it very hard to deviate from the Golden Standard.

There is another old adage, "Policy vs Mechanism". Implement the mechanisms, but don't dictate the policy.

In 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:

```ruby
yaks = Yaks.new do
  mapper_for Post, SpecialMapper

  derive_mapper_from_object do |model|
    # ...
  end

  derive_mapper_from_collection do |collection|
    # ...
  end

  derive_mapper_from_item do |model|
    # ...
  end

  derive_type_from_mapper_class do |mapper_class|
    # ...
  end

  derive_mapper_from_association do |association|
    # ...
  end

  derive_rel_from_association do |mapper, association|
    # ...
  end
end
```

Note that within these blocks, you may call `super()` which would call
the default implementation.

You can also subclass or create from scratch your own policy class

```ruby
class MyPolicy < Yaks::DefaultPolicy
  #...
end

yaks = Yaks.new do
  policy_class MyPolicy
end
```

<a id="derive_mapper_from_object"></a>

### derive_mapper_from_object

This is called when trying to serialize something and no explicit
mapper is given. To recap, it's always possible to be explicit, e.g.

```
yaks.call(widget, mapper: WidgetMapper)
yaks.call(array_of_widgets, mapper: MyCollectionMapper, item_mapper: WidgetMapper)
```

If the mapper is left unspecified, Yaks will inspect whatever you pass
it. First it will test the given object against the mappings defined using `mapper_for`.
If no mapper is found, it will call `derive_mapper_from_item` or `derive_mapper_from_collection`
depending on whether the given object is a collection or not. If the object responds
to `to_ary` it is considered a collection.

### mapper_for

This method allows you to define a one-to-one mapping between a mapping rule and a mapper class.
During the lookup, Yaks will check if any mapping rule matches the given object using the `#===`
operator.

Here are a few examples on how to use it:
```ruby
yaks = Yaks.new do
  mapper_for(:home, HomeMapper)
  mapper_for(Post, SpecialMapper)
  mapper_for(->(author) { author.respond_to?(:name) && author.name == 'doh' }, AuthorMapper)
end

yaks.call(:home) # would map using HomeMapper
yaks.call(Post.new) # would map using PostMapper
yaks.call(Author.new(name: 'doh')) # would map using AuthorMapper
```

### derive_mapper_from_collection
This method will try various constant lookups based on naming. These all happen
in the configured namespace, which defaults to the Ruby top level.

If the first object in the collection has a class of `Widget`, and the
configured namespace is `API`, then these are tried in turn

* `API::WidgetCollectionMapper`
* `API::CollectionMapper`
* `Yaks::CollectionMapper`

Note that Yaks can only find a specific collection mapper for a type
if the collection passed to Yaks contains at least one element. If
it's important that empty collections are handled by the right mapper
(e.g. to set a specific `self` or `profile` link), then you have to be
explicit.

### derive_mapper_from_item

When using this method, the lookup happens based on the class name,
and will traverse up the class hierarchy in the configured namespace if
no suitable mapper is found. Take the following
code:
```ruby
module Stuff
  class Thing ; end
  class Widget < Thing ; end
end
```
The lookup we'll be done as followed.

* If the `namespace` option is set (to `Mappers` for example):
 * `Mappers::Stuff::WidgetMapper`
 * `Mappers::Stuff::ThingMapper`
 * `Mappers::Stuff::ObjectMapper`
 * `Mappers::Stuff::BasicObjectMapper`
 * `Mappers::WidgetMapper`
 * `Mappers::ThingMapper`

* If the `namespace` option is not set:
 * `Stuff::WidgetMapper`
 * `Stuff::ThingMapper`
 * `Stuff::ObjectMapper`
 * `Stuff::BasicObjectMapper`
 * `WidgetMapper`
 * `ThingMapper`

If none of these are found an error is raised.

### derive_mapper_from_association

When no mapper is specified for an association, then this method is
called to find the right mapper, based on the association name. In
case of `has_many` collections this is the "item mapper", the
collection mapper is resolved using `derive_mapper_from_object`.

By default the mapper class is derived from the name of the association, e.g.

```
has_many :widgets #=> WidgetMapper
has_one :widget   #=> WidgetMapper
```

It is always possible to explicitly set a mapper.

```
has_one :widget, mapper: FooMapper
has_many :widgets, collection_mapper: MyCollectionMapper, mapper: FooMapper
```

### derive_rel_from_association

Associations have a "rel", an IANA registered identifier or fully
qualified URI, that specifies how the object relates to the parent
document.

When configuring Yaks one can set a `rel_template`, that will be used
to generate these rels if not explicitly given. The `rel` placeholder
in the template will be substituted with the association name.

``` ruby
yaks = Yaks.new do
  rel_template "http://api.example.com/rel/{rel}"
end

class MyMapper < Yaks::Mapper
  # rel: "http://api.example.com/rel/widgets"
  has_many :widgets

  # rel: "http://api.example.com/rel/widget"
  has_one :widget
end
```

<a id="primitivizer"></a>

## Primitivizer

For 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`

Here's an example with a custom `Currency` class, which can be represented as an integer.

```ruby
Yaks.new do
  map_to_primitive Currency do |currency|
    currency.to_i
  end
end
```

One notable use case is representing dates and times. The JSON
specification does not define any syntax for these, so the only
solution is to represent them either as numbers or strings. If you're
not sure what to do with these then the ISO8601 standard is a safe
bet. It defines a way to represent times and dates as strings, and is
also adopted by the W3C in [RFC3339](http://tools.ietf.org/html/rfc3339).

An alternative representation that is sometimes used is "unix time",
defined as the numbers of seconds passed since 1 January 1970.

Here's an example for a Rails app, so including ActiveSupport's `TimeWithZone`.

```ruby
Yaks.new do
  map_to_primitive Date, Time, DateTime, ActiveSupport::TimeWithZone, &:iso8601
end
```

`map_to_primitive` can also be used to transform alternative data
structures, like those from [Hamster](https://github.com/hamstergem/hamster),
into Ruby arrays and hashes. Use `call()` to recursively turn things into
primitives.

```ruby
Yaks.new do
  map_to_primitive Hamster::Vector, Hamster::List do |list|
    list.map do |item|
      call(item)
    end
  end
end
```

Yaks by default "primitivizes" symbols (as strings), and classes that include Enumerable (as arrays).


<a id="integration"></a>

## Integration

It is recommended to let Yaks handle the negotiation of media types,
so that consumer can request the format they prefer using an `Accept:`
header. To do this requires two steps: first make sure you pass the
rack env to Yaks, this way it will detect any `Accept` header and
honor it. While this is enough to get the correct serialized output,
it will likely be served up with the wrong `Content-Type` header by
your web framework.

To fix this, ask Yaks first for the "runner" for a given input, then
get the media type and serialized resource from the runner.

```ruby
# Tell your web framework about the supported formats
Yaks::Format.all.each do |format|
  mime_type format.format_name, format.media_type
end

# one time Yaks configuration
yaks = Yaks.new

# on each request
runner = yaks.runner(post, env: rack_env)
format = runner.format_name
output = runner.call
```


## Real World Usage

Yaks is used in production by

* [Ticketsolve](http://www.ticketsolve.com/). You can find an example API endpoint [here](http://leicestersquaretheatre.ticketsolve.com/api).
* Advertile Mobile for their product AppBounty (internal API)

## Demo

You 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).

## Cookbook

See the [cookbook](COOKBOOK.md) for some usage examples taking from a real world app.

## Standards Based

Yaks is based on internet standards, including

* [RFC4288 Media types](http://tools.ietf.org/html/rfc4288)
* [RFC5988 Web Linking](http://tools.ietf.org/html/rfc5988)
* [RFC6906 The "profile" link relation](http://tools.ietf.org/search/rfc6906)
* [RFC6570 URI Templates](http://tools.ietf.org/html/rfc6570)
* [RFC4229 HTTP Header Field Registrations](http://tools.ietf.org/html/rfc4229).

## How to contribute

Run the tests, the examples, try it with your own stuff and leave your impressions in the issues.

To fix a bug

1. Fork the repo
2. Fix the bug, add tests for it
3. Push it to a named branch
4. Add a PR

To add a feature

1. Open an issue as soon as possible to gather feedback
2. Same as above, fork, push to named branch, make a pull-request

Yaks uses [Mutation Testing](https://github.com/mbj/mutant). Run `rake mutant` and look for percentage coverage. In general this should only go up.

## License

MIT License (Expat License), see [LICENSE](./LICENSE)

![](shaved_yak.gif)


================================================
FILE: yaks/Rakefile
================================================
load '../shared/rake_tasks.rb'

gem_tasks(:yaks)

task :mutant_chunked do
  # No subjects:
  # Yaks,
  # Yaks::Error,
  # Yaks::IllegalStateError,
  # Yaks::UnsupportedOperationError,
  # Yaks::PrimitivizeError,
  # Yaks::Undefined,
  # Yaks::HTML5Forms,

  # Hangs:
  # Yaks::Changelog,

  # 100% verified:
  # Yaks::Util,
  # Yaks::Util::Deprecated,
  # Yaks::FP
  # Yaks::FP::Callable,
  # Yaks::DefaultPolicy,
  # Yaks::Mapper::HasOne,
  # Yaks::Mapper::HasMany,
  # Yaks::Mapper::Attribute,
  # Yaks::Mapper::Config,
  # Yaks::Mapper::ClassMethods,
  # Yaks::Mapper::AssociationMapper,
  # Yaks::Format::CollectionJson,
  # Yaks::Config,
  # Yaks::Config::DSL,
  # Yaks::Attributes::InstanceMethods,
  # Yaks::Configurable,
  # Yaks::NullResource,
  # Yaks::Runner,

  [
    # >> Yaks::Attributes
    # >> Yaks::Attributes::InstanceMethods
    # >> Yaks::Builder
    # >> Yaks::CollectionMapper
    # >> Yaks::CollectionResource
    # >> Yaks::Config
    # >> Yaks::Configurable
    # >> Yaks::DefaultPolicy
    # >> Yaks::Error
    # >> Yaks::FP
    # >> Yaks::FP::Callable
    # >> Yaks::Format
    # >> Yaks::Format::CollectionJson
    # >> Yaks::Format::HTML
    # >> Yaks::Format::Hal
    # >> Yaks::Format::Halo
    # >> Yaks::Format::JsonAPI
    # >> Yaks::Format::Reader
    # >> Yaks::Format::Transit
    # >> Yaks::Format::Transit::ReadHandler
    # >> Yaks::Format::Transit::WriteHandler
    # >> Yaks::HTML5Forms
    # >> Yaks::IllegalStateError
    # >> Yaks::Mapper
    # >> Yaks::Mapper::Association
    # >> Yaks::Mapper::AssociationMapper
    # >> Yaks::Mapper::Attribute
    # >> Yaks::Mapper::Config
    # >> Yaks::Mapper::Form
    # >> Yaks::Mapper::Form::Config
    # >> Yaks::Mapper::Form::Field
    # >> Yaks::Mapper::Form::Field::Option
    # >> Yaks::Mapper::Form::Fieldset
    # >> Yaks::Mapper::HasMany
    # >> Yaks::Mapper::HasOne
    # >> Yaks::Mapper::Link
    # >> Yaks::NullResource
    # >> Yaks::Pipeline
    # >> Yaks::Primitivize
    # >> Yaks::PrimitivizeError
    # >> Yaks::Reader
    # >> Yaks::Reader::Hal
    # Yaks::Resource,
    # Yaks::Resource::Form,
    # Yaks::Resource::Form::Field,
    # Yaks::Resource::Form::Field::Option,
    # Yaks::Resource::Form::Fieldset,
    # Yaks::Resource::Link,
    Yaks::Resource::HasFields,
    # >> Yaks::Runner
    # >> Yaks::RuntimeError
    # >> Yaks::Serializer
    # >> Yaks::Serializer::JSONReader
    # >> Yaks::Serializer::JSONWriter
    # >> Yaks::Undefined
    # >> Yaks::UnsupportedOperationError
    # >> Yaks::Util
    # >> Yaks::Util::Deprecated
  ].each do |space|
    puts space
    ENV['PATTERN'] = "#{space}"
    Rake::Task["mutant"].execute
    break
  end
end


================================================
FILE: yaks/ataru_setup.rb
================================================
# "Require your project source code, with the correct path"

require "yaks"
require "hamster"

Post = Struct.new(:id, :title, :author, :comments)
Author = Struct.new(:name)

module MyAPI
  Product = Struct.new(:id, :label)

  class ProductMapper < Yaks::Mapper
    attributes :id, :label
  end
end

class AuthorMapper < Yaks::Mapper
end

class CommentMapper < Yaks::Mapper
end

class PostMapper < Yaks::Mapper
  link :self, '/api/posts/{id}'

  attributes :id, :title

  has_one :author
  has_many :comments
end

class HomeMapper < Yaks::Mapper; end

class SpecialMapper < Yaks::Mapper; end

module ActiveSupport
  class TimeWithZone < Time ; end
end

class Currency ; end

module Setup
  def setup
    # Do some nice setup that is run before every snippet
    # If you'd like to use instance variables define them here, e.g
    #  @important_variable_i_will_use_in_my_code_snippets = true
  end

  def teardown
    # Do some cleanup that is run after every snippet
  end

  # If you like local variables you can define methods, e.g
  # def number_of_wishes
  #  101
  # end

  def my_env
    {'something' => true}
  end
  alias_method :rack_env, :my_env

  def post
    Post.new(7, "Yaks is Al Dente", nil, [])
  end
  alias_method :foo, :post

  def product
    MyAPI::Product.new(42, "Shiny thing")
  end

  # # Tell your web framework about the supported formats
  # Yaks::Format.all.each do |format|
  #   mime_type format.format_name, format.media_type
  # end
  def mime_type(*_args)
  end
end


================================================
FILE: yaks/find_missing_tests.rb
================================================
#!/usr/bin/env ruby

require 'mutant'
require 'pry'

# These are private methods that are tested by other methods in the same class
SKIP = %w[
  Yaks::CollectionMapper#collection_rel
  Yaks::CollectionMapper#collection_type
  Yaks::CollectionMapper#mapper_for_model
  Yaks::Resource::Form::Field#select_options_for_value
  Yaks::Mapper::AssociationMapper#add_link
  Yaks::Mapper::AssociationMapper#add_subresource
  Yaks::Mapper::Link#resource_link_options
]

args = ["-Ilib", "-ryaks", "--use", "rspec", "Yaks*"]
env = Mutant::Env::Bootstrap.call(Mutant::CLI.call(args))

integration = env.config.integration

integration.setup
binding.pry if integration.all_tests.empty? # rubocop:disable Lint/Debugger

env.subjects.each do |subject|
  match_expression = subject.match_expressions.first
  subject_tests = integration.all_tests.select do |test|
    match_expression.prefix?(test.expression)
  end
  unless subject_tests.any? || SKIP.include?(subject.expression.syntax)
    puts subject.identification
    exit if ARGV.include?("-1")
  end
end


================================================
FILE: yaks/lib/yaks/behaviour/optional_includes.rb
================================================
require "rack/utils"

module Yaks
  module Behaviour
    module OptionalIncludes
      RACK_KEY = "yaks.optional_includes".freeze

      def associations
        super.select do |association|
          association.if != Undefined || include_association?(association)
        end
      end

      private

      def include_association?(association)
        includes = env.fetch(RACK_KEY) do
          query_string = env.fetch("QUERY_STRING", nil)
          query = Rack::Utils.parse_query(query_string)
          env[RACK_KEY] = query.fetch("include", "").split(",").map { |r| r.split(".") }
        end

        includes.any? do |relationship|
          relationship[mapper_stack.size].eql?(association.name.to_s)
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/breaking_changes.rb
================================================
module Yaks
# These are displayed in a post-install message when installing the
# gem to aid upgraiding

BreakingChanges = {

  '0.7.6' => %q~
Breaking Changes in Yaks 0.7.6
==============================
Breaking change: using a symbol instead of link template no longer
works, use a lambda.

    link :foo, :bar

Becomes

    link :foo, ->{ bar }

Strictly speaking the equivalent version would be `link :foo, ->{
load_attribute(:bar) }`. Depending on if `bar` is implemented on the
mapper or is an attribute of the object, this would simplify to `link
:foo, ->{ bar }` or `link :foo, ->{ object.bar }` respectively.

The `href` attribute of a control has been renamed `action`, in line
with the attribute name in HTML. An alias is available but will output
a deprecation warning.
~,

  '0.7.0' => %q~
Breaking Changes in Yaks 0.7.0
==============================
Yaks::Resource#subresources is now an array, not a hash. The rel is
stored on the resource itself as Yaks::Resource#rels (an array). This
should only be of concern if you implement custom output formats

The general signature of all processing steps (mapper, formatter,
hooks) has changed to incldue a second parameter, the rack env. If you
have custom implementations of any of these, or hooks that are not
specified as ruby blocks, you will need to take this into account
~,

  '0.5.0' => %q~

Breaking Changes in Yaks 0.5.0
==============================

Yaks now serializes its output, you no longer have to convert to JSON
yourself. Use `skip :serialize' to get the old behavior, or
`json_serializer` to use a different JSON implementation.

The single `after' hook has been replaced with a set of `before',
`after', `around' and `skip' hooks.

If you've created your own subclass of `Yaks::Format' (previously:
`Yaks::Serializer'), then you need to update the call to
`Format.register'.

These are potentially breaking changes. See the CHANGELOG and README
for full documentation.

~,

  '0.4.3' => %q~

Breaking Changes in Yaks 0.4.3
==============================

Yaks::Mapper#filter was removed, if you override this method in your
mappers to conditionally filter attributes or associations, you will
have to override #attributes or #associations instead.

When specifying a rel_template, now a single {rel} placeholder is
expected instead of {src} and {dest}.

There are other internal changes. See the CHANGELOG and README for full
documentation.

~
}

BreakingChanges['0.4.4'] = BreakingChanges['0.4.3']
BreakingChanges['0.7.1'] = BreakingChanges['0.7.0']
end


================================================
FILE: yaks/lib/yaks/builder.rb
================================================
module Yaks
  # State monad-ish thing.
  #
  # Generate a DSL syntax for immutable classes.
  #
  # @example
  #
  #   # This code
  #   Form.create(:search)
  #       .method("POST")
  #       .action("/search")
  #
  #   # Can be written as
  #   Builder.new(Form, [:method, :action]).create(:search) do
  #     method "POST"
  #     action "/search"
  #   end
  #
  class Builder
    include Configurable

    def initialize(klass, methods = [], &block)
      @klass = klass
      @methods = methods
      def_forward(*methods) if methods.any?
      instance_eval(&block) if block
    end

    def create(*args, &block)
      build(@klass.create(*args), &block)
    end

    def build(init_state, *extra_args, &block)
      @config = init_state
      instance_exec(*extra_args, &block) if block
      @config
    end

    def inspect
      "#<Builder #{@klass} #{@methods}>"
    end
  end
end


================================================
FILE: yaks/lib/yaks/changelog.rb
================================================
module Yaks
  module Changelog
    module_function

    def current
      versions[Yaks::VERSION]
    end

    def versions
      markdown.split(/(?=###\s*[\d\w\.]+\n)/).each_with_object({}) do |section, hsh|
        version = section.each_line.first[/[\d\w\.]+/]
        log     = section.each_line.drop(1).join.strip
        hsh[version] = log
      end
    end

    def markdown
      Pathname(__FILE__).join('../../../../CHANGELOG.md').read
    end
  end
end


================================================
FILE: yaks/lib/yaks/collection_mapper.rb
================================================
module Yaks
  class CollectionMapper < Mapper
    alias_method :collection, :object

    # @param [Array] collection
    # @return [Array]
    def call(collection, _env = nil)
      @object = collection

      attrs = {
        type: collection_type,
        members: collection().map do |obj|
          mapper_for_model(obj).new(context).call(obj)
        end
      }

      # For collections from associations the rel will be based on the
      # association. At the top level there's no association, so we
      # use a generic rel. This matters especially for HAL, where a
      # top-level collection is rendered as an object with the
      # collection as a subresource.
      attrs[:rels] = [collection_rel] if context[:mapper_stack].empty?

      map_attributes(
        map_links(
          CollectionResource.new(attrs)
        )
      )
    end

    private

    def collection_rel
      if collection_type
        policy.expand_rel(pluralize(collection_type))
      else
        'collection'
      end
    end

    def collection_type
      if item_mapper = context[:item_mapper]
        item_mapper.config.type || policy.derive_type_from_mapper_class(item_mapper)
      else
        policy.derive_type_from_collection(collection)
      end
    end

    def mapper_for_model(model)
      context.fetch(:item_mapper) do
        policy.derive_mapper_from_object(model)
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/collection_resource.rb
================================================
module Yaks
  # A collection of Resource objects, it has members, and its own set of link
  # relations like self and profile describing the collection.
  #
  # A collection can be the top-level result of an API call, like all posts to
  # a blog, or a subresource collection, like the comments on a post result.
  #
  class CollectionResource < Resource
    include attributes.add(members: [])

    extend Forwardable
    def_delegators :members, :each, :map, :each_with_object

    # @return [Boolean]
    def collection?
      true
    end

    def seq
      self
    end
  end
end


================================================
FILE: yaks/lib/yaks/config.rb
================================================
module Yaks
  class Config
    extend Yaks::Util::Deprecated
    include Yaks::FP::Callable,
            Attribs.new(
              format_options_hash: Hash.new({}),
              default_format: :hal,
              policy_options: {},
              policy_class: DefaultPolicy,
              primitivize: Primitivize.create,
              serializers: Serializer.all,
              hooks: []
            )

    class << self
      alias_method :create, :new
    end

    deprecated_alias :namespace, :mapper_namespace

    def format_options(format, options)
      with(format_options_hash: format_options_hash.merge(format => options))
    end

    def serializer(type, &serializer)
      with(serializers: serializers.merge(type => serializer))
    end

    def json_serializer(&serializer)
      serializer(:json, &serializer)
    end

    %w[before after around skip].map(&:intern).each do |hook_type|
      define_method hook_type do |step, name = :"#{hook_type}_#{step}", &block|
        append_to(:hooks, [hook_type, step, name, block])
      end
    end

    def rel_template(template)
      with(policy_options: policy_options.merge(rel_template: template))
    end

    def mapper_namespace(namespace)
      with(policy_options: policy_options.merge(namespace: namespace))
    end

    def mapper_for(rule, mapper_class)
      policy_options[:mapper_rules] ||= {}
      mapper_rules = policy_options[:mapper_rules].merge(rule => mapper_class)
      with(policy_options: policy_options.merge(mapper_rules: mapper_rules))
    end

    def map_to_primitive(*args, &block)
      with(primitivize: primitivize.dup.tap { |prim| prim.map(*args, &block) })
    end

    DefaultPolicy.public_instance_methods(false).each do |method|
      define_method method do |&block|
        with(
          policy_class: Class.new(policy_class) do
            define_method method, &block
          end
        )
      end
    end

    # @return [Yaks::DefaultPolicy]
    def policy
      @policy ||= @policy_class.new(@policy_options)
    end

    def runner(object, options)
      Runner.new(config: self, object: object, options: options)
    end

    # Main entry point into yaks
    #
    # @param object [Object] The object to serialize
    # @param options [Hash<Symbol,Object>] Serialization options
    #
    # @option env [Hash] The rack environment
    # @option format [Symbol] The target format, default :hal
    # @option mapper [Class] Mapper class to use
    # @option item_mapper [Class] Mapper class to use for items in a top-level collection
    #
    def call(object, options = {})
      runner(object, options).call
    end
    alias_method :serialize, :call

    def map(object, options = {})
      runner(object, options).map
    end

    def format(data, options = {})
      runner(data, options).format
    end

    def read(data, options = {})
      runner(data, options).read
    end
  end
end


================================================
FILE: yaks/lib/yaks/configurable.rb
================================================
module Yaks
  # A "Configurable" class is one that keeps a configuration in a
  # separate immutable object, of type class::Config. say you have
  #
  #     class MyMapper < Yaks::Mapper
  #       # use yaks configuration DSL in here
  #     end
  #
  # The links, associations, etc, that you set up for MyMapper, will
  # be available in MyMapper.config, which is an instance of
  # Yaks::Mapper::Config.
  #
  # Each configuration step, like `link`, `has_many`, will replace
  # MyMapper.config with an updated version, discarding the old
  # config.
  #
  # By extending Configurable, a number of "macros" become available
  # to describe the DSL that subclasses can use. See the docs for
  # `def_set`. `def_forward`, and `def_add`.
  module Configurable
    attr_accessor :config

    def self.extended(child)
      child.config = child::Config.new
    end

    def inherited(child)
      child.config = config
    end

    # Create a DSL method to set a certain config property. The
    # generated method will take either a plain value, or a block,
    # which will be captured and stored instead.
    def def_set(*method_names)
      method_names.each do |method_name|
        define_singleton_method method_name do |arg = Undefined, &block|
          if arg.equal?(Undefined)
            unless block
              raise ArgumentError, "setting #{method_name}: no value and no block given"
            end
            self.config = config.with(method_name => block)
          else
            if block
              raise ArgumentError, "ambiguous invocation setting #{method_name}: give either a value or a block, not both."
            end
            self.config = config.with(method_name => arg)
          end
        end
      end
    end

    # Forward a method to the config object. This assumes the method
    # will return an updated config instance.
    #
    # Either takes a list of methods to forward, or a mapping (hash)
    # of source to destination method name.
    def def_forward(mappings, *names)
      if mappings.instance_of? Hash
        mappings.each do |method_name, target|
          define_singleton_method method_name do |*args, &block|
            self.config = config.public_send(target, *args, &block)
          end
        end
      else
        def_forward([mappings, *names].map{|name| {name => name}}.inject(:merge))
      end
    end

    # Generate a DSL method that creates a certain type of domain
    # object, and adds it to a list on the config.
    #
    #     def_add :fieldset, create: Fieldset, append_to: :fields
    #
    # This will generate a `fieldset` method, which will call
    # `Fieldset.create`, and append the result to `config.fields`
    def def_add(name, options)
      old_verbose, $VERBOSE = $VERBOSE, false # skip method redefinition warning
      define_singleton_method name do |*args, &block|
        defaults = options.fetch(:defaults, {})
        klass    = options.fetch(:create)

        if args.last.instance_of?(Hash)
          args[-1] = defaults.merge(args[-1])
        else
          args << defaults
        end

        self.config = config.append_to(
          options.fetch(:append_to),
          klass.create(*args, &block)
        )
      end
    ensure
      $VERBOSE = old_verbose
    end
  end
end


================================================
FILE: yaks/lib/yaks/default_policy.rb
================================================

module Yaks
  class DefaultPolicy
    include Util

    # Default policy options.
    DEFAULTS = {
      rel_template: "rel:{rel}",
      namespace: Object,
      mapper_rules: {}
    }

    # @!attribute [r]
    #   @return [Hash]
    attr_reader :options

    # @param options [Hash] options
    def initialize(options = {})
      @options = DEFAULTS.merge(options)
    end

    # Main point of entry for mapper derivation. Calls
    # derive_mapper_from_collection or derive_mapper_from_item
    # depending on the model.
    #
    # @param model [Object]
    # @return [Class] A mapper, typically a subclass of Yaks::Mapper
    #
    # @raise [RuntimeError] occurs when no mapper is found
    def derive_mapper_from_object(model)
      mapper = detect_configured_mapper_for(model)
      return mapper if mapper
      return derive_mapper_from_collection(model) if model.respond_to? :to_ary
      derive_mapper_from_item(model)
    end

    # Derives a mapper from the given collection.
    #
    # @param collection [Object]
    # @return [Class] A mapper, typically a subclass of Yaks::Mapper
    def derive_mapper_from_collection(collection)
      if m = collection.first
        name = "#{m.class.name.split('::').last}CollectionMapper"
        begin
          return @options[:namespace].const_get(name)
        rescue NameError               # rubocop:disable Lint/HandleExceptions
        end
      end
      begin
        return @options[:namespace].const_get(:CollectionMapper)
      rescue NameError                 # rubocop:disable Lint/HandleExceptions
      end
      CollectionMapper
    end

    # Derives a mapper from the given item. This item should not
    # be a collection.
    #
    # @param item [Object]
    # @return [Class] A mapper, typically a subclass of Yaks::Mapper
    #
    # @raise [RuntimeError] only occurs when no mapper is found for the given item.
    def derive_mapper_from_item(item)
      klass = item.class
      namespaces = klass.name.split("::")[0...-1]
      begin
        return build_mapper_class(namespaces, klass)
      rescue NameError
        klass = next_class_for_lookup(item, namespaces, klass)
        retry if klass
      end
      raise_mapper_not_found(item)
    end

    # Derive the a mapper type name
    #
    # This returns the 'system name' for a mapper,
    # e.g. ShowEventMapper => show_event.
    #
    # @param [Class]  mapper_class
    #
    # @return [String]
    def derive_type_from_mapper_class(mapper_class)
      underscore(mapper_class.name.split('::').last.sub(/Mapper$/, ''))
    end

    # Derive the mapper type name from a collection
    #
    # This inspects the first element of the collection, so
    # it requires a collection with truthy elements. Will
    # return `nil` if the collection has no truthy elements.
    #
    # @param [#first] collection
    #
    # @return [String|nil]
    #
    # @raise [NameError]
    def derive_type_from_collection(collection)
      return if collection.none?
      derive_type_from_mapper_class(derive_mapper_from_object(collection.first))
    end

    def derive_mapper_from_association(association)
      @options[:namespace].const_get("#{camelize(association.singular_name)}Mapper")
    end

    # @param association [Yaks::Mapper::Association]
    # @return [String]
    def derive_rel_from_association(association)
      expand_rel(association.name)
    end

    # @param relname [String]
    # @return [String]
    def expand_rel(relname)
      URITemplate.new(@options[:rel_template]).expand(rel: relname)
    end

    private

    def build_mapper_class(namespaces, klass)
      mapper_class = "#{klass.name.split('::').last}Mapper"
      [*namespaces, mapper_class].inject(@options[:namespace]) do |namespace, module_or_class|
        namespace.const_get(module_or_class, false)
      end
    end

    def next_class_for_lookup(item, namespaces, klass)
      superclass = klass.superclass
      return superclass if superclass < Object
      return nil if namespaces.empty?
      namespaces.clear
      item.class
    end

    def raise_mapper_not_found(item)
      namespace = "#{@options[:namespace]}::" unless Object.equal?(@options[:namespace])
      mapper_class = "#{namespace}#{item.class}Mapper"
      raise "Failed to find a mapper for #{item.inspect}. Did you mean to implement #{mapper_class}?"
    end

    def detect_configured_mapper_for(object)
      @options[:mapper_rules].each do |rule, mapper_class|
        return mapper_class if rule === object # rubocop:disable Style/CaseEquality
      end
      nil
    end
  end
end


================================================
FILE: yaks/lib/yaks/errors.rb
================================================
module Yaks
  Error = Class.new(StandardError)

  IllegalStateError         = Class.new(Error)
  RuntimeError              = Class.new(Error)
  UnsupportedOperationError = Class.new(Error)
  PrimitivizeError          = Class.new(Error)
end


================================================
FILE: yaks/lib/yaks/format/collection_json.rb
================================================
module Yaks
  class Format
    class CollectionJson < self
      register :collection_json, :json, 'application/vnd.collection+json'

      include FP

      # @param [Yaks::Resource] resource
      # @return [Hash]
      def serialize_resource(resource)
        result = {
          version: "1.0",
          items: serialize_items(resource)
        }
        result[:href] = resource.self_link.uri if resource.self_link
        result[:links] = serialize_links(resource) if links?(resource)
        result[:queries] = serialize_queries(resource) if queries?(resource)
        result[:template] = serialize_template(resource) if template?(resource)
        {collection: result}
      end

      # @param [Yaks::Resource] resource
      # @return [Array]
      def serialize_items(resource)
        resource.seq.map do |item|
          attrs = item.attributes.map do |name, value|
            {
              name: name,
              value: value
            }
          end
          result = {data: attrs}
          result[:href] = item.self_link.uri if item.self_link
          item.links.each do |link|
            next if link.rel.equal? :self
            result[:links] = [] unless result.key?(:links)
            result[:links] << {rel: link.rel, href: link.uri}
            result[:links].last[:name] = link.title if link.title
          end
          result
        end
      end

      def serialize_links(resource)
        resource.links.each_with_object([]) do |link, result|
          result << {href: link.uri, rel: link.rel}
        end
      end

      def serialize_queries(resource)
        resource.forms.each_with_object([]) do |form, result|
          next unless form_is_query? form

          result << {rel: form.name, href: form.action}
          result.last[:prompt] = form.title if form.title

          form.fields_flat.each do |field|
            result.last[:data] = [] unless result.last.key? :data
            result.last[:data] << {name: field.name, value: nil.to_s}
            result.last[:data].last[:prompt] = field.label if field.label
          end
        end
      end

      def queries?(resource)
        resource.forms.any? { |f| form_is_query? f }
      end

      def links?(resource)
        resource.collection? && resource.links.any?
      end

      def template?(resource)
        options.key?(:template) && template_form_exists?(resource)
      end

      protected

      def form_is_query?(form)
        form.method?(:get) && form.has_action?
      end

      def template_form_exists?(resource)
        !resource.find_form(options.fetch(:template)).nil?
      end

      def serialize_template(resource)
        fields = resource.find_form(options.fetch(:template)).fields
        result = {data: []}
        fields.each do |field|
          result[:data] << {name: field.name, value: nil.to_s}
          result[:data].last[:prompt] = field.label if field.label
        end
        result
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/format/hal.rb
================================================
module Yaks
  class Format
    # Hypertext Application Language (http://stateless.co/hal_specification.html)
    #
    # A lightweight JSON Hypermedia message format.
    #
    # Options: +:plural_links+ In HAL, a single rel can correspond to
    # a single link, or to a list of links. Which rels are singular
    # and which are plural is application-dependant. Yaks assumes all
    # links are singular. If your resource might contain multiple
    # links for the same rel, then configure that rel to be plural. In
    # that case it will always be rendered as a collection, even when
    # the resource only contains a single link.
    #
    # @example
    #
    #   yaks = Yaks.new do
    #     format_options :hal, {plural_links: [:related_content]}
    #   end
    #
    class Hal < self
      register :hal, :json, 'application/hal+json'

      def transitive?
        true
      end

      def inverse
        Yaks::Reader::Hal.new
      end

      protected

      # @param [Yaks::Resource] resource
      # @return [Hash]
      def serialize_resource(resource)
        # The HAL spec doesn't say explicitly how to deal missing values,
        # looking at client behavior (Hyperagent) it seems safer to return an empty
        # resource.
        #
        result = resource.attributes

        if resource.links.any?
          result = result.merge(_links: serialize_links(resource.links))
        end

        if resource.collection?
          result = result.merge(_embedded:
                                  serialize_embedded([resource]))
        elsif resource.subresources.any?
          result = result.merge(_embedded:
                                  serialize_embedded(resource.subresources))
        end

        result
      end

      # @param [Array] links
      # @return [Hash]
      def serialize_links(links)
        links.reduce({}, &method(:serialize_link))
      end

      # @param [Hash] memo
      # @param [Yaks::Resource::Link]
      # @return [Hash]
      def serialize_link(memo, link)
        hal_link = {href: link.uri}
        hal_link.merge!(link.options)

        memo[link.rel] = if singular?(link.rel)
                           hal_link
                         else
                           (memo[link.rel] || []) + [hal_link]
                         end
        memo
      end

      # @param [String] rel
      # @return [Boolean]
      def singular?(rel)
        !options.fetch(:plural_links) { [] }.include?(rel)
      end

      # @param [Array] subresources
      # @return [Hash]
      def serialize_embedded(subresources)
        subresources.each_with_object({}) do |sub, memo|
          memo[sub.rels.first] = if sub.collection?
                                   sub.map(&method(:serialize_resource))
                                 elsif sub.null_resource?
                                   nil
                                 else
                                   serialize_resource(sub)
                                 end
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/format/halo.rb
================================================
module Yaks
  class Format
    # Extension of Hal loosely based on the example by Mike Kelly given at
    # https://gist.github.com/mikekelly/893552
    class Halo < Hal
      register :halo, :json, 'application/halo+json'

      def serialize_resource(resource)
        if resource.forms.any?
          super.merge(_controls: serialize_forms(resource.forms))
        else
          super
        end
      end

      def serialize_forms(forms)
        forms.each_with_object({}) do |form, result|
          result[form.name] = serialize_form(form)
        end
      end

      def serialize_form(form)
        raw = form.to_h_compact
        raw[:href] = raw.delete(:action) if raw[:action]
        raw[:fields] = form.fields.map(&method(:serialize_form_field))
        raw
      end

      def serialize_form_field(field)
        if field.type == :fieldset
          {
            type: :fieldset,
            fields: field.fields.map(&method(:serialize_form_field))
          }
        else
          field.to_h_compact.each_with_object({}) do |(attr, value), hsh|
            if attr == :options # <option>s of a <select>
              hsh[:options] = value.map(&:to_h_compact) unless value.empty?
            else
              hsh[attr] = value
            end
          end
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/format/json_api.rb
================================================
module Yaks
  class Format
    class JsonAPI < self
      register :json_api, :json, 'application/vnd.api+json'

      include FP

      # @param [Yaks::Resource] resource
      # @return [Hash]
      def call(resource, _env = nil)
        output = {}
        if resource.collection?
          output[:data]  = resource.map(&method(:serialize_resource))
          output[:links] = serialize_links(resource.links) if resource.links.any?
        else
          output[:data] = serialize_resource(resource)
        end
        included = resource.seq.each_with_object([]) do |res, array|
          serialize_included_subresources(res.subresources, array)
        end
        output[:included] = included if included.any?
        output[:meta] = resource[:meta] if resource[:meta]

        output
      end

      # @param [Yaks::Resource] resource
      # @return [Hash]
      def serialize_links(links)
        links.each_with_object({}) do |link, hash|
          hash[link.rel] = link.uri
        end
      end

      # @param [Yaks::Resource] resource
      # @return [Hash]
      def serialize_resource(resource)
        result = {}
        result[:type] = pluralize(resource.type)
        result[:id]   = resource[:id].to_s if resource[:id]

        attributes = resource.attributes.reject { |k| k.equal?(:id) }
        result[:attributes] = attributes if attributes.any?

        relationships = serialize_relationships(resource.subresources)
        result[:relationships] = relationships unless relationships.empty?
        links = serialize_links(resource.links)
        result[:links] = links unless links.empty?

        result
      end

      # @param [Array] subresources
      # @return [Hash]
      def serialize_relationships(subresources)
        subresources.each_with_object({}) do |resource, hsh|
          hsh[resource.rels.first.sub(/^rel:/, '').to_sym] = serialize_relationship(resource)
        end
      end

      # @param [Yaks::Resource] resource
      # @return [Array, Hash]
      def serialize_relationship(resource)
        if resource.collection?
          data = resource.map { |r| {type: pluralize(r.type), id: r[:id].to_s} }
        elsif !resource.null_resource?
          data = {type: pluralize(resource.type), id: resource[:id].to_s}
        end
        {data: data}
      end

      # @param [Hash] subresources
      # @param [Array] array
      # @return [Array]
      def serialize_included_subresources(subresources, array)
        subresources.each do |resources|
          serialize_included_resources(resources, array)
        end
      end

      # @param [Array] resources
      # @param [Array] included
      # @return [Array]
      def serialize_included_resources(subresource, included)
        subresource.seq.each_with_object(included) do |resource, memo|
          serialize_subresource(resource, memo)
        end
      end

      # {shows => [{id: '3', name: 'foo'}]}
      #
      # @param [Yaks::Resource] resource
      # @param [Hash] included
      # @return [Hash]
      def serialize_subresource(resource, included)
        included << serialize_resource(resource) unless included.any? do |item|
          item[:id].eql?(resource[:id].to_s) && item[:type].eql?(pluralize(resource.type))
        end
        serialize_included_subresources(resource.subresources, included)
      end

      def inverse
        Yaks::Reader::JsonAPI.new
      end
    end

    class Reader
    end
  end
end


================================================
FILE: yaks/lib/yaks/format.rb
================================================
module Yaks
  class Format
    extend Forwardable
    include Util,
            FP::Callable

    # @!attribute [r] options
    #   @return [Hash]
    attr_reader :options

    attr_reader :env

    def_delegators :resource, :links, :attributes, :subresources

    protected :links, :attributes, :subresources, :options

    # @param [Hash] options
    # @return [Hash]
    def initialize(options = {})
      @options = options
    end

    # @param [Yaks::Resource] resource
    # @return [Hash]
    def call(resource, env = {})
      @env = env
      serialize_resource(resource)
    end
    alias_method :serialize, :call

    # @abstract
    def serialize_resource(_resource)
    end

    class << self
      extend Util::Deprecated

      attr_reader :format_name, :serializer, :media_type

      deprecated_alias :mime_type, :media_type

      def all
        @formats ||= []
      end

      # @param [Constant] klass
      # @param [Symbol] format_name
      # @param [String] media_type
      # @return [Array]
      def register(format_name, serializer, media_type)
        @format_name = format_name
        @serializer = serializer
        @media_type = media_type

        Format.all << self
      end

      # @param [Symbol] format_name
      # @return [Constant]
      # @raise [KeyError]
      def by_name(format_name)
        find(:format_name, format_name)
      end

      # @param [Symbol] media_type
      # @return [Constant]
      # @raise [KeyError]
      def by_media_type(media_type)
        find(:media_type, media_type)
      end
      deprecated_alias :by_mime_type, :by_media_type

      def by_accept_header(accept_header)
        media_type = Rack::Accept::Charset.new(accept_header).best_of(media_types.values)
        if media_type
          by_media_type(media_type)
        else
          yield if block_given?
        end
      end

      def media_types
        Format.all.each_with_object({}) do |format, memo|
          memo[format.format_name] = format.media_type
        end
      end
      deprecated_alias :mime_types, :media_types

      def names
        media_types.keys
      end

      private

      def find(key, cond)
        Format.all.detect {|format| format.send(key) == cond }
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/fp/callable.rb
================================================
module Yaks
  module FP
    module Callable
      def to_proc
        method(:call).to_proc
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/html5_forms.rb
================================================
module Yaks
  # Based on the HTML living standard over at WHATWG
  # https://html.spec.whatwg.org/multipage/forms.html
  #
  # Does not aim to be complete, does aim to be a strict subset.
  module HTML5Forms
    INPUT_TYPES = [
      :checkbox,
      :color,
      :date,
      :datetime,
      :datetime_local, # :datetime-local in the spec
      :email,
      :file,
      :hidden,
      :image,
      :month,
      :number,
      :password,
      :radio,
      :range,
      :reset,
      :search,
      :tel,
      :text,
      :time,
      :url,
      :week,

      :select,
      :textarea,
      :datalist,
      :legend
    ]

    FIELD_OPTIONS = {
      type: nil,
      required: false,
      rows: nil,
      value: nil,
      pattern: nil,
      maxlength: nil,
      minlength: 0,
      size: 20,
      readonly: false,
      multiple: false,
      min: nil,
      max: nil,
      step: nil,
      list: nil,
      placeholder: nil,
      checked: false,
      disabled: false
    }
  end
end


================================================
FILE: yaks/lib/yaks/mapper/association.rb
================================================
module Yaks
  class Mapper
    class Association
      include Attribs.new(
                name:        Undefined,
                item_mapper: Undefined,
                rel:         Undefined,
                href:        Undefined,
                link_if:     Undefined,
                if:          Undefined
              ),
              Util,
              AbstractType

      def self.create(name, options = {})
        if options.key?(:mapper)
          options = options.dup
          mapper  = options.delete(:mapper)
          options[:item_mapper] = mapper
        end
        options[:name] = name
        new(options)
      end

      def add_to_resource(resource, parent_mapper, context)
        return resource unless parent_mapper.expand_value(self.if)
        AssociationMapper.new(parent_mapper, self, context).call(resource)
      end

      def render_as_link?(parent_mapper)
        href != Undefined && link_if != Undefined && Resolve(link_if, parent_mapper)
      end

      def map_rel(policy)
        return rel unless rel.equal?(Undefined)
        policy.derive_rel_from_association(self)
      end

      # @param object
      # @param context
      abstract_method :map_resource

      # support for HasOne and HasMany
      def resolve_association_mapper(policy)
        return item_mapper unless item_mapper.equal?(Undefined)
        policy.derive_mapper_from_association(self)
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper/association_mapper.rb
================================================
module Yaks
  class Mapper
    class AssociationMapper
      attr_reader :parent_mapper, :context, :rel, :association

      def initialize(parent_mapper, association, context)
        @parent_mapper = parent_mapper
        @association   = association
        @context       = context.merge(
          mapper_stack: context[:mapper_stack] + [parent_mapper]
        )
        @rel           = association.map_rel(policy) # rubocop:disable Style/ExtraSpacing
      end

      def policy
        context.fetch(:policy)
      end

      def call(resource)
        if association.render_as_link?(parent_mapper)
          add_link(resource)
        else
          add_subresource(resource)
        end
      end

      private

      def add_link(resource)
        Link.create(rel, association.href)
          .add_to_resource(resource, parent_mapper, nil)
        # Yaks::Mapper::Link doesn't do anything with the context, making it
        # hard to test that we pass it a context. Passing nil for now, until
        # this is actually needed and can be tested.
      end

      def add_subresource(resource)
        object      = parent_mapper.load_association(association.name)
        subresource = association.map_resource(object, context).add_rel(rel)
        resource.add_subresource(subresource)
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper/attribute.rb
================================================
module Yaks
  class Mapper
    class Attribute
      extend Forwardable, Util
      include Attribs.new(:name, :block, if: true), Util

      def self.create(name, options = {}, &block)
        new(options.merge(name: name, block: block))
      end

      def add_to_resource(resource, mapper, _context)
        return resource unless Resolve(self.if, mapper)

        if block
          attribute = Resolve(block, mapper)
        else
          attribute = mapper.load_attribute(name)
        end

        resource.merge_attributes(name => attribute)
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper/config.rb
================================================
module Yaks
  class Mapper
    class Config
      include Attribs.new(
                type: nil, attributes: [], links: [], associations: [], forms: []
              )

      def add_attributes(*attrs)
        append_to(:attributes, *attrs.map(&Attribute.method(:create)))
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper/form/config.rb
================================================
module Yaks
  class Mapper
    class Form
      class Config
        include Attribs.new(
                  name: nil,
                  action: nil,
                  title: nil,
                  method: nil,
                  media_type: nil,
                  fields: [].freeze,
                  if: nil
                )

        Builder = Yaks::Builder.new(self) do
          def_set :action, :title, :method, :media_type
          def_add :field, create: Field::Builder, append_to: :fields
          def_add :fieldset, create: Fieldset, append_to: :fields
          HTML5Forms::INPUT_TYPES.each do |type|
            def_add(type,
                    create: Field::Builder,
                    append_to: :fields,
                    defaults: {type: type})
          end
          def_add :legend, create: Legend, append_to: :fields
          def_add :dynamic, create: DynamicField, append_to: :fields
          def_forward :condition
        end

        # Builder expects a `create' method. Alias to constructor
        def self.create(options)
          new(options)
        end

        # Build up a configuration based on an initial set of
        # attributes, and a configuration block
        def self.build(options = {}, &block)
          Builder.create(options, &block)
        end

        # Build up a configuration based on a config block. Provide an
        # object to be supplied to the block
        def self.build_with_object(object, &block)
          Builder.build(new, object, &block)
        end

        def condition(prc = nil, &blk)
          with(if: prc || blk)
        end

        def to_resource_fields(mapper)
          fields.flat_map do |field|
            field.to_resource_fields(mapper)
          end
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper/form/dynamic_field.rb
================================================
module Yaks
  class Mapper
    class Form
      class DynamicField
        include Attribs.new(:block)

        def self.create(_opts = nil, &block)
          new(block: block)
        end

        def to_resource_fields(mapper)
          Config.build_with_object(mapper.object, &block).to_resource_fields(mapper)
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper/form/field/option.rb
================================================
module Yaks
  class Mapper
    class Form
      class Field
        # <option>, as used in a <select>
        class Option
          include Attribs.new(:value, :label, selected: false, disabled: false, if: nil)

          def self.create(value, opts)
            new(opts.merge(value: value))
          end

          def to_resource_field_option(mapper)
            return unless self.if.nil? || mapper.expand_value(self.if)

            Resource::Form::Field::Option.new(
              value: mapper.expand_value(value),
              label: mapper.expand_value(label),
              selected: mapper.expand_value(selected),
              disabled: mapper.expand_value(disabled)
            )
          end
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper/form/field.rb
================================================
module Yaks
  class Mapper
    class Form
      class Field
        include Attribs.new(
                  :name,
                  label: nil,
                  options: [].freeze,
                  if: nil
                ).add(HTML5Forms::FIELD_OPTIONS)

        Builder = Yaks::Builder.new(self) do
          def_set :name, :label
          def_add :option, create: Option, append_to: :options

          def condition(blk1 = nil, &blk2)
            @config = @config.with(if: blk1 || blk2)
          end

          HTML5Forms::FIELD_OPTIONS.each do |option, _|
            def_set option
          end
        end

        def self.create(*args)
          attrs = args.last.instance_of?(Hash) ? args.pop : {}
          if name = args.shift
            attrs = attrs.merge(name: name)
          end
          new(attrs)
        end

        # Convert to a Resource::Form::Field, expanding any dynamic
        # values
        def to_resource_fields(mapper)
          return [] unless self.if.nil? || mapper.expand_value(self.if)
          [ Resource::Form::Field.new(
              resource_attributes.each_with_object({}) do |attr, attrs|
                attrs[attr] = mapper.expand_value(public_send(attr))
              end.merge(options: resource_options(mapper))) ]
        end

        def resource_options(mapper)
          # make sure all empty options arrays are the same instance,
          # makes for prettier #pp
          if options.empty?
            options
          else
            options.map {|opt| opt.to_resource_field_option(mapper) }.compact
          end
        end

        # All attributes that can be converted 1-to-1 to
        # Resource::Form::Field
        def resource_attributes
          self.class.attributes.names - [:options, :if]
        end
      end # Field
    end # Form
  end # Mapper
end # Yaks


================================================
FILE: yaks/lib/yaks/mapper/form/fieldset.rb
================================================
module Yaks
  class Mapper
    class Form
      class Fieldset
        extend Forwardable
        include Attribs.new(:config)

        def_delegators :config, :fields

        def self.create(options = {}, &block)
          new(config: Config.build(options, &block))
        end

        def to_resource_fields(mapper)
          return [] if config.if && !mapper.expand_value(config.if)
          [ Resource::Form::Fieldset.new(fields: config.to_resource_fields(mapper)) ]
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper/form/legend.rb
================================================
module Yaks
  class Mapper
    class Form
      class Legend
        include Attribs.new(:type, :label, if: nil)

        def self.create(label, opts = {})
          new(opts.merge(type: :legend, label: label))
        end

        def to_resource_fields(mapper)
          return [] unless self.if.nil? || mapper.expand_value(self.if)
          [ Resource::Form::Legend.new(label: mapper.expand_value(label)) ]
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper/form.rb
================================================
module Yaks
  class Mapper
    class Form
      extend Forwardable, Util

      def_delegators :config, :name, :action, :title, :method,
                              :media_type, :fields, :dynamic_blocks

      def self.create(*args, &block)
        args, options = extract_options(args)
        options[:name] = args.first if args.first
        new(config: Config.build(options, &block))
      end

      ############################################################
      # instance

      include Attribs.new(:config)

      def add_to_resource(resource, mapper, _context)
        return resource if config.if && !mapper.expand_value(config.if)
        resource.add_form(to_resource_form(mapper))
      end

      def to_resource_form(mapper)
        attrs = {
          fields: config.to_resource_fields(mapper),
          action: mapper.expand_uri(action)
        }

        [:name, :title, :method, :media_type].each do |attr|
          attrs[attr] = mapper.expand_value(public_send(attr))
        end

        Resource::Form.new(attrs)
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper/has_many.rb
================================================
module Yaks
  class Mapper
    class HasMany < Association
      include Util,
              attributes.add(collection_mapper: Undefined)

      def map_resource(collection, context)
        return NullResource.new(collection: true) if collection.nil?
        policy      = context.fetch(:policy)
        item_mapper = resolve_association_mapper(policy)
        context     = context.merge(item_mapper: item_mapper)
        collection_mapper(collection, policy).new(context).call(collection)
      end

      undef collection_mapper
      def collection_mapper(collection = nil, policy = nil)
        return @collection_mapper unless @collection_mapper.equal? Undefined
        policy.derive_mapper_from_object(collection) if policy && collection
      end

      def singular_name
        singularize(name.to_s)
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper/has_one.rb
================================================
module Yaks
  class Mapper
    class HasOne < Association
      def map_resource(object, context)
        resolve_association_mapper(context.fetch(:policy))
          .new(context)
          .call(object)
      end

      def singular_name
        name.to_s
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper/link.rb
================================================
module Yaks
  class Mapper
    # A Yaks::Mapper::Link is part of a mapper's configuration. It captures
    # what is set through the mapper's class level `#link` function, and is
    # capable of generating a `Yaks::Resource::Link` for a given mapper
    # instance (and hence subject).
    #
    # @example
    #   link :self, 'http://api.foo.org/users/{id}', title: ->{ "User #{object.name}" }
    #   link :profile, 'http://apidocs.foo.org/profiles/users'
    #   link 'http://apidocs.foo.org/rels/friends', 'http://api.foo.org/users/{id}/friends?page={page}', expand: [:id]
    #
    # It takes a relationship identifier, a URI template and an options hash.
    #
    # @param rel [Symbol|String] Either a registered relationship type (Symbol)
    #   or a relationship URI. See [RFC5988 Web Linking](http://tools.ietf.org/html/rfc5988)
    # @param template [String] A [RFC6570](http://tools.ietf.org/html/rfc6570) URI template
    # @param template [Symbol] A method name that generates the link. No more expansion is done afterwards
    # @option expand [Boolean] pass false to pass on the URI template in the response,
    #   instead of expanding the variables
    # @option expand [Array[Symbol]] pass a list of variable names to only expand those,
    #   and return a partially expanded URI template in the response
    # @option title [String] Give the link a title
    # @option title [#to_proc] Block that returns the title. If it takes an argument,
    #   it will receive the mapper instance as argument. Otherwise it is evaluated in the mapper context
    class Link
      extend Forwardable, Util
      include Attribs.new(:rel, :template, options: {}.freeze), Util

      def self.create(*args)
        args, options = extract_options(args)
        new(rel: args.first, template: args.last, options: options)
      end

      def add_to_resource(resource, mapper, _context)
        if options[:remove]
          return resource.with(links: resource.links.reject {|link| link.rel?(rel)})
        end

        resource_link = map_to_resource_link(mapper)
        return resource unless resource_link

        if options[:replace]
          resource.with(links: resource.links.reject {|link| link.rel?(rel)} << resource_link)
        else
          resource.add_link(resource_link)
        end
      end

      def rel?(rel)
        rel().eql? rel
      end

      # A link is templated if it does not expand, or only partially
      def templated?
        !options.fetch(:expand) { true }.equal? true
      end

      def map_to_resource_link(mapper)
        return unless mapper.expand_value(options.fetch(:if, true))

        uri = mapper.expand_uri(template, options.fetch(:expand, true))
        return if uri.nil?

        attrs = {
          rel: rel,
          uri: uri
        }

        resource_link_options(mapper).tap do |opts|
          attrs[:options] = opts unless opts.empty?
        end

        Resource::Link.new(attrs)
      end

      private

      def resource_link_options(mapper)
        options = options()
        options = options.merge(title: Resolve(options[:title], mapper)) if options.key?(:title)
        options = options.merge(templated: true) if templated?
        options.reject{|key| [:expand, :replace, :if].include? key }
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/mapper.rb
================================================
module Yaks
  class Mapper
    extend Configurable

    def_set :type

    def_forward attributes: :add_attributes
    def_forward :append_to

    def_add :link,      create: Link,      append_to: :links
    def_add :has_one,   create: HasOne,    append_to: :associations
    def_add :has_many,  create: HasMany,   append_to: :associations
    def_add :attribute, create: Attribute, append_to: :attributes
    def_add :form,      create: Form,      append_to: :forms

    extend Forwardable
    include Util, FP, FP::Callable

    attr_reader :object, :context

    def_delegators 'self.class', :config
    def_delegators :config, :attributes, :links, :associations, :forms

    def initialize(context)
      @context = context
    end

    def policy
      context.fetch(:policy)
    end

    def env
      context.fetch(:env)
    end

    def mapper_stack
      context.fetch(:mapper_stack)
    end

    def self.mapper_name(policy)
      config.type || policy.derive_type_from_mapper_class(self)
    end

    def mapper_name
      self.class.mapper_name(policy)
    end

    def call(object, _env = nil)
      @object = object

      return NullResource.new if object.nil?

      [ :map_attributes,
        :map_links,
        :map_subresources,
        :map_forms
      ].inject(Resource.new(type: mapper_name)) do |resource, method|
        __send__(method, resource)
      end
    end

    def load_attribute(name)
      respond_to?(name) ? public_send(name) : object.public_send(name)
    end
    alias_method :load_association, :load_attribute

    def expand_value(value)
      Resolve(value, self)
    end

    def expand_uri(uri, expand = true)
      return if uri.nil?
      return Resolve(uri, self) if uri.respond_to?(:to_proc)

      template = URITemplate.new(uri)
      expand_vars = case expand
                    when true
                      template.variables
                    when false
                      []
                    else
                      expand
                    end

      mapping = expand_vars.each_with_object({}) do |name, hsh|
        hsh[name] = load_attribute(name)
      end

      template.expand_partial(mapping).to_s
    end

    private

    def map_attributes(resource)
      attributes.inject(resource) do |res, attribute|
        attribute.add_to_resource(res, self, context)
      end
    end

    def map_links(resource)
      links.inject(resource) do |res, mapper_link|
        mapper_link.add_to_resource(res, self, context)
      end
    end

    def map_subresources(resource)
      associations.inject(resource) do |res, association|
        association.add_to_resource(res, self, context)
      end
    end

    def map_forms(resource)
      forms.inject(resource) do |res, form|
        form.add_to_resource(res, self, context)
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/null_resource.rb
================================================
module Yaks
  class NullResource < Resource
    include attributes.add(collection: false),
            Equalizer.new(:rels, :collection)

    def initialize(opts = {})
      local_opts = {}
      local_opts[:rels]       = opts[:rels]       if opts.key?(:rels)
      local_opts[:collection] = opts[:collection] if opts.key?(:collection)
      super(local_opts)
    end

    def each
      to_enum
    end

    def collection? # rubocop:disable Style/TrivialAccessors
      @collection
    end

    def null_resource?
      true
    end

    def seq
      []
    end

    def map
      return [] if collection?
      raise UnsupportedOperationError, "Operation #{__method__} not supported on #{self.class}"
    end

    def merge_attributes(_new_attrs)
      raise UnsupportedOperationError, "Operation #{__method__} not supported on #{self.class}"
    end

    def add_link(_link)
      raise UnsupportedOperationError, "Operation #{__method__} not supported on #{self.class}"
    end

    def add_form(_form)
      raise UnsupportedOperationError, "Operation #{__method__} not supported on #{self.class}"
    end

    def add_subresource(_subresource)
      raise UnsupportedOperationError, "Operation #{__method__} not supported on #{self.class}"
    end
  end
end


================================================
FILE: yaks/lib/yaks/pipeline.rb
================================================
module Yaks
  class Pipeline
    include Concord.new(:steps)

    def call(input, env)
      steps.inject(input) {|memo, (_, step)| step.call(memo, env) }
    end

    def insert_hooks(hooks)
      new_steps = hooks.inject(steps) do |steps, (type, target_step, name, hook)|
        steps.flat_map do |step_name, callable|
          if step_name.equal? target_step
            case type
            when :before
              [[name, hook], [step_name, callable]]
            when :after
              [[step_name, callable], [name, hook]]
            when :around
              [[name, ->(x, env) { hook.call(x, env, &callable) }]]
            when :skip
              []
            end
          end || [[step_name, callable]]
        end
      end

      self.class.new(new_steps)
    end

    def transitive?
      steps.all? {|_name, step| step.respond_to?(:transitive?) && step.transitive?}
    end

    def inverse
      unless transitive?
        raise "Unable to get inverse pipeline, not all pipeline steps are transitive."
      end

      self.class.new(steps.map {|name, step| [name, step.inverse]}.reverse)
    end
  end
end


================================================
FILE: yaks/lib/yaks/primitivize.rb
================================================
module Yaks
  class Primitivize
    attr_reader :mappings

    def initialize
      @mappings = {}
    end

    def call(object)
      mappings.each do |pattern, block|
        # rubocop:disable Style/CaseEquality
        return instance_exec(object, &block) if pattern === object
      end
      raise PrimitivizeError, "don't know how to turn #{object.class} (#{object.inspect}) into a primitive"
    end

    def map(*types, &block)
      types.each do |type|
        @mappings = mappings.merge(type => block)
      end
    end

    def self.create
      new.tap do |p|
        p.map String, Numeric, true, false, nil do |object|
          object
        end

        p.map Symbol, URI do |object|
          object.to_s
        end

        p.map Hash do |object|
          object.to_enum.with_object({}) do |(key, value), output|
            output[call(key)] = call(value)
          end
        end

        p.map Enumerable do |object|
          object.map(&method(:call))
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/reader/hal.rb
================================================
module Yaks
  module Reader
    class Hal
      include Util

      def call(parsed_json, _env = {})
        attributes = parsed_json.dup
        links      = convert_links(attributes.delete('_links') || {})
        embedded   = convert_embedded(attributes.delete('_embedded') || {})

        Resource.new(
          type: attributes.delete('type') || type_from_links(links),
          attributes: Util.symbolize_keys(attributes),
          links: links,
          subresources: embedded
        )
      end

      def type_from_links(links)
        profile = links.detect {|l| l.rel?(:profile)}
        profile.uri[/\w+$/] if profile
      end

      def convert_links(links)
        links.flat_map do |rel, link|
          array(link).map do |l|
            options = symbolize_keys(slice_hash(l, 'title', 'templated'))
            # if it looks like a keyword we'll assume it's a registered rel type
            rel = rel.to_sym if rel =~ /\A\w+\z/
            Resource::Link.new(rel: rel, uri: l['href'], options: options)
          end
        end
      end

      def array(x)
        x.instance_of?(Array) ? x : [x]
      end

      def convert_embedded(embedded)
        embedded.flat_map do |rel, resource|
          case resource
          when nil
            NullResource.new
          when Array
            if resource.empty?
              NullResource.new(collection: true)
            else
              CollectionResource.new(
                members: resource.map { |r|
                  call(r).with(type: Util.singularize(rel[/\w+$/]))
                }
              )
            end
          else
            call(resource)
          end.with(rels: [rel])
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/reader/json_api.rb
================================================
module Yaks
  module Reader
    class JsonAPI
      def call(parsed_json, _env = {})
        included = parsed_json['included'].nil? ? {} : parsed_json['included'].dup

        if parsed_json['data'].is_a?(Array)
          CollectionResource.new(
            attributes: parsed_json['meta'].nil? ? nil : {meta: parsed_json['meta']},
            members: parsed_json['data'].map { |data| call('data' => data, 'included' => included) }
          )
        else
          attributes = parsed_json['data'].dup
          links = attributes.delete('links') || {}
          relationships = attributes.delete('relationships') || {}
          type = attributes.delete('type')
          attributes.merge!(attributes.delete('attributes') || {})

          embedded   = convert_embedded(Hash[relationships], included)
          links      = convert_links(Hash[links])

          Resource.new(
            type: Util.singularize(type),
            attributes: Util.symbolize_keys(attributes),
            subresources: embedded,
            links: links
          )
        end
      end

      def convert_embedded(relationships, included)
        relationships.flat_map do |rel, relationship|
          # A Link doesn't have to contain a `data` member.
          # It can contain URLs instead, or as well, but we are only worried about *embedded* links here.
          data = relationship['data']
          # Resource data MUST be represented as one of the following:
          #
          # * `null` for empty to-one relationships.
          # * a "resource identifier object" for non-empty to-one relationships.
          # * an empty array ([]) for empty to-many relationships.
          # * an array of resource identifier objects for non-empty to-many relationships.
          if data.nil?
            NullResource.new(rels: [rel])
          elsif data.is_a? Array
            if data.empty?
              NullResource.new(collection: true, rels: [rel])
            else
              CollectionResource.new(
                members: data.map { |link|
                  data = included.find{ |item| (item['id'] == link['id']) && (item['type'] == link['type']) }
                  call('data' => data, 'included' => included)
                },
                rels: [rel]
              )
            end
          else
            data = included.find{ |item| (item['id'] == data['id']) && (item['type'] == data['type']) }
            call('data' => data, 'included' => included).with(rels: [rel])
          end
        end.compact
      end

      def convert_links(links)
        links.map do |rel, link|
          Resource::Link.new(rel: rel.to_sym, uri: link)
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/resource/form/field/option.rb
================================================
module Yaks
  class Resource
    class Form
      class Field
        class Option
          include Attribs.new(:value, :label, selected: false, disabled: false)
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/resource/form/field.rb
================================================
module Yaks
  class Resource
    class Form
      class Field
        include Yaks::Mapper::Form::Field.attributes.add(error: nil)

        undef value
        def value
          if type.equal? :select
            selected = options.find(&:selected)
            selected.value if selected
          else
            @value
          end
        end

        def with_value(value)
          if type.equal? :select
            with(options: select_options_for_value(value))
          else
            with(value: value)
          end
        end

        private

        def select_options_for_value(value)
          unset = ->(option) { option.selected && !value().eql?(value) }
          set   = ->(option) { !option.selected && option.value.eql?(value) }

          options.each_with_object([]) do |option, new_opts|
            new_opts << case option
                        when unset
                          option.with selected: false
                        when set
                          option.with selected: true
                        else
                          option
                        end
          end
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/resource/form/fieldset.rb
================================================
module Yaks
  class Resource
    class Form
      class Fieldset
        include Attribs.new(:fields)
        include Yaks::Resource::HasFields

        def type
          :fieldset
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/resource/form/legend.rb
================================================
module Yaks
  class Resource
    class Form
      class Legend
        include Attribs.new(:label, :type)

        def initialize(opts)
          super(opts.merge(type: :legend))
        end

        # Up to 0.9.0 legends were represented as Form::Field
        # instances with the label stored as name, hence this alias
        # for compatibility
        alias_method :name, :label
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/resource/form.rb
================================================
module Yaks
  class Resource
    class Form
      include Yaks::Mapper::Form::Config.attributes.remove(:dynamic_blocks)
      include Yaks::Resource::HasFields

      def [](name)
        fields.find {|field| field.name.equal? name}.value
      end

      def values
        fields_flat.each_with_object({}) do |field, values|
          values[field.name] = field.value
        end
      end

      def method?(meth)
        !method.nil? && method.downcase.to_sym == meth.downcase.to_sym
      end

      def has_action? # rubocop:disable Style/PredicateName
        !action.nil?
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/resource/has_fields.rb
================================================
module Yaks
  class Resource
    module HasFields
      def map_fields(&block)
        with(
          fields: fields_flat(&block)
        )
      end

      def fields_flat(&block)
        return to_enum(__method__) unless block_given?
        fields.map do |field|
          next field if field.type.equal? :legend
          if field.respond_to?(:map_fields)
            field.map_fields(&block)
          else
            block.call(field)
          end
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/resource/link.rb
================================================
module Yaks
  class Resource
    class Link
      include Attribs.new(:rel, :uri, options: {}.freeze)

      def title
        options[:title]
      end

      def templated?
        options.fetch(:templated) { false }
      end

      def rel?(rel)
        rel().eql? rel
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/resource.rb
================================================
module Yaks
  class Resource
    include Attribs.new(
              type: nil,
              rels: [],
              links: [],
              attributes: {},
              subresources: [],
              forms: []
            )
    extend Util::Deprecated

    def initialize(attrs = {})
      raise attrs.inspect if attrs.key?(:subresources) && !attrs[:subresources].instance_of?(Array)
      super
    end

    def [](attr)
      attributes[attr]
    end

    def find_form(name)
      forms.find { |form| form.name.equal? name }
    end

    def seq
      [self]
    end

    def self_link
      # This reverse is there so that the last :self link specified
      # "wins". The use case is having a self link defined in a base
      # mapper class, but having it overridden in specific
      # subclasses. In combination with formats that expect resources
      # to have up to one self link, this is the preferred behavior.
      # However since 0.7.5 links take a "replace: true" option to
      # specifiy they should replace previous defintions with the same
      # rel, wich should be used instead. The behavior that the last
      # link "wins" will be deprecated, the result of multiple links
      # with the same rel will be unspecified.
      links.reverse.find do |link|
        link.rel.equal? :self
      end
    end

    def collection?
      false
    end
    alias_method :collection, :collection?

    def with_collection(*)
      self
    end

    def null_resource?
      false
    end

    def members
      raise UnsupportedOperationError, "Only Yaks::CollectionResource has members"
    end
    alias_method :each, :members
    alias_method :map, :members
    alias_method :each_with_object, :members
    alias_method :with_members, :members

    def merge_attributes(new_attrs)
      with(attributes: @attributes.merge(new_attrs))
    end
    deprecated_alias :update_attributes, :merge_attributes

    def add_rel(rel)
      append_to(:rels, rel)
    end

    def add_link(link)
      append_to(:links, link)
    end

    def add_form(form)
      append_to(:forms, form)
    end

    def add_subresource(subresource)
      append_to(:subresources, subresource)
    end
  end
end


================================================
FILE: yaks/lib/yaks/runner.rb
================================================
module Yaks
  class Runner
    include Util
    include Anima.new(:object, :config, :options)
    include Adamantium::Flat
    extend Forwardable

    def_delegators :config,        :policy, :default_format, :format_options_hash,
                                   :primitivize, :serializers
    def_delegators :format_class,  :media_type, :format_name

    def call
      Pipeline.new(steps).insert_hooks(hooks).call(object, env)
    end

    def read
      Pipeline.new([[:parse, serializer.inverse], [:read, formatter.inverse]]).insert_hooks(hooks).call(object, env)
    end

    def format
      Pipeline.new([[:format, formatter], [:primitivize, primitivizer]]).insert_hooks(hooks).call(object, env)
    end

    def map
      Pipeline.new([[:map, mapper]]).insert_hooks(hooks).call(object, env)
    end

    def context
      {
        policy: policy,
        env: env,
        mapper_stack: []
      }.merge(slice_hash(options, :item_mapper))
    end
    memoize :context, freezer: :flat

    def env
      options.fetch(:env, {})
    end
    memoize :env, freezer: :noop

    # @return [Class]
    def format_class
      Format.by_accept_header(env['HTTP_ACCEPT']) {
        Format.by_name(options.fetch(:format) { default_format })
      }
    end
    memoize :format_class, freezer: :noop

    def steps
      [[ :map, mapper ],
       [ :format, formatter ],
       [ :primitivize, primitivizer],
       [ :serialize, serializer ]]
    end
    memoize :steps

    def mapper
      options.fetch(:mapper) do
        policy.derive_mapper_from_object(object)
      end.new(context)
    end
    memoize :mapper, freezer: :noop

    def formatter
      format_class.new(format_options_hash[format_name])
    end
    memoize :formatter, freezer: :noop

    def primitivizer
      proc do |input|
        if format_class.serializer.equal? :json
          primitivize.call(input)
        else
          input
        end
      end
    end
    memoize :primitivizer

    def serializer
      serializers.fetch(format_class.serializer)
    end
    memoize :serializer, freezer: :noop

    def hooks
      config.hooks + options.fetch(:hooks, [])
    end
  end
end


================================================
FILE: yaks/lib/yaks/serializer.rb
================================================
module Yaks
  module Serializer
    def self.register(format, serializer)
      raise "Serializer for #{format} already registered" if all.key? format
      all[format] = serializer
    end

    def self.all
      @serializers ||= {json: JSONWriter}
    end

    module JSONWriter
      extend Yaks::FP::Callable

      def self.call(data, _env)
        JSON.pretty_generate(data)
      end

      def self.transitive?
        true
      end

      def self.inverse
        JSONReader
      end
    end

    module JSONReader
      extend Yaks::FP::Callable

      def self.call(data, _env)
        JSON.parse(data)
      end

      def self.transitive?
        true
      end

      def self.inverse
        JSONWriter
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/util.rb
================================================
module Yaks
  module Util
    extend self
    extend Forwardable

    def_delegators Inflection, :singular, :singularize, :pluralize

    def underscore(str)
      str.gsub(/::/, '/')
        .gsub(%r{(?<!^|/)([A-Z])(?=[a-z$])|(?<=[a-z])([A-Z])}, '_\1\2')
        .tr("-", "_")
        .downcase
    end

    def camelize(str)
      str.gsub(%r{/(.?)})    { "::#{ $1.upcase }" }
        .gsub!(/(?:^|_)(.)/) { $1.upcase          }
    end

    def slice_hash(hash, *keys)
      keys.each_with_object({}) {|k, dest| dest[k] = hash[k] if hash.key?(k) }
    end

    def reject_keys(hash, *keys)
      hash.keys.each_with_object({}) {|k, dest| dest[k] = hash[k] unless keys.include?(k) }
    end

    def symbolize_keys(hash)
      hash.each_with_object({}) {|(k, v), hsh| hsh[k.to_sym] = v}
    end

    def extract_options(args)
      args.last.instance_of?(Hash) ? [args[0..-2], args.last] : [args, {}]
    end

    # Turn what is maybe a Proc into its result (or itself)
    #
    # When input can be either a value or a proc that returns a value,
    # this conversion function can be used to resolve the thing to a
    # value.
    #
    # The proc can be evaluated (instance_evaled) in a certain context,
    # or evaluated as a closure.
    #
    # @param [Object|Proc] maybe_proc
    #   A proc or a plain value
    # @param [Object] context
    #   (optional) A context used to instance_eval the proc
    def Resolve(maybe_proc, context = nil) # rubocop:disable Style/MethodName
      if maybe_proc.respond_to?(:to_proc) && !maybe_proc.instance_of?(Symbol)
        if context
          if maybe_proc.arity > 0
            context.instance_eval(&maybe_proc)
          else
            # In case it's a lambda with zero arity instance_eval fails
            context.instance_exec(&maybe_proc)
          end
        else
          maybe_proc.to_proc.call()
        end
      else
        maybe_proc
      end
    end

    module Deprecated
      def deprecated_alias(name, actual)
        define_method name do |*args, &block|
          $stderr.puts "WARNING: #{self.class}##{name} is deprecated, use `#{actual}'. at #{caller.first}"
          public_send(actual, *args, &block)
        end
      end
    end
  end
end


================================================
FILE: yaks/lib/yaks/version.rb
================================================
module Yaks
  VERSION = '0.13.0'
end


================================================
FILE: yaks/lib/yaks.rb
================================================
require 'forwardable'
require 'set'
require 'pathname'
require 'json'
require 'csv'

require 'concord'
require 'attribs'
require 'inflection'
require 'uri_template'
require 'rack/accept'

require 'yaks/version'
require 'yaks/util'
require 'yaks/configurable'
require 'yaks/fp/callable'
require 'yaks/primitivize'
require 'yaks/builder'
require 'yaks/errors'

require 'yaks/default_policy'
require 'yaks/serializer'
require 'yaks/config'

module Yaks
  Undefined = Module.new.freeze

  # Set the Root constant as the gems root path
  Root = Pathname(__FILE__).join('../..')

  DSL_METHODS = [
    :format_options,
    :rel_template,
    :mapper_for,
    :before,
    :after,
    :around,
    :skip,
    :namespace,
    :mapper_namespace,
    :serializer,
    :json_serializer,
    :map_to_primitive
  ]

  ConfigBuilder = Builder.new(Yaks::Config) do
    def_set(*Yaks::Config.attributes.names)
    def_forward(*DSL_METHODS)
    def_forward(*Yaks::DefaultPolicy.public_instance_methods(false))
  end

  class << self
    # @param [Proc] blk
    # @return [Yaks::Config]

    def new(&blk)
      ConfigBuilder.create(&blk)
    end
  end
end

require 'yaks/resource'
require 'yaks/null_resource'
require 'yaks/resource/link'
require 'yaks/collection_resource'

require 'yaks/html5_forms'

require 'yaks/mapper/association'
require 'yaks/mapper/has_one'
require 'yaks/mapper/has_many'
require 'yaks/mapper/attribute'
require 'yaks/mapper/link'
require 'yaks/mapper/form/field/option'
require 'yaks/mapper/form/field'
require 'yaks/mapper/form/fieldset'
require 'yaks/mapper/form/legend'
require 'yaks/mapper/form/dynamic_field'
require 'yaks/mapper/form/config'
require 'yaks/mapper/form'
require 'yaks/mapper/config'
require 'yaks/mapper'
require 'yaks/mapper/association_mapper'
require 'yaks/collection_mapper'

require 'yaks/resource/has_fields'
require 'yaks/resource/form'
require 'yaks/resource/form/field'
require 'yaks/resource/form/field/option'
require 'yaks/resource/form/fieldset'
require 'yaks/resource/form/legend'

require 'yaks/format'
require 'yaks/format/hal'
require 'yaks/format/halo'
require 'yaks/format/json_api'
require 'yaks/format/collection_json'

require 'yaks/reader/hal'
require 'yaks/reader/json_api'
require 'yaks/pipeline'
require 'yaks/runner'


================================================
FILE: yaks/spec/acceptance/acceptance_spec.rb
================================================
require 'acceptance/models'
require 'acceptance/json_shared_examples'

RSpec.describe Yaks::Format::Hal do
  let(:format_name) { :hal }

  context 'with a configured rel template' do
    let(:yaks_config) {
      Yaks.new do
        format_options :hal, plural_links: ['http://literature.example.com/rels/quotes']
        rel_template "http://literature.example.com/rel/{rel}"
      end
    }

    include_examples 'JSON Writer',     'confucius'
    include_examples 'JSON round trip', 'confucius'
  end

  context 'with a rel computed by a policy override' do
    let(:yaks_config) {
      Yaks.new do
        format_options :hal, plural_links: ['http://literature.example.com/rels/quotes']
        derive_rel_from_association do |association|
          "http://literature.example.com/rel/#{association.name}"
        end
      end
    }

    include_examples 'JSON Writer',     'confucius'
    include_examples 'JSON round trip', 'confucius'
    include_examples 'JSON Writer',     'list_of_quotes'
    include_examples 'JSON round trip', 'list_of_quotes'
  end
end

RSpec.describe Yaks::Format::Halo do
  let(:format_name) { :halo }
  let(:yaks_config) {
    Yaks.new do
      default_format :halo
      rel_template "http://literature.example.com/rel/{rel}"
    end
  }

  include_examples 'JSON Writer', 'confucius'
end

RSpec.describe Yaks::Format::JsonAPI do
  let(:format_name) { :json_api }
  let(:yaks_config) { Yaks.new }

  include_examples 'JSON Writer', 'confucius'
  # include_examples 'JSON Reader', 'confucius'
  include_examples 'JSON round trip', 'confucius'
  include_examples 'JSON Writer', 'list_of_quotes'
  # include_examples 'JSON round trip', 'list_of_quotes'
end

RSpec.describe Yaks::Format::CollectionJson do
  let(:format_name) { :collection_json }
  let(:yaks_config) { Yaks.new }

  include_examples 'JSON Writer', 'confucius'
  include_examples 'JSON Writer', 'list_of_quotes'

  context 'with a namespace' do
    let(:yaks_config) {
      Yaks.new do
        mapper_namespace Youtypeitwepostit
      end
    }

    include_examples 'JSON Writer', 'youtypeitwepostit'
  end
end


================================================
FILE: yaks/spec/acceptance/json_shared_examples.rb
================================================
RSpec.shared_examples_for 'JSON Writer' do |fixture_name|
  describe 'Yaks::Resource => JSON' do
    let(:object) { load_yaml_fixture(fixture_name) }
    let(:json_fixture) { load_json_fixture("#{fixture_name}.#{format_name}") }
    let(:serialized) {
      yaks_config.call(object, hooks: [[:skip, :serialize]], format: format_name)
    }

    # before do
    #   puts "============================expected========================================="
    #   puts JSON.pretty_generate(expected)
    #   puts "=================yaks.call(object, format: #{format.inspect})================"
    #   puts JSON.pretty_generate(serialized)
    # end

    it 'should match the JSON fixture' do
      expect(serialized).to deep_eql json_fixture
    end
  end
end

RSpec.shared_examples_for 'JSON Reader' do |fixture_name|
  describe 'JSON => Yaks::Resource' do
    let(:object) { load_yaml_fixture(fixture_name) }
    let(:json_fixture) { load_json_fixture("#{fixture_name}.#{format_name}") }
    let(:resource) {
      yaks_config.read(json_fixture, hooks: [[:skip, :parse]], format: format_name)
    }

    it 'should equal the corresponding Yaks::Resource' do
      # Comparing type+to_h to get better RSpec output upon failure
      expect(resource).to be_a Yaks::Resource
      expect(resource.to_h).to eql yaks.map(object).to_h
    end
  end
end

RSpec.shared_examples_for 'JSON round trip' do |fixture_name|
  describe 'JSON => Yaks::Resource => JSON' do
    let(:json_fixture) { load_json_fixture("#{fixture_name}.#{format_name}") }
    let(:read_and_written) {
      config = yaks_config.with(default_format: format_name)
      config.format(
        config.read(json_fixture, hooks: [[:skip, :parse]])
      )
    }

    specify 'it should be identical' do
      expect(read_and_written).to deep_eql json_fixture
    end
  end
end


================================================
FILE: yaks/spec/acceptance/models.rb
================================================
require 'anima'

class Scholar
  include Anima.new(:id, :name, :pinyin, :latinized, :works)
end

class Work
  include Anima.new(:id, :chinese_name, :english_name, :quotes, :era)
end

class Quote
  include Anima.new(:id, :chinese, :english, :sources)
end

class Era
  include Anima.new(:id, :name)
end

class LiteratureBaseMapper < Yaks::Mapper
  link :profile, 'http://literature.example.com/profiles/{mapper_name}', expand: true
  link :self, 'http://literature.example.com/{mapper_name}/{id}'
end

class ScholarMapper < LiteratureBaseMapper
  attributes :id, :name, :pinyin, :latinized
  has_many :works

  link 'http://literature.example.com/rels/quotes', 'http://literature.example.com/quotes/?author={downcased_pinyin}&q={query}', expand: [:downcased_pinyin], title: 'Search for quotes'
  link :self, 'http://literature.example.com/authors/{downcased_pinyin}', replace: true

  form :search do
    title 'Find a Scholar'
    method 'POST'
    media_type 'application/x-www-form-urlencoded'

    field :name,   label: 'Scholar Name', type: 'text'
    field :pinyin, label: 'Hanyu Pinyin', type: 'text'
  end

  def downcased_pinyin
    object.pinyin.downcase
  end
end

class WorkMapper < LiteratureBaseMapper
  attributes :id, :chinese_name, :english_name
  has_many :quotes
  has_one :era
end

class QuoteMapper < Yaks::Mapper
  attributes :id, :chinese
end

class EraMapper < Yaks::Mapper
  attributes :id, :name
end


================================================
FILE: yaks/spec/fixture_helpers.rb
================================================
require 'yaml'
require 'json'

module FixtureHelpers
  module_function

  def load_json_fixture(name)
    JSON.parse(Yaks::Root.join('spec/json', name + '.json').read)
  end

  def load_yaml_fixture(name)
    YAML.load(Yaks::Root.join('spec/yaml', name + '.yaml').read)
  end
end


================================================
FILE: yaks/spec/integration/dynamic_form_fields_spec.rb
================================================
RSpec.describe 'dynamic form fields' do
  let(:mapper) do
    Class.new(Yaks::Mapper) do
      type :awesome
      form :foo do
        text :name
        dynamic do |object|
          object.each do |x|
            text x
          end
        end
      end
    end
  end

  let(:yaks) { Yaks.new }
  let(:object) { [:a, :b, :c] }

  it 'should create dynamic form fields' do
    expect(yaks.map(object, mapper: mapper)).to eql Yaks::Resource.new(
      type: :awesome,
      forms: [
        Yaks::Resource::Form.new(
          name: :foo,
          fields: [
            Yaks::Resource::Form::Field.new(name: :name, type: :text),
            Yaks::Resource::Form::Field.new(name: :a, type: :text),
            Yaks::Resource::Form::Field.new(name: :b, type: :text),
            Yaks::Resource::Form::Field.new(name: :c, type: :text)
          ]
        )
      ]
    )
  end
end


================================================
FILE: yaks/spec/integration/fieldset_spec.rb
================================================
RSpec.describe 'dynamic form fields' do
  let(:mapper) do
    Class.new(Yaks::Mapper) do
      type :awesome
      form :foo do
        fieldset do
          legend "I am legend"
          text :bar
        end
      end
    end
  end

  let(:yaks) { Yaks.new }

  it 'should create fieldsets with fields' do
    expect(yaks.map(:foo, mapper: mapper)).to eql Yaks::Resource.new(
      type: :awesome,
      forms: [
        Yaks::Resource::Form.new(
          name: :foo,
          fields: [
            Yaks::Resource::Form::Fieldset.new(
              fields: [
                Yaks::Resource::Form::Legend.new(label: "I am legend", type: :legend),
                Yaks::Resource::Form::Field.new(name: :bar, type: :text)
              ]
            )
          ]
        )
      ]
    )
  end

  it 'should convert to halo' do
    expect(
      yaks.with(default_format: :halo, hooks: [[:skip, :serialize]]).call(:foo, mapper: mapper)
    ).to eql(
      "_controls" => {
        "foo" => {
          "name" => "foo",
          "fields" => [
            {
              "type" => "fieldset",
              "fields" => [
                {
                  "label" => "I am legend",
                  "type" => "legend"
                }, {
                  "name" => "bar",
                  "type" => "text"
                }
              ]
            }
          ]
        }
      }
    )
  end
end


================================================
FILE: yaks/spec/integration/map_to_resource_spec.rb
================================================
RSpec.describe 'Mapping domain models to Resource objects' do
  include_context 'fixtures'
  include_context 'yaks context'

  subject { mapper.call(john) }
  let(:mapper) { FriendMapper.new(yaks_context) }

  it { should be_a Yaks::Resource }
  its(:type)         { should eql 'friend' }
  its(:attributes)   { should eql(id: 1, name: 'john') }
  its(:links)        { should eql [ Yaks::Resource::Link.new(rel: :copyright, uri: '/api/copyright/2024') ] }

  specify {
    subject.subresources == [
      Yaks::Resource.new(
        type: 'pet_peeve',
        rels: ['rel:pet_peeve'],
        attributes: {id: 4, type: 'parsing with regexps'}
      ),
      Yaks::CollectionResource.new(
        type: 'pet',
        rels: ['rel:pets'],
        members: [
          Yaks::Resource.new(
            type: 'pet',
            attributes: {id: 2, species: 'dog', name: 'boingboing'}
          ),
          Yaks::Resource.new(
            type: 'pet',
            attributes: {id: 3, species: 'cat', name: 'wassup'}
          )
        ]
      )
    ]
  }

  its(:subresources) {
    should eq(
      [
        Yaks::Resource.new(
          type: 'pet_peeve',
          rels: ['rel:pet_peeve'],
          attributes: {id: 4, type: 'parsing with regexps'}
        ),
        Yaks::CollectionResource.new(
          type: 'pet',
          rels: ['rel:pets'],
          members: [
            Yaks::Resource.new(
              type: 'pet',
              attributes: {id: 2, species: 'dog', name: 'boingboing'}
            ),
            Yaks::Resource.new(
              type: 'pet',
              attributes: {id: 3, species: 'cat', name: 'wassup'}
            )
          ]
        )
      ]
    )
  }
end


================================================
FILE: yaks/spec/json/confucius.collection_json.json
================================================
{
  "collection": {
    "version": "1.0",
    "href": "http://literature.example.com/authors/kongzi",
    "items": [
      {
        "href": "http://literature.example.com/authors/kongzi",
        "data": [
          { "name": "id",         "value": 9           },
          { "name": "name",       "value": "孔子"      },
          { "name": "pinyin",     "value": "Kongzi"    },
          { "name": "latinized",  "value": "Confucius" }
        ],
        "links": [
          {
            "rel": "profile",
            "href": "http://literature.example.com/profiles/scholar"
          },
          {
            "name": "Search for quotes",
            "rel": "http://literature.example.com/rels/quotes",
            "href": "http://literature.example.com/quotes/?author=kongzi&q={query}"
          }
        ]
      }
    ]
  }
}


================================================
FILE: yaks/spec/json/confucius.hal.json
================================================
{
  "id": 9,
  "name": "孔子",
  "pinyin": "Kongzi",
  "latinized": "Confucius",
  "_links": {
    "self":    { "href": "http://literature.example.com/authors/kongzi"  },
    "profile": { "href": "http://literature.example.com/profiles/scholar" },
    "http://literature.example.com/rels/quotes": [
        {
          "href": "http://literature.example.com/quotes/?author=kongzi&q={query}",
          "templated": true,
          "title": "Search for quotes"
        }
    ]
  },
  "_embedded": {
    "http://literature.example.com/rel/works": [
      {
        "id": 11,
        "chinese_name": "論語",
        "english_name": "Analects",
        "_links": {
          "self":    { "href": "http://literature.example.com/work/11" },
          "profile": { "href": "http://literature.example.com/profiles/work" }
        },
        "_embedded": {
          "http://literature.example.com/rel/quotes": [
            {
              "id": 17,
              "chinese": "廄焚。子退朝,曰:“傷人乎?” 不問馬。"
            },
            {
              "id": 18,
              "chinese": "子曰:“其恕乎!己所不欲、勿施於人。”"
            }
          ],
          "http://literature.example.com/rel/era": {
            "id": 99,
            "name": "Zhou Dynasty"
          }
        }
      },
      {
        "id": 12,
        "chinese_name": "易經",
        "english_name": "Commentaries to the Yi-jing",
        "_links": {
          "self":    { "href": "http://literature.example.com/work/12" },
          "profile": { "href": "http://literature.example.com/profiles/work" }
        },
        "_embedded": {
          "http://literature.example.com/rel/quotes": [],
          "http://literature.example.com/rel/era": null
        }
      }
    ]
  }
}


================================================
FILE: yaks/spec/json/confucius.halo.json
================================================
{
  "id": 9,
  "name": "孔子",
  "pinyin": "Kongzi",
  "latinized": "Confucius",
  "_links": {
    "self":    { "href": "http://literature.example.com/authors/kongzi"  },
    "profile": { "href": "http://literature.example.com/profiles/scholar" },
    "http://literature.example.com/rels/quotes": {
       "href": "http://literature.example.com/quotes/?author=kongzi&q={query}",
       "templated": true,
       "title": "Search for quotes"
     }
  },
  "_embedded": {
    "http://literature.example.com/rel/works": [
      {
        "id": 11,
        "chinese_name": "論語",
        "english_name": "Analects",
        "_links": {
          "self":    { "href": "http://literature.example.com/work/11" },
          "profile": { "href": "http://literature.example.com/profiles/work" }
        },
        "_embedded": {
          "http://literature.example.com/rel/quotes": [
            {
              "id": 17,
              "chinese": "廄焚。子退朝,曰:“傷人乎?” 不問馬。"
            },
            {
              "id": 18,
              "chinese": "子曰:“其恕乎!己所不欲、勿施於人。”"
            }
          ],
          "http://literature.example.com/rel/era": {
            "id": 99,
            "name": "Zhou Dynasty"
          }
        }
      },
      {
        "id": 12,
        "chinese_name": "易經",
        "english_name": "Commentaries to the Yi-jing",
        "_links": {
          "self":    { "href": "http://literature.example.com/work/12" },
          "profile": { "href": "http://literature.example.com/profiles/work" }
        },
        "_embedded": {
          "http://literature.example.com/rel/quotes": [],
          "http://literature.example.com/rel/era": null
        }
      }
    ]
  },
  "_controls": {
    "search": {
      "name": "search",
      "title": "Find a Scholar",
      "method": "POST",
      "media_type": "application/x-www-form-urlencoded",
      "fields": [
        {
          "name": "name",
          "label": "Scholar Name",
          "type": "text"
        },
        {
          "name": "pinyin",
          "label": "Hanyu Pinyin",
          "type": "text"
        }
      ]
    }
  }
}


================================================
FILE: yaks/spec/json/confucius.json_api.json
================================================
{
  "data": {
    "id": "9",
    "type": "scholars",
    "attributes": {
      "name": "孔子",
      "pinyin": "Kongzi",
      "latinized": "Confucius"
    },
    "relationships": {
      "works": {
        "data": [{"type": "works", "id": "11"}, {"type": "works", "id": "12"}]
      }
    },
		"links": {
  	  "profile": "http://literature.example.com/profiles/scholar",
      "self": "http://literature.example.com/authors/kongzi",
			"http://literature.example.com/rels/quotes": "http://literature.example.com/quotes/?author=kongzi&q={query}"
	  }
  },
  "included": [
      {
        "id": "11",
        "type": "works",
        "attributes": {
          "chinese_name": "論語",
          "english_name": "Analects"
        },
        "relationships": {
          "quotes": {
            "data": [{"type": "quotes", "id": "17"}, {"type": "quotes", "id": "18"}]
          },
          "era": {"data": {"type": "erae", "id": "99"}}
        },
        "links": {
          "profile": "http://literature.example.com/profiles/work",
          "self": "http://literature.example.com/work/11"
        }
      },
      {
        "id": "17",
        "type": "quotes",
        "attributes": {
          "chinese": "廄焚。子退朝,曰:“傷人乎?” 不問馬。"
        }
      },
      {
        "id": "18",
        "type": "quotes",
        "attributes": {
          "chinese": "子曰:“其恕乎!己所不欲、勿施於人。”"
        }
      },
      {
        "id": "99",
        "type": "erae",
        "attributes": {
          "name": "Zhou Dynasty"
        }
      },
      {
        "id": "12",
        "type": "works",
        "attributes": {
          "chinese_name": "易經",
          "english_name": "Commentaries to the Yi-jing"
        },
        "relationships": {
          "quotes": { "data": [] },
          "era": { "data": null }
        },
        "links": {
          "profile": "http://literature.example.com/profiles/work",
          "self": "http://literature.example.com/work/12"
        }
      }
  ]
}


================================================
FILE: yaks/spec/json/john.hal.json
================================================
{
  "id": 1,
  "name": "john",
  "_links": {
    "copyright": [
      {
        "href": "/api/copyright/2024"
      }
    ]
  },
  "_embedded": {
    "http://api.mysuperfriends.com/pet_peeve": {
      "id": 4,
      "type": "parsing with regexps"
    },
    "http://api.mysuperfriends.com/pets": [
      {
        "id": 2,
        "name": "boingboing",
        "species": "dog"
      },
      {
        "id": 3,
        "name": "wassup",
        "species": "cat"
      }
    ]
  }
}


================================================
FILE: yaks/spec/json/list_of_quotes.collection_json.json
================================================
{
  "collection": {
    "version": "1.0",
    "items": [
      {
        "data": [
          {
            "name": "id",
            "value": 17
          },
          {
            "name": "chinese",
            "value": "廄焚。子退朝,曰:“傷人乎?” 不問馬。"
          }
        ]
      },
      {
        "data": [
          {
            "name": "id",
            "value": 18
          },
          {
            "name": "chinese",
            "value": "子曰:“其恕乎!己所不欲、勿施於人。”"
          }
        ]
      },
      {
        "data": [
          {
            "name": "id",
            "value": 99
          },
          {
            "name": "chinese",
            "value": null
          }
        ]
      }
    ]
  }
}


================================================
FILE: yaks/spec/json/list_of_quotes.hal.json
================================================
{
  "_embedded": {
    "rel:quotes": [
      {
        "id": 17,
        "chinese": "廄焚。子退朝,曰:“傷人乎?” 不問馬。"
      },
      {
        "id": 18,
        "chinese": "子曰:“其恕乎!己所不欲、勿施於人。”"
      },
      {
        "id": 99,
        "chinese": null
      }
    ]
  }
}


================================================
FILE: yaks/spec/json/list_of_quotes.json_api.json
================================================
 {
  "data": [
    {
      "type": "quotes",
      "id": "17",
      "attributes": {
        "chinese": "廄焚。子退朝,曰:“傷人乎?” 不問馬。"
      }
    },
    {
      "type": "quotes",
      "id": "18",
      "attributes": {
        "chinese": "子曰:“其恕乎!己所不欲、勿施於人。”"
      }
    },
    {
      "type": "quotes",
      "id": "99",
      "attributes": {
        "chinese": null
      }
    }
  ]
}


================================================
FILE: yaks/spec/json/plant_collection.collection.json
================================================
{
  "collection": {
    "version": "1.0",
    "href": "http://api.example.com/plants",
    "items": [
      {
        "href": "http://api.example.com/plants/7/plain_grass",
        "data": [ { "name": "name", "value": "Plain grass" },
                  { "name": "type", "value": "grass" } ],
        "links": [
          { "rel": "profile", "href": "http://api.example.com/doc/plant" }
        ]
      },
      {
        "href": "http://api.example.com/plants/15/oak",
        "data": [ { "name": "name", "value": "Oak" },
                  { "name": "type", "value": "tree" } ],
        "links": [
          { "rel": "profile", "href": "http://api.example.com/doc/plant" }
        ]
      },
      {
        "href": "http://api.example.com/plants/33/passiflora",
        "data": [ { "name": "name", "value": "Passiflora" },
                  { "name": "type", "value": "flower" } ],
        "links": [
          { "rel": "profile", "href": "http://api.example.com/doc/plant" }
        ]
      }
    ],
    "links": [
      { "href": "http://api.example.com/plants", "rel": "self" },
      { "href": "http://api.example.com/doc/plant_collection", "rel": "profile" }
    ]
  }
}


================================================
FILE: yaks/spec/json/plant_collection.hal.json
================================================
{
   "_links" : {
      "profile" : { "href" : "http://api.example.com/doc/plant_collection" },
      "self" : { "href" : "http://api.example.com/plants" }
  },
   "_embedded" : {
      "http://api.example.com/rels/plants" : [
         {
            "_links" : {
               "profile" : { "href" : "http://api.example.com/doc/plant" },
               "self" : { "href" : "http://api.example.com/plants/7/plain_grass" }
            },
            "name" : "Plain grass",
            "type" : "grass"
         },
         {
            "_links" : {
               "profile" : { "href" : "http://api.example.com/doc/plant" },
               "self" : { "href" : "http://api.example.com/plants/15/oak" }
            },
            "name" : "Oak",
            "type" : "tree"
         },
         {
            "_links" : {
               "profile" : { "href" : "http://api.example.com/doc/plant" },
               "self" : { "href" : "http://api.example.com/plants/33/passiflora" }
            },
            "name" : "Passiflora",
            "type" : "flower"
         }
      ]
   }
}


================================================
FILE: yaks/spec/json/youtypeitwepostit.collection_json.json
================================================
{
  "collection": {
    "version": "1.0",
    "href": "http://www.youtypeitwepostit.com/api/",

    "links": [
    
Download .txt
gitextract_hj5iupwf/

├── .gitignore
├── .rubocop.yml
├── .travis.yml
├── ADDING_FORMATS.md
├── CHANGELOG.md
├── COOKBOOK.md
├── DEVELOPERS.md
├── FORMATS.org
├── Gemfile
├── IDENTIFIERS.md
├── LICENSE
├── Rakefile
├── bench/
│   ├── bench.rb
│   └── bench_1000.rb
├── code_of_conduct.md
├── notes.org
├── shared/
│   ├── rake_tasks.rb
│   └── rspec_config.rb
├── yaks/
│   ├── .rspec
│   ├── README.md
│   ├── Rakefile
│   ├── ataru_setup.rb
│   ├── find_missing_tests.rb
│   ├── lib/
│   │   ├── yaks/
│   │   │   ├── behaviour/
│   │   │   │   └── optional_includes.rb
│   │   │   ├── breaking_changes.rb
│   │   │   ├── builder.rb
│   │   │   ├── changelog.rb
│   │   │   ├── collection_mapper.rb
│   │   │   ├── collection_resource.rb
│   │   │   ├── config.rb
│   │   │   ├── configurable.rb
│   │   │   ├── default_policy.rb
│   │   │   ├── errors.rb
│   │   │   ├── format/
│   │   │   │   ├── collection_json.rb
│   │   │   │   ├── hal.rb
│   │   │   │   ├── halo.rb
│   │   │   │   └── json_api.rb
│   │   │   ├── format.rb
│   │   │   ├── fp/
│   │   │   │   └── callable.rb
│   │   │   ├── html5_forms.rb
│   │   │   ├── mapper/
│   │   │   │   ├── association.rb
│   │   │   │   ├── association_mapper.rb
│   │   │   │   ├── attribute.rb
│   │   │   │   ├── config.rb
│   │   │   │   ├── form/
│   │   │   │   │   ├── config.rb
│   │   │   │   │   ├── dynamic_field.rb
│   │   │   │   │   ├── field/
│   │   │   │   │   │   └── option.rb
│   │   │   │   │   ├── field.rb
│   │   │   │   │   ├── fieldset.rb
│   │   │   │   │   └── legend.rb
│   │   │   │   ├── form.rb
│   │   │   │   ├── has_many.rb
│   │   │   │   ├── has_one.rb
│   │   │   │   └── link.rb
│   │   │   ├── mapper.rb
│   │   │   ├── null_resource.rb
│   │   │   ├── pipeline.rb
│   │   │   ├── primitivize.rb
│   │   │   ├── reader/
│   │   │   │   ├── hal.rb
│   │   │   │   └── json_api.rb
│   │   │   ├── resource/
│   │   │   │   ├── form/
│   │   │   │   │   ├── field/
│   │   │   │   │   │   └── option.rb
│   │   │   │   │   ├── field.rb
│   │   │   │   │   ├── fieldset.rb
│   │   │   │   │   └── legend.rb
│   │   │   │   ├── form.rb
│   │   │   │   ├── has_fields.rb
│   │   │   │   └── link.rb
│   │   │   ├── resource.rb
│   │   │   ├── runner.rb
│   │   │   ├── serializer.rb
│   │   │   ├── util.rb
│   │   │   └── version.rb
│   │   └── yaks.rb
│   ├── spec/
│   │   ├── acceptance/
│   │   │   ├── acceptance_spec.rb
│   │   │   ├── json_shared_examples.rb
│   │   │   └── models.rb
│   │   ├── fixture_helpers.rb
│   │   ├── integration/
│   │   │   ├── dynamic_form_fields_spec.rb
│   │   │   ├── fieldset_spec.rb
│   │   │   └── map_to_resource_spec.rb
│   │   ├── json/
│   │   │   ├── confucius.collection_json.json
│   │   │   ├── confucius.hal.json
│   │   │   ├── confucius.halo.json
│   │   │   ├── confucius.json_api.json
│   │   │   ├── john.hal.json
│   │   │   ├── list_of_quotes.collection_json.json
│   │   │   ├── list_of_quotes.hal.json
│   │   │   ├── list_of_quotes.json_api.json
│   │   │   ├── plant_collection.collection.json
│   │   │   ├── plant_collection.hal.json
│   │   │   └── youtypeitwepostit.collection_json.json
│   │   ├── sanity_spec.rb
│   │   ├── spec_helper.rb
│   │   ├── support/
│   │   │   ├── classes_for_policy_testing.rb
│   │   │   ├── deep_eql.rb
│   │   │   ├── fixtures.rb
│   │   │   ├── friends_mapper.rb
│   │   │   ├── models.rb
│   │   │   ├── pet_mapper.rb
│   │   │   ├── pet_peeve_mapper.rb
│   │   │   ├── shared_contexts.rb
│   │   │   └── youtypeit_models_mappers.rb
│   │   ├── unit/
│   │   │   └── yaks/
│   │   │       ├── behaviour/
│   │   │       │   └── optional_includes_spec.rb
│   │   │       ├── builder_spec.rb
│   │   │       ├── collection_mapper_spec.rb
│   │   │       ├── collection_resource_spec.rb
│   │   │       ├── config_spec.rb
│   │   │       ├── configurable_spec.rb
│   │   │       ├── default_policy/
│   │   │       │   ├── derive_mapper_from_collection_spec.rb
│   │   │       │   ├── derive_mapper_from_item_spec.rb
│   │   │       │   └── derive_mapper_from_object_spec.rb
│   │   │       ├── default_policy_spec.rb
│   │   │       ├── format/
│   │   │       │   ├── collection_json_spec.rb
│   │   │       │   ├── hal_spec.rb
│   │   │       │   ├── halo_spec.rb
│   │   │       │   ├── html_spec.rb
│   │   │       │   └── json_api_spec.rb
│   │   │       ├── format_spec.rb
│   │   │       ├── fp/
│   │   │       │   └── callable_spec.rb
│   │   │       ├── mapper/
│   │   │       │   ├── association_mapper_spec.rb
│   │   │       │   ├── association_spec.rb
│   │   │       │   ├── attribute_spec.rb
│   │   │       │   ├── config_spec.rb
│   │   │       │   ├── form/
│   │   │       │   │   ├── config_spec.rb
│   │   │       │   │   ├── dynamic_field_spec.rb
│   │   │       │   │   ├── field/
│   │   │       │   │   │   └── option_spec.rb
│   │   │       │   │   ├── field_spec.rb
│   │   │       │   │   ├── fieldset_spec.rb
│   │   │       │   │   └── legend_spec.rb
│   │   │       │   ├── form_spec.rb
│   │   │       │   ├── has_many_spec.rb
│   │   │       │   ├── has_one_spec.rb
│   │   │       │   └── link_spec.rb
│   │   │       ├── mapper_spec.rb
│   │   │       ├── null_resource_spec.rb
│   │   │       ├── pipeline_spec.rb
│   │   │       ├── primitivize_spec.rb
│   │   │       ├── resource/
│   │   │       │   ├── form/
│   │   │       │   │   ├── field_spec.rb
│   │   │       │   │   ├── fieldset_spec.rb
│   │   │       │   │   └── legend_spec.rb
│   │   │       │   ├── form_spec.rb
│   │   │       │   ├── has_fields_spec.rb
│   │   │       │   └── link_spec.rb
│   │   │       ├── resource_spec.rb
│   │   │       ├── runner_spec.rb
│   │   │       ├── serializer_spec.rb
│   │   │       └── util_spec.rb
│   │   └── yaml/
│   │       ├── confucius.yaml
│   │       ├── list_of_quotes.yaml
│   │       └── youtypeitwepostit.yaml
│   └── yaks.gemspec
├── yaks-html/
│   ├── README.md
│   ├── Rakefile
│   ├── lib/
│   │   ├── yaks/
│   │   │   └── format/
│   │   │       ├── html.rb
│   │   │       └── template.html
│   │   ├── yaks-html/
│   │   │   └── rspec.rb
│   │   └── yaks-html.rb
│   ├── spec/
│   │   ├── smoke_test_spec.rb
│   │   ├── spec_helper.rb
│   │   └── support/
│   │       └── test_app.rb
│   └── yaks-html.gemspec
├── yaks-sinatra/
│   ├── .rspec
│   ├── README.md
│   ├── Rakefile
│   ├── lib/
│   │   └── yaks-sinatra.rb
│   ├── spec/
│   │   ├── integration/
│   │   │   ├── classic_app.rb
│   │   │   ├── classic_spec.rb
│   │   │   └── modular_spec.rb
│   │   ├── integration_helper.rb
│   │   └── spec_helper.rb
│   └── yaks-sinatra.gemspec
└── yaks-transit/
    ├── README.md
    ├── lib/
    │   └── yaks-transit.rb
    └── yaks-transit.gemspec
Download .txt
SYMBOL INDEX (576 symbols across 82 files)

FILE: bench/bench_1000.rb
  class FlatMapper (line 24) | class FlatMapper < Yaks::Mapper
  class DeepMapper (line 29) | class DeepMapper < Yaks::Mapper
  function profile! (line 35) | def profile!(name)

FILE: shared/rake_tasks.rb
  function mutant_task (line 7) | def mutant_task(_gem)
  function gem_tasks (line 19) | def gem_tasks(gem)

FILE: yaks-html/lib/yaks-html/rspec.rb
  type YaksHTML (line 4) | module YaksHTML
    type CapybaraDSL (line 16) | module CapybaraDSL
      function included (line 17) | def self.included(base)
      function click_rel (line 23) | def click_rel(rel)
      function click_first_rel (line 28) | def click_first_rel(rel)
      function submit! (line 33) | def submit!
      function within_form (line 37) | def within_form(name, &block)
      function submit_form (line 41) | def submit_form(name, &block)
      function refresh (line 48) | def refresh
      function env (line 52) | def env
      function find_form (line 58) | def find_form(name)

FILE: yaks-html/lib/yaks/format/html.rb
  type Yaks (line 3) | module Yaks
    class Format (line 4) | class Format
      class HTML (line 5) | class HTML < self
        method template (line 10) | def template
        method section (line 14) | def section(name)
        method serialize_resource (line 18) | def serialize_resource(resource)
        method render (line 32) | def render(*args)
        method render_resource (line 38) | def render_resource(resource, templ = section('resource'))
        method render_attributes (line 49) | def render_attributes(attributes)
        method rel_href (line 59) | def rel_href(rel)
        method render_links (line 67) | def render_links(links)
        method render_subresources (line 84) | def render_subresources(resource, templ, sub_templ)
        method render_forms (line 103) | def render_forms(forms)
        method render_form (line 111) | def render_form(form_control)
        method render_field (line 126) | def render_field(field)
        method render_fieldset (line 158) | def render_fieldset(fieldset)
        method render_select_options (line 168) | def render_select_options(options)

FILE: yaks-html/spec/support/test_app.rb
  class TestApp (line 1) | class TestApp < Sinatra::Base
    class HomeMapper (line 4) | class HomeMapper < Yaks::Mapper
    class FriendMapper (line 8) | class FriendMapper < Yaks::Mapper
      method name (line 11) | def name
    class MessageMapper (line 22) | class MessageMapper < Yaks::Mapper

FILE: yaks-sinatra/lib/yaks-sinatra.rb
  type Yaks (line 4) | module Yaks
    type Sinatra (line 5) | module Sinatra
      type Helpers (line 10) | module Helpers
        function yaks (line 11) | def yaks(object, opts = {})
      function configure_yaks (line 18) | def configure_yaks(&block)
      function registered (line 28) | def self.registered(app)
  type Sinatra (line 41) | module Sinatra

FILE: yaks-sinatra/spec/integration/classic_app.rb
  class RootMapper (line 6) | class RootMapper < Yaks::Mapper

FILE: yaks-sinatra/spec/integration_helper.rb
  class MediaType (line 9) | class MediaType
    method initialize (line 17) | def initialize(header_string)
    method type_and_rest (line 21) | def type_and_rest
    method parameters (line 33) | def parameters
    method type (line 44) | def type
    method main_type (line 48) | def main_type
    method sub_type (line 52) | def sub_type
    method charset (line 56) | def charset
  type Yaks (line 61) | module Yaks
    type Sinatra (line 62) | module Sinatra
      type Test (line 63) | module Test
        type Helpers (line 64) | module Helpers
          function make_req (line 65) | def make_req(mime_type = 'application/hal+json')
          function last_content_type (line 70) | def last_content_type
        class ModularApp (line 75) | class ModularApp < ::Sinatra::Base
          type Helpers (line 76) | module Helpers
            function app (line 79) | def app
          class RootMapper (line 86) | class RootMapper < Yaks::Mapper
        type ClassicApp (line 101) | module ClassicApp
          type Helpers (line 102) | module Helpers
            function app (line 105) | def app

FILE: yaks-transit/lib/yaks-transit.rb
  type Yaks (line 6) | module Yaks
    class Format (line 7) | class Format
      class Transit (line 8) | class Transit < self
        class WriteHandler (line 11) | class WriteHandler
          method initialize (line 12) | def initialize(klass)
          method tag (line 16) | def tag(_o)
          method rep (line 20) | def rep(_o)
          method string_rep (line 23) | def string_rep(_)
        class ReadHandler (line 28) | class ReadHandler
          method initialize (line 29) | def initialize(klass)
          method from_rep (line 33) | def from_rep(rep)
        method call (line 45) | def call(resource, _env = {})

FILE: yaks/ataru_setup.rb
  type MyAPI (line 9) | module MyAPI
    class ProductMapper (line 12) | class ProductMapper < Yaks::Mapper
  class AuthorMapper (line 17) | class AuthorMapper < Yaks::Mapper
  class CommentMapper (line 20) | class CommentMapper < Yaks::Mapper
  class PostMapper (line 23) | class PostMapper < Yaks::Mapper
  class HomeMapper (line 32) | class HomeMapper < Yaks::Mapper; end
  class SpecialMapper (line 34) | class SpecialMapper < Yaks::Mapper; end
  type ActiveSupport (line 36) | module ActiveSupport
    class TimeWithZone (line 37) | class TimeWithZone < Time ; end
  class Currency (line 40) | class Currency ; end
  type Setup (line 42) | module Setup
    function setup (line 43) | def setup
    function teardown (line 49) | def teardown
    function my_env (line 58) | def my_env
    function post (line 63) | def post
    function product (line 68) | def product
    function mime_type (line 76) | def mime_type(*_args)

FILE: yaks/lib/yaks.rb
  type Yaks (line 25) | module Yaks
    function new (line 56) | def new(&blk)

FILE: yaks/lib/yaks/behaviour/optional_includes.rb
  type Yaks (line 3) | module Yaks
    type Behaviour (line 4) | module Behaviour
      type OptionalIncludes (line 5) | module OptionalIncludes
        function associations (line 8) | def associations
        function include_association? (line 16) | def include_association?(association)

FILE: yaks/lib/yaks/breaking_changes.rb
  type Yaks (line 1) | module Yaks

FILE: yaks/lib/yaks/builder.rb
  type Yaks (line 1) | module Yaks
    class Builder (line 19) | class Builder
      method initialize (line 22) | def initialize(klass, methods = [], &block)
      method create (line 29) | def create(*args, &block)
      method build (line 33) | def build(init_state, *extra_args, &block)
      method inspect (line 39) | def inspect

FILE: yaks/lib/yaks/changelog.rb
  type Yaks (line 1) | module Yaks
    type Changelog (line 2) | module Changelog
      function current (line 5) | def current
      function versions (line 9) | def versions
      function markdown (line 17) | def markdown

FILE: yaks/lib/yaks/collection_mapper.rb
  type Yaks (line 1) | module Yaks
    class CollectionMapper (line 2) | class CollectionMapper < Mapper
      method call (line 7) | def call(collection, _env = nil)
      method collection_rel (line 33) | def collection_rel
      method collection_type (line 41) | def collection_type
      method mapper_for_model (line 49) | def mapper_for_model(model)

FILE: yaks/lib/yaks/collection_resource.rb
  type Yaks (line 1) | module Yaks
    class CollectionResource (line 8) | class CollectionResource < Resource
      method collection? (line 15) | def collection?
      method seq (line 19) | def seq

FILE: yaks/lib/yaks/config.rb
  type Yaks (line 1) | module Yaks
    class Config (line 2) | class Config
      method format_options (line 21) | def format_options(format, options)
      method serializer (line 25) | def serializer(type, &serializer)
      method json_serializer (line 29) | def json_serializer(&serializer)
      method rel_template (line 39) | def rel_template(template)
      method mapper_namespace (line 43) | def mapper_namespace(namespace)
      method mapper_for (line 47) | def mapper_for(rule, mapper_class)
      method map_to_primitive (line 53) | def map_to_primitive(*args, &block)
      method policy (line 68) | def policy
      method runner (line 72) | def runner(object, options)
      method call (line 86) | def call(object, options = {})
      method map (line 91) | def map(object, options = {})
      method format (line 95) | def format(data, options = {})
      method read (line 99) | def read(data, options = {})

FILE: yaks/lib/yaks/configurable.rb
  type Yaks (line 1) | module Yaks
    type Configurable (line 20) | module Configurable
      function extended (line 23) | def self.extended(child)
      function inherited (line 27) | def inherited(child)
      function def_set (line 34) | def def_set(*method_names)
      function def_forward (line 57) | def def_forward(mappings, *names)
      function def_add (line 76) | def def_add(name, options)

FILE: yaks/lib/yaks/default_policy.rb
  type Yaks (line 2) | module Yaks
    class DefaultPolicy (line 3) | class DefaultPolicy
      method initialize (line 18) | def initialize(options = {})
      method derive_mapper_from_object (line 30) | def derive_mapper_from_object(model)
      method derive_mapper_from_collection (line 41) | def derive_mapper_from_collection(collection)
      method derive_mapper_from_item (line 63) | def derive_mapper_from_item(item)
      method derive_type_from_mapper_class (line 83) | def derive_type_from_mapper_class(mapper_class)
      method derive_type_from_collection (line 98) | def derive_type_from_collection(collection)
      method derive_mapper_from_association (line 103) | def derive_mapper_from_association(association)
      method derive_rel_from_association (line 109) | def derive_rel_from_association(association)
      method expand_rel (line 115) | def expand_rel(relname)
      method build_mapper_class (line 121) | def build_mapper_class(namespaces, klass)
      method next_class_for_lookup (line 128) | def next_class_for_lookup(item, namespaces, klass)
      method raise_mapper_not_found (line 136) | def raise_mapper_not_found(item)
      method detect_configured_mapper_for (line 142) | def detect_configured_mapper_for(object)

FILE: yaks/lib/yaks/errors.rb
  type Yaks (line 1) | module Yaks

FILE: yaks/lib/yaks/format.rb
  type Yaks (line 1) | module Yaks
    class Format (line 2) | class Format
      method initialize (line 19) | def initialize(options = {})
      method call (line 25) | def call(resource, env = {})
      method serialize_resource (line 32) | def serialize_resource(_resource)
      method all (line 42) | def all
      method register (line 50) | def register(format_name, serializer, media_type)
      method by_name (line 61) | def by_name(format_name)
      method by_media_type (line 68) | def by_media_type(media_type)
      method by_accept_header (line 73) | def by_accept_header(accept_header)
      method media_types (line 82) | def media_types
      method names (line 89) | def names
      method find (line 95) | def find(key, cond)

FILE: yaks/lib/yaks/format/collection_json.rb
  type Yaks (line 1) | module Yaks
    class Format (line 2) | class Format
      class CollectionJson (line 3) | class CollectionJson < self
        method serialize_resource (line 10) | def serialize_resource(resource)
        method serialize_items (line 24) | def serialize_items(resource)
        method serialize_links (line 44) | def serialize_links(resource)
        method serialize_queries (line 50) | def serialize_queries(resource)
        method queries? (line 65) | def queries?(resource)
        method links? (line 69) | def links?(resource)
        method template? (line 73) | def template?(resource)
        method form_is_query? (line 79) | def form_is_query?(form)
        method template_form_exists? (line 83) | def template_form_exists?(resource)
        method serialize_template (line 87) | def serialize_template(resource)

FILE: yaks/lib/yaks/format/hal.rb
  type Yaks (line 1) | module Yaks
    class Format (line 2) | class Format
      class Hal (line 21) | class Hal < self
        method transitive? (line 24) | def transitive?
        method inverse (line 28) | def inverse
        method serialize_resource (line 36) | def serialize_resource(resource)
        method serialize_links (line 60) | def serialize_links(links)
        method serialize_link (line 67) | def serialize_link(memo, link)
        method singular? (line 81) | def singular?(rel)
        method serialize_embedded (line 87) | def serialize_embedded(subresources)

FILE: yaks/lib/yaks/format/halo.rb
  type Yaks (line 1) | module Yaks
    class Format (line 2) | class Format
      class Halo (line 5) | class Halo < Hal
        method serialize_resource (line 8) | def serialize_resource(resource)
        method serialize_forms (line 16) | def serialize_forms(forms)
        method serialize_form (line 22) | def serialize_form(form)
        method serialize_form_field (line 29) | def serialize_form_field(field)

FILE: yaks/lib/yaks/format/json_api.rb
  type Yaks (line 1) | module Yaks
    class Format (line 2) | class Format
      class JsonAPI (line 3) | class JsonAPI < self
        method call (line 10) | def call(resource, _env = nil)
        method serialize_links (line 29) | def serialize_links(links)
        method serialize_resource (line 37) | def serialize_resource(resource)
        method serialize_relationships (line 55) | def serialize_relationships(subresources)
        method serialize_relationship (line 63) | def serialize_relationship(resource)
        method serialize_included_subresources (line 75) | def serialize_included_subresources(subresources, array)
        method serialize_included_resources (line 84) | def serialize_included_resources(subresource, included)
        method serialize_subresource (line 95) | def serialize_subresource(resource, included)
        method inverse (line 102) | def inverse
      class Reader (line 107) | class Reader

FILE: yaks/lib/yaks/fp/callable.rb
  type Yaks (line 1) | module Yaks
    type FP (line 2) | module FP
      type Callable (line 3) | module Callable
        function to_proc (line 4) | def to_proc

FILE: yaks/lib/yaks/html5_forms.rb
  type Yaks (line 1) | module Yaks
    type HTML5Forms (line 6) | module HTML5Forms

FILE: yaks/lib/yaks/mapper.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      method initialize (line 24) | def initialize(context)
      method policy (line 28) | def policy
      method env (line 32) | def env
      method mapper_stack (line 36) | def mapper_stack
      method mapper_name (line 40) | def self.mapper_name(policy)
      method mapper_name (line 44) | def mapper_name
      method call (line 48) | def call(object, _env = nil)
      method load_attribute (line 62) | def load_attribute(name)
      method expand_value (line 67) | def expand_value(value)
      method expand_uri (line 71) | def expand_uri(uri, expand = true)
      method map_attributes (line 94) | def map_attributes(resource)
      method map_links (line 100) | def map_links(resource)
      method map_subresources (line 106) | def map_subresources(resource)
      method map_forms (line 112) | def map_forms(resource)

FILE: yaks/lib/yaks/mapper/association.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class Association (line 3) | class Association
        method create (line 15) | def self.create(name, options = {})
        method add_to_resource (line 25) | def add_to_resource(resource, parent_mapper, context)
        method render_as_link? (line 30) | def render_as_link?(parent_mapper)
        method map_rel (line 34) | def map_rel(policy)
        method resolve_association_mapper (line 44) | def resolve_association_mapper(policy)

FILE: yaks/lib/yaks/mapper/association_mapper.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class AssociationMapper (line 3) | class AssociationMapper
        method initialize (line 6) | def initialize(parent_mapper, association, context)
        method policy (line 15) | def policy
        method call (line 19) | def call(resource)
        method add_link (line 29) | def add_link(resource)
        method add_subresource (line 37) | def add_subresource(resource)

FILE: yaks/lib/yaks/mapper/attribute.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class Attribute (line 3) | class Attribute
        method create (line 7) | def self.create(name, options = {}, &block)
        method add_to_resource (line 11) | def add_to_resource(resource, mapper, _context)

FILE: yaks/lib/yaks/mapper/config.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class Config (line 3) | class Config
        method add_attributes (line 8) | def add_attributes(*attrs)

FILE: yaks/lib/yaks/mapper/form.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class Form (line 3) | class Form
        method create (line 9) | def self.create(*args, &block)
        method add_to_resource (line 20) | def add_to_resource(resource, mapper, _context)
        method to_resource_form (line 25) | def to_resource_form(mapper)

FILE: yaks/lib/yaks/mapper/form/config.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class Form (line 3) | class Form
        class Config (line 4) | class Config
          method create (line 31) | def self.create(options)
          method build (line 37) | def self.build(options = {}, &block)
          method build_with_object (line 43) | def self.build_with_object(object, &block)
          method condition (line 47) | def condition(prc = nil, &blk)
          method to_resource_fields (line 51) | def to_resource_fields(mapper)

FILE: yaks/lib/yaks/mapper/form/dynamic_field.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class Form (line 3) | class Form
        class DynamicField (line 4) | class DynamicField
          method create (line 7) | def self.create(_opts = nil, &block)
          method to_resource_fields (line 11) | def to_resource_fields(mapper)

FILE: yaks/lib/yaks/mapper/form/field.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class Form (line 3) | class Form
        class Field (line 4) | class Field
          method condition (line 16) | def condition(blk1 = nil, &blk2)
          method create (line 25) | def self.create(*args)
          method to_resource_fields (line 35) | def to_resource_fields(mapper)
          method resource_options (line 43) | def resource_options(mapper)
          method resource_attributes (line 55) | def resource_attributes

FILE: yaks/lib/yaks/mapper/form/field/option.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class Form (line 3) | class Form
        class Field (line 4) | class Field
          class Option (line 6) | class Option
            method create (line 9) | def self.create(value, opts)
            method to_resource_field_option (line 13) | def to_resource_field_option(mapper)

FILE: yaks/lib/yaks/mapper/form/fieldset.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class Form (line 3) | class Form
        class Fieldset (line 4) | class Fieldset
          method create (line 10) | def self.create(options = {}, &block)
          method to_resource_fields (line 14) | def to_resource_fields(mapper)

FILE: yaks/lib/yaks/mapper/form/legend.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class Form (line 3) | class Form
        class Legend (line 4) | class Legend
          method create (line 7) | def self.create(label, opts = {})
          method to_resource_fields (line 11) | def to_resource_fields(mapper)

FILE: yaks/lib/yaks/mapper/has_many.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class HasMany (line 3) | class HasMany < Association
        method map_resource (line 7) | def map_resource(collection, context)
        method collection_mapper (line 16) | def collection_mapper(collection = nil, policy = nil)
        method singular_name (line 21) | def singular_name

FILE: yaks/lib/yaks/mapper/has_one.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class HasOne (line 3) | class HasOne < Association
        method map_resource (line 4) | def map_resource(object, context)
        method singular_name (line 10) | def singular_name

FILE: yaks/lib/yaks/mapper/link.rb
  type Yaks (line 1) | module Yaks
    class Mapper (line 2) | class Mapper
      class Link (line 26) | class Link
        method create (line 30) | def self.create(*args)
        method add_to_resource (line 35) | def add_to_resource(resource, mapper, _context)
        method rel? (line 50) | def rel?(rel)
        method templated? (line 55) | def templated?
        method map_to_resource_link (line 59) | def map_to_resource_link(mapper)
        method resource_link_options (line 79) | def resource_link_options(mapper)

FILE: yaks/lib/yaks/null_resource.rb
  type Yaks (line 1) | module Yaks
    class NullResource (line 2) | class NullResource < Resource
      method initialize (line 6) | def initialize(opts = {})
      method each (line 13) | def each
      method collection? (line 17) | def collection? # rubocop:disable Style/TrivialAccessors
      method null_resource? (line 21) | def null_resource?
      method seq (line 25) | def seq
      method map (line 29) | def map
      method merge_attributes (line 34) | def merge_attributes(_new_attrs)
      method add_link (line 38) | def add_link(_link)
      method add_form (line 42) | def add_form(_form)
      method add_subresource (line 46) | def add_subresource(_subresource)

FILE: yaks/lib/yaks/pipeline.rb
  type Yaks (line 1) | module Yaks
    class Pipeline (line 2) | class Pipeline
      method call (line 5) | def call(input, env)
      method insert_hooks (line 9) | def insert_hooks(hooks)
      method transitive? (line 30) | def transitive?
      method inverse (line 34) | def inverse

FILE: yaks/lib/yaks/primitivize.rb
  type Yaks (line 1) | module Yaks
    class Primitivize (line 2) | class Primitivize
      method initialize (line 5) | def initialize
      method call (line 9) | def call(object)
      method map (line 17) | def map(*types, &block)
      method create (line 23) | def self.create

FILE: yaks/lib/yaks/reader/hal.rb
  type Yaks (line 1) | module Yaks
    type Reader (line 2) | module Reader
      class Hal (line 3) | class Hal
        method call (line 6) | def call(parsed_json, _env = {})
        method type_from_links (line 19) | def type_from_links(links)
        method convert_links (line 24) | def convert_links(links)
        method array (line 35) | def array(x)
        method convert_embedded (line 39) | def convert_embedded(embedded)

FILE: yaks/lib/yaks/reader/json_api.rb
  type Yaks (line 1) | module Yaks
    type Reader (line 2) | module Reader
      class JsonAPI (line 3) | class JsonAPI
        method call (line 4) | def call(parsed_json, _env = {})
        method convert_embedded (line 31) | def convert_embedded(relationships, included)
        method convert_links (line 63) | def convert_links(links)

FILE: yaks/lib/yaks/resource.rb
  type Yaks (line 1) | module Yaks
    class Resource (line 2) | class Resource
      method initialize (line 13) | def initialize(attrs = {})
      method [] (line 18) | def [](attr)
      method find_form (line 22) | def find_form(name)
      method seq (line 26) | def seq
      method self_link (line 30) | def self_link
      method collection? (line 46) | def collection?
      method with_collection (line 51) | def with_collection(*)
      method null_resource? (line 55) | def null_resource?
      method members (line 59) | def members
      method merge_attributes (line 67) | def merge_attributes(new_attrs)
      method add_rel (line 72) | def add_rel(rel)
      method add_link (line 76) | def add_link(link)
      method add_form (line 80) | def add_form(form)
      method add_subresource (line 84) | def add_subresource(subresource)

FILE: yaks/lib/yaks/resource/form.rb
  type Yaks (line 1) | module Yaks
    class Resource (line 2) | class Resource
      class Form (line 3) | class Form
        method [] (line 7) | def [](name)
        method values (line 11) | def values
        method method? (line 17) | def method?(meth)
        method has_action? (line 21) | def has_action? # rubocop:disable Style/PredicateName

FILE: yaks/lib/yaks/resource/form/field.rb
  type Yaks (line 1) | module Yaks
    class Resource (line 2) | class Resource
      class Form (line 3) | class Form
        class Field (line 4) | class Field
          method value (line 8) | def value
          method with_value (line 17) | def with_value(value)
          method select_options_for_value (line 27) | def select_options_for_value(value)

FILE: yaks/lib/yaks/resource/form/field/option.rb
  type Yaks (line 1) | module Yaks
    class Resource (line 2) | class Resource
      class Form (line 3) | class Form
        class Field (line 4) | class Field
          class Option (line 5) | class Option

FILE: yaks/lib/yaks/resource/form/fieldset.rb
  type Yaks (line 1) | module Yaks
    class Resource (line 2) | class Resource
      class Form (line 3) | class Form
        class Fieldset (line 4) | class Fieldset
          method type (line 8) | def type

FILE: yaks/lib/yaks/resource/form/legend.rb
  type Yaks (line 1) | module Yaks
    class Resource (line 2) | class Resource
      class Form (line 3) | class Form
        class Legend (line 4) | class Legend
          method initialize (line 7) | def initialize(opts)

FILE: yaks/lib/yaks/resource/has_fields.rb
  type Yaks (line 1) | module Yaks
    class Resource (line 2) | class Resource
      type HasFields (line 3) | module HasFields
        function map_fields (line 4) | def map_fields(&block)
        function fields_flat (line 10) | def fields_flat(&block)

FILE: yaks/lib/yaks/resource/link.rb
  type Yaks (line 1) | module Yaks
    class Resource (line 2) | class Resource
      class Link (line 3) | class Link
        method title (line 6) | def title
        method templated? (line 10) | def templated?
        method rel? (line 14) | def rel?(rel)

FILE: yaks/lib/yaks/runner.rb
  type Yaks (line 1) | module Yaks
    class Runner (line 2) | class Runner
      method call (line 12) | def call
      method read (line 16) | def read
      method format (line 20) | def format
      method map (line 24) | def map
      method context (line 28) | def context
      method env (line 37) | def env
      method format_class (line 43) | def format_class
      method steps (line 50) | def steps
      method mapper (line 58) | def mapper
      method formatter (line 65) | def formatter
      method primitivizer (line 70) | def primitivizer
      method serializer (line 81) | def serializer
      method hooks (line 86) | def hooks

FILE: yaks/lib/yaks/serializer.rb
  type Yaks (line 1) | module Yaks
    type Serializer (line 2) | module Serializer
      function register (line 3) | def self.register(format, serializer)
      function all (line 8) | def self.all
      type JSONWriter (line 12) | module JSONWriter
        function call (line 15) | def self.call(data, _env)
        function transitive? (line 19) | def self.transitive?
        function inverse (line 23) | def self.inverse
      type JSONReader (line 28) | module JSONReader
        function call (line 31) | def self.call(data, _env)
        function transitive? (line 35) | def self.transitive?
        function inverse (line 39) | def self.inverse

FILE: yaks/lib/yaks/util.rb
  type Yaks (line 1) | module Yaks
    type Util (line 2) | module Util
      function underscore (line 8) | def underscore(str)
      function camelize (line 15) | def camelize(str)
      function slice_hash (line 20) | def slice_hash(hash, *keys)
      function reject_keys (line 24) | def reject_keys(hash, *keys)
      function symbolize_keys (line 28) | def symbolize_keys(hash)
      function extract_options (line 32) | def extract_options(args)
      function Resolve (line 49) | def Resolve(maybe_proc, context = nil) # rubocop:disable Style/Metho...
      type Deprecated (line 66) | module Deprecated
        function deprecated_alias (line 67) | def deprecated_alias(name, actual)

FILE: yaks/lib/yaks/version.rb
  type Yaks (line 1) | module Yaks

FILE: yaks/spec/acceptance/models.rb
  class Scholar (line 3) | class Scholar
  class Work (line 7) | class Work
  class Quote (line 11) | class Quote
  class Era (line 15) | class Era
  class LiteratureBaseMapper (line 19) | class LiteratureBaseMapper < Yaks::Mapper
  class ScholarMapper (line 24) | class ScholarMapper < LiteratureBaseMapper
    method downcased_pinyin (line 40) | def downcased_pinyin
  class WorkMapper (line 45) | class WorkMapper < LiteratureBaseMapper
  class QuoteMapper (line 51) | class QuoteMapper < Yaks::Mapper
  class EraMapper (line 55) | class EraMapper < Yaks::Mapper

FILE: yaks/spec/fixture_helpers.rb
  type FixtureHelpers (line 4) | module FixtureHelpers
    function load_json_fixture (line 7) | def load_json_fixture(name)
    function load_yaml_fixture (line 11) | def load_yaml_fixture(name)

FILE: yaks/spec/support/classes_for_policy_testing.rb
  class SoyMapper (line 3) | class SoyMapper; end
  class Soy (line 4) | class Soy; end
  class WildSoy (line 5) | class WildSoy < Soy; end
  type Grain (line 7) | module Grain
    class Soy (line 8) | class Soy; end
    class WildSoy (line 9) | class WildSoy < Soy; end
    class Wheat (line 11) | class Wheat; end
    class Durum (line 12) | class Durum < Wheat; end
    type Dry (line 14) | module Dry
      class Soy (line 15) | class Soy < ::Grain::Soy; end
      class SoyMapper (line 16) | class SoyMapper; end
    class SoyMapper (line 19) | class SoyMapper; end
    class SoyCollectionMapper (line 20) | class SoyCollectionMapper; end
  class HomeMapper (line 23) | class HomeMapper; end
  class WheatMapper (line 24) | class WheatMapper; end
  class ObjectMapper (line 25) | class ObjectMapper; end
  class BasicObjectMapper (line 26) | class BasicObjectMapper; end
  type MyMappers (line 28) | module MyMappers
    class SoyMapper (line 29) | class SoyMapper; end
    class WheatMapper (line 30) | class WheatMapper; end
    type Grain (line 32) | module Grain
      class SoyMapper (line 33) | class SoyMapper; end
  class SoyCollectionMapper (line 37) | class SoyCollectionMapper; end
  type Namespace (line 39) | module Namespace
    type Nested (line 40) | module Nested
      class Rye (line 41) | class Rye; end
      class Mung (line 42) | class Mung
        method to_s (line 44) | def to_s
    class RyeMapper (line 50) | class RyeMapper; end
    class RyeCollectionMapper (line 51) | class RyeCollectionMapper; end
    class CollectionMapper (line 53) | class CollectionMapper; end
    class ShoeMapper (line 55) | class ShoeMapper; end
  type DislikesCollectionMapper (line 58) | module DislikesCollectionMapper
    function const_get (line 59) | def self.const_get(const)
  type DislikesOtherMappers (line 64) | module DislikesOtherMappers
    function const_get (line 65) | def self.const_get(const)

FILE: yaks/spec/support/deep_eql.rb
  type Matchers (line 10) | module Matchers
    class DeepEql (line 11) | class DeepEql
      method initialize (line 16) | def initialize(expectation, stack = [], diffs = [])
      method description (line 23) | def description
      method recurse (line 27) | def recurse(target, expectation)
      method stack_as_jsonpath (line 33) | def stack_as_jsonpath
      method add_failure_message (line 44) | def add_failure_message(message)
      method compare (line 49) | def compare(key)
      method matches? (line 69) | def matches?(target)
      method failure_message_for_should (line 108) | def failure_message_for_should
      method failure_message_for_should_not (line 113) | def failure_message_for_should_not
  type RSpec (line 120) | module RSpec
    type Matchers (line 121) | module Matchers
      function deep_eql (line 122) | def deep_eql(exp)

FILE: yaks/spec/support/friends_mapper.rb
  class FriendMapper (line 1) | class FriendMapper < Yaks::Mapper
    method year (line 6) | def year

FILE: yaks/spec/support/models.rb
  class Pet (line 1) | class Pet
  class PetPeeve (line 9) | class PetPeeve
  class Friend (line 16) | class Friend

FILE: yaks/spec/support/pet_mapper.rb
  class PetMapper (line 1) | class PetMapper < Yaks::Mapper
  class GreatPetMapper (line 4) | class GreatPetMapper < Yaks::Mapper; end
  class GreatPetCollectionMapper (line 5) | class GreatPetCollectionMapper < Yaks::Mapper; end

FILE: yaks/spec/support/pet_peeve_mapper.rb
  class PetPeeveMapper (line 1) | class PetPeeveMapper < Yaks::Mapper

FILE: yaks/spec/support/youtypeit_models_mappers.rb
  type Youtypeitwepostit (line 1) | module Youtypeitwepostit
    class Message (line 2) | class Message
    class MessageMapper (line 10) | class MessageMapper < Yaks::Mapper
    class CollectionMapper (line 17) | class CollectionMapper < Yaks::CollectionMapper

FILE: yaks/spec/unit/yaks/builder_spec.rb
  class Buildable (line 2) | class Buildable
    method create (line 5) | def self.create(foo, bar)
    method finalize (line 9) | def finalize
    method wrong_type (line 13) | def wrong_type(x, y)

FILE: yaks/spec/unit/yaks/collection_mapper_spec.rb
  function foo (line 102) | def foo
  function bar (line 106) | def bar
  function collection (line 146) | def collection

FILE: yaks/spec/unit/yaks/config_spec.rb
  function configure (line 4) | def self.configure(&blk)

FILE: yaks/spec/unit/yaks/configurable_spec.rb
  class Kitten (line 3) | class Kitten
    method create (line 6) | def self.create(opts, &block)
  class Hanky (line 13) | class Hanky
    method create (line 16) | def self.create(sticky, opts = {})

FILE: yaks/spec/unit/yaks/fp/callable_spec.rb
  function call (line 6) | def call(x)

FILE: yaks/spec/unit/yaks/mapper/association_spec.rb
  function map_resource (line 6) | def map_resource(_object, _context)

FILE: yaks/spec/unit/yaks/mapper/form/field/option_spec.rb
  function color (line 6) | def color

FILE: yaks/spec/unit/yaks/mapper/form/field_spec.rb
  function month (line 18) | def month

FILE: yaks/spec/unit/yaks/mapper/link_spec.rb
  function link_computer (line 35) | def link_computer; end
  function mapper_method (line 209) | def mapper_method
  function add_link? (line 233) | def add_link?
  function add_link? (line 249) | def add_link?

FILE: yaks/spec/unit/yaks/mapper_spec.rb
  function attributes (line 40) | def attributes
  function associations (line 199) | def associations
  function fooattr (line 217) | def fooattr
  function link_generating_method (line 240) | def link_generating_method
  function foo (line 319) | def mapper.foo
  function foo (line 403) | def foo

FILE: yaks/spec/unit/yaks/primitivize_spec.rb
  function inspect (line 53) | def funny_object.inspect

FILE: yaks/spec/unit/yaks/runner_spec.rb
  function steps (line 22) | def steps
  function call (line 327) | def call(obj, _env)

FILE: yaks/spec/unit/yaks/util_spec.rb
  function to_s (line 91) | def self.to_s
  function foo (line 94) | def foo(x)
  function capture_stderr (line 101) | def capture_stderr
Condensed preview — 174 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (401K chars).
[
  {
    "path": ".gitignore",
    "chars": 110,
    "preview": "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",
    "chars": 3206,
    "preview": "AllCops:\n  Exclude:\n    - 'pkg/**/*'\n    - 'vendor/**/*'\n    - 'bench/**/*'\n\nLint/AmbiguousRegexpLiteral:\n  Enabled: fal"
  },
  {
    "path": ".travis.yml",
    "chars": 611,
    "preview": "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\nsc"
  },
  {
    "path": "ADDING_FORMATS.md",
    "chars": 1533,
    "preview": "# Adding Extra Output Formats to Yaks\n\nIndividual output formats are each handled by a dedicated `Yaks::Serializer` clas"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 18554,
    "preview": "### 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]"
  },
  {
    "path": "COOKBOOK.md",
    "chars": 4250,
    "preview": "# Yaks Cookbook\n\n## Represent Date/Time objects as iso8601\n\n``` ruby\n$yaks = Yaks.new do\n  map_to_primitive Date, Time, "
  },
  {
    "path": "DEVELOPERS.md",
    "chars": 8251,
    "preview": "# Yaks Dev Docs\n\nThis document is for when you want to hack on Yaks itself, or better\nunderstand its internals. To simpl"
  },
  {
    "path": "FORMATS.org",
    "chars": 6489,
    "preview": "#+TITLE:Comparison of Hypermedia Message Formats\n#+AUTHOR: Arne Brasseur\n#+email: arne@arnebrasseur.net\n#+INFOJS_OPT: vi"
  },
  {
    "path": "Gemfile",
    "chars": 406,
    "preview": "source 'https://rubygems.org'\n\ngemspec path: 'yaks'\ngemspec path: 'yaks-html'\ngemspec path: 'yaks-sinatra'\n\n# Transit de"
  },
  {
    "path": "IDENTIFIERS.md",
    "chars": 4598,
    "preview": "# Identifiers\n\nIn Yaks, and Hypermedia message formats in general, a number of\ndifferent types of identifiers are used. "
  },
  {
    "path": "LICENSE",
    "chars": 1062,
    "preview": "Copyright (c) 2013-2014 Arne Brasseur\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of t"
  },
  {
    "path": "Rakefile",
    "chars": 1560,
    "preview": "require 'yaks'\nrequire 'yaks-html'\nrequire 'yaks-sinatra'\nrequire 'yaks-transit'\n\nrequire 'rspec/core/rake_task'\nrequire"
  },
  {
    "path": "bench/bench.rb",
    "chars": 299,
    "preview": "\nrequire 'benchmark/ips'\nrequire 'yaks'\n\nrequire_relative '../spec/acceptance/models'\nrequire_relative '../spec/fixture_"
  },
  {
    "path": "bench/bench_1000.rb",
    "chars": 1390,
    "preview": "#!/usr/bin/env ruby\n\nrequire 'English'\nrequire 'benchmark/ips'\nrequire 'ruby-prof'\nrequire 'yaks'\n\nSIZE = 20\n$timestamp "
  },
  {
    "path": "code_of_conduct.md",
    "chars": 1420,
    "preview": "# Contributor Code of Conduct\n\nAs contributors and maintainers of this project, we pledge to respect\nall people who cont"
  },
  {
    "path": "notes.org",
    "chars": 4826,
    "preview": "0.4\n\n* DONE Get rid of profile/rel registry, use policy instead\n* DONE pass around policy explicitly instead of through "
  },
  {
    "path": "shared/rake_tasks.rb",
    "chars": 961,
    "preview": "require 'yaks'\nrequire 'yaks-html'\nrequire 'rubygems/package_task'\nrequire 'rspec/core/rake_task'\nrequire 'yard'\n\ndef mu"
  },
  {
    "path": "shared/rspec_config.rb",
    "chars": 1725,
    "preview": "require 'rspec/its'\nrequire 'bogus/rspec'\nrequire 'timeout'\n\n# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Config"
  },
  {
    "path": "yaks/.rspec",
    "chars": 14,
    "preview": "-r spec_helper"
  },
  {
    "path": "yaks/README.md",
    "chars": 30276,
    "preview": "[![Gem Version](https://badge.fury.io/rb/yaks.png)][gem]\n[![Build Status](https://secure.travis-ci.org/plexus/yaks.png?b"
  },
  {
    "path": "yaks/Rakefile",
    "chars": 2668,
    "preview": "load '../shared/rake_tasks.rb'\n\ngem_tasks(:yaks)\n\ntask :mutant_chunked do\n  # No subjects:\n  # Yaks,\n  # Yaks::Error,\n  "
  },
  {
    "path": "yaks/ataru_setup.rb",
    "chars": 1501,
    "preview": "# \"Require your project source code, with the correct path\"\n\nrequire \"yaks\"\nrequire \"hamster\"\n\nPost = Struct.new(:id, :t"
  },
  {
    "path": "yaks/find_missing_tests.rb",
    "chars": 1045,
    "preview": "#!/usr/bin/env ruby\n\nrequire 'mutant'\nrequire 'pry'\n\n# These are private methods that are tested by other methods in the"
  },
  {
    "path": "yaks/lib/yaks/behaviour/optional_includes.rb",
    "chars": 755,
    "preview": "require \"rack/utils\"\n\nmodule Yaks\n  module Behaviour\n    module OptionalIncludes\n      RACK_KEY = \"yaks.optional_include"
  },
  {
    "path": "yaks/lib/yaks/breaking_changes.rb",
    "chars": 2540,
    "preview": "module Yaks\n# These are displayed in a post-install message when installing the\n# gem to aid upgraiding\n\nBreakingChanges"
  },
  {
    "path": "yaks/lib/yaks/builder.rb",
    "chars": 896,
    "preview": "module Yaks\n  # State monad-ish thing.\n  #\n  # Generate a DSL syntax for immutable classes.\n  #\n  # @example\n  #\n  #   #"
  },
  {
    "path": "yaks/lib/yaks/changelog.rb",
    "chars": 463,
    "preview": "module Yaks\n  module Changelog\n    module_function\n\n    def current\n      versions[Yaks::VERSION]\n    end\n\n    def versi"
  },
  {
    "path": "yaks/lib/yaks/collection_mapper.rb",
    "chars": 1407,
    "preview": "module Yaks\n  class CollectionMapper < Mapper\n    alias_method :collection, :object\n\n    # @param [Array] collection\n   "
  },
  {
    "path": "yaks/lib/yaks/collection_resource.rb",
    "chars": 585,
    "preview": "module Yaks\n  # A collection of Resource objects, it has members, and its own set of link\n  # relations like self and pr"
  },
  {
    "path": "yaks/lib/yaks/config.rb",
    "chars": 2913,
    "preview": "module Yaks\n  class Config\n    extend Yaks::Util::Deprecated\n    include Yaks::FP::Callable,\n            Attribs.new(\n  "
  },
  {
    "path": "yaks/lib/yaks/configurable.rb",
    "chars": 3292,
    "preview": "module Yaks\n  # A \"Configurable\" class is one that keeps a configuration in a\n  # separate immutable object, of type cla"
  },
  {
    "path": "yaks/lib/yaks/default_policy.rb",
    "chars": 4583,
    "preview": "\nmodule Yaks\n  class DefaultPolicy\n    include Util\n\n    # Default policy options.\n    DEFAULTS = {\n      rel_template: "
  },
  {
    "path": "yaks/lib/yaks/errors.rb",
    "chars": 240,
    "preview": "module Yaks\n  Error = Class.new(StandardError)\n\n  IllegalStateError         = Class.new(Error)\n  RuntimeError           "
  },
  {
    "path": "yaks/lib/yaks/format/collection_json.rb",
    "chars": 2973,
    "preview": "module Yaks\n  class Format\n    class CollectionJson < self\n      register :collection_json, :json, 'application/vnd.coll"
  },
  {
    "path": "yaks/lib/yaks/format/hal.rb",
    "chars": 3037,
    "preview": "module Yaks\n  class Format\n    # Hypertext Application Language (http://stateless.co/hal_specification.html)\n    #\n    #"
  },
  {
    "path": "yaks/lib/yaks/format/halo.rb",
    "chars": 1321,
    "preview": "module Yaks\n  class Format\n    # Extension of Hal loosely based on the example by Mike Kelly given at\n    # https://gist"
  },
  {
    "path": "yaks/lib/yaks/format/json_api.rb",
    "chars": 3453,
    "preview": "module Yaks\n  class Format\n    class JsonAPI < self\n      register :json_api, :json, 'application/vnd.api+json'\n\n      i"
  },
  {
    "path": "yaks/lib/yaks/format.rb",
    "chars": 2262,
    "preview": "module Yaks\n  class Format\n    extend Forwardable\n    include Util,\n            FP::Callable\n\n    # @!attribute [r] opti"
  },
  {
    "path": "yaks/lib/yaks/fp/callable.rb",
    "chars": 120,
    "preview": "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",
    "chars": 1006,
    "preview": "module Yaks\n  # Based on the HTML living standard over at WHATWG\n  # https://html.spec.whatwg.org/multipage/forms.html\n "
  },
  {
    "path": "yaks/lib/yaks/mapper/association.rb",
    "chars": 1440,
    "preview": "module Yaks\n  class Mapper\n    class Association\n      include Attribs.new(\n                name:        Undefined,\n    "
  },
  {
    "path": "yaks/lib/yaks/mapper/association_mapper.rb",
    "chars": 1328,
    "preview": "module Yaks\n  class Mapper\n    class AssociationMapper\n      attr_reader :parent_mapper, :context, :rel, :association\n\n "
  },
  {
    "path": "yaks/lib/yaks/mapper/attribute.rb",
    "chars": 580,
    "preview": "module Yaks\n  class Mapper\n    class Attribute\n      extend Forwardable, Util\n      include Attribs.new(:name, :block, i"
  },
  {
    "path": "yaks/lib/yaks/mapper/config.rb",
    "chars": 302,
    "preview": "module Yaks\n  class Mapper\n    class Config\n      include Attribs.new(\n                type: nil, attributes: [], links:"
  },
  {
    "path": "yaks/lib/yaks/mapper/form/config.rb",
    "chars": 1786,
    "preview": "module Yaks\n  class Mapper\n    class Form\n      class Config\n        include Attribs.new(\n                  name: nil,\n "
  },
  {
    "path": "yaks/lib/yaks/mapper/form/dynamic_field.rb",
    "chars": 354,
    "preview": "module Yaks\n  class Mapper\n    class Form\n      class DynamicField\n        include Attribs.new(:block)\n\n        def self"
  },
  {
    "path": "yaks/lib/yaks/mapper/form/field/option.rb",
    "chars": 750,
    "preview": "module Yaks\n  class Mapper\n    class Form\n      class Field\n        # <option>, as used in a <select>\n        class Opti"
  },
  {
    "path": "yaks/lib/yaks/mapper/form/field.rb",
    "chars": 1846,
    "preview": "module Yaks\n  class Mapper\n    class Form\n      class Field\n        include Attribs.new(\n                  :name,\n      "
  },
  {
    "path": "yaks/lib/yaks/mapper/form/fieldset.rb",
    "chars": 514,
    "preview": "module Yaks\n  class Mapper\n    class Form\n      class Fieldset\n        extend Forwardable\n        include Attribs.new(:c"
  },
  {
    "path": "yaks/lib/yaks/mapper/form/legend.rb",
    "chars": 451,
    "preview": "module Yaks\n  class Mapper\n    class Form\n      class Legend\n        include Attribs.new(:type, :label, if: nil)\n\n      "
  },
  {
    "path": "yaks/lib/yaks/mapper/form.rb",
    "chars": 1071,
    "preview": "module Yaks\n  class Mapper\n    class Form\n      extend Forwardable, Util\n\n      def_delegators :config, :name, :action, "
  },
  {
    "path": "yaks/lib/yaks/mapper/has_many.rb",
    "chars": 841,
    "preview": "module Yaks\n  class Mapper\n    class HasMany < Association\n      include Util,\n              attributes.add(collection_m"
  },
  {
    "path": "yaks/lib/yaks/mapper/has_one.rb",
    "chars": 286,
    "preview": "module Yaks\n  class Mapper\n    class HasOne < Association\n      def map_resource(object, context)\n        resolve_associ"
  },
  {
    "path": "yaks/lib/yaks/mapper/link.rb",
    "chars": 3308,
    "preview": "module Yaks\n  class Mapper\n    # A Yaks::Mapper::Link is part of a mapper's configuration. It captures\n    # what is set"
  },
  {
    "path": "yaks/lib/yaks/mapper.rb",
    "chars": 2832,
    "preview": "module Yaks\n  class Mapper\n    extend Configurable\n\n    def_set :type\n\n    def_forward attributes: :add_attributes\n    d"
  },
  {
    "path": "yaks/lib/yaks/null_resource.rb",
    "chars": 1266,
    "preview": "module Yaks\n  class NullResource < Resource\n    include attributes.add(collection: false),\n            Equalizer.new(:re"
  },
  {
    "path": "yaks/lib/yaks/pipeline.rb",
    "chars": 1139,
    "preview": "module Yaks\n  class Pipeline\n    include Concord.new(:steps)\n\n    def call(input, env)\n      steps.inject(input) {|memo,"
  },
  {
    "path": "yaks/lib/yaks/primitivize.rb",
    "chars": 1019,
    "preview": "module Yaks\n  class Primitivize\n    attr_reader :mappings\n\n    def initialize\n      @mappings = {}\n    end\n\n    def call"
  },
  {
    "path": "yaks/lib/yaks/reader/hal.rb",
    "chars": 1720,
    "preview": "module Yaks\n  module Reader\n    class Hal\n      include Util\n\n      def call(parsed_json, _env = {})\n        attributes "
  },
  {
    "path": "yaks/lib/yaks/reader/json_api.rb",
    "chars": 2698,
    "preview": "module Yaks\n  module Reader\n    class JsonAPI\n      def call(parsed_json, _env = {})\n        included = parsed_json['inc"
  },
  {
    "path": "yaks/lib/yaks/resource/form/field/option.rb",
    "chars": 203,
    "preview": "module Yaks\n  class Resource\n    class Form\n      class Field\n        class Option\n          include Attribs.new(:value,"
  },
  {
    "path": "yaks/lib/yaks/resource/form/field.rb",
    "chars": 1175,
    "preview": "module Yaks\n  class Resource\n    class Form\n      class Field\n        include Yaks::Mapper::Form::Field.attributes.add(e"
  },
  {
    "path": "yaks/lib/yaks/resource/form/fieldset.rb",
    "chars": 222,
    "preview": "module Yaks\n  class Resource\n    class Form\n      class Fieldset\n        include Attribs.new(:fields)\n        include Ya"
  },
  {
    "path": "yaks/lib/yaks/resource/form/legend.rb",
    "chars": 413,
    "preview": "module Yaks\n  class Resource\n    class Form\n      class Legend\n        include Attribs.new(:label, :type)\n\n        def i"
  },
  {
    "path": "yaks/lib/yaks/resource/form.rb",
    "chars": 608,
    "preview": "module Yaks\n  class Resource\n    class Form\n      include Yaks::Mapper::Form::Config.attributes.remove(:dynamic_blocks)\n"
  },
  {
    "path": "yaks/lib/yaks/resource/has_fields.rb",
    "chars": 497,
    "preview": "module Yaks\n  class Resource\n    module HasFields\n      def map_fields(&block)\n        with(\n          fields: fields_fl"
  },
  {
    "path": "yaks/lib/yaks/resource/link.rb",
    "chars": 301,
    "preview": "module Yaks\n  class Resource\n    class Link\n      include Attribs.new(:rel, :uri, options: {}.freeze)\n\n      def title\n "
  },
  {
    "path": "yaks/lib/yaks/resource.rb",
    "chars": 2207,
    "preview": "module Yaks\n  class Resource\n    include Attribs.new(\n              type: nil,\n              rels: [],\n              lin"
  },
  {
    "path": "yaks/lib/yaks/runner.rb",
    "chars": 2165,
    "preview": "module Yaks\n  class Runner\n    include Util\n    include Anima.new(:object, :config, :options)\n    include Adamantium::Fl"
  },
  {
    "path": "yaks/lib/yaks/serializer.rb",
    "chars": 748,
    "preview": "module Yaks\n  module Serializer\n    def self.register(format, serializer)\n      raise \"Serializer for #{format} already "
  },
  {
    "path": "yaks/lib/yaks/util.rb",
    "chars": 2223,
    "preview": "module Yaks\n  module Util\n    extend self\n    extend Forwardable\n\n    def_delegators Inflection, :singular, :singularize"
  },
  {
    "path": "yaks/lib/yaks/version.rb",
    "chars": 37,
    "preview": "module Yaks\n  VERSION = '0.13.0'\nend\n"
  },
  {
    "path": "yaks/lib/yaks.rb",
    "chars": 2275,
    "preview": "require 'forwardable'\nrequire 'set'\nrequire 'pathname'\nrequire 'json'\nrequire 'csv'\n\nrequire 'concord'\nrequire 'attribs'"
  },
  {
    "path": "yaks/spec/acceptance/acceptance_spec.rb",
    "chars": 2111,
    "preview": "require 'acceptance/models'\nrequire 'acceptance/json_shared_examples'\n\nRSpec.describe Yaks::Format::Hal do\n  let(:format"
  },
  {
    "path": "yaks/spec/acceptance/json_shared_examples.rb",
    "chars": 1832,
    "preview": "RSpec.shared_examples_for 'JSON Writer' do |fixture_name|\n  describe 'Yaks::Resource => JSON' do\n    let(:object) { load"
  },
  {
    "path": "yaks/spec/acceptance/models.rb",
    "chars": 1424,
    "preview": "require 'anima'\n\nclass Scholar\n  include Anima.new(:id, :name, :pinyin, :latinized, :works)\nend\n\nclass Work\n  include An"
  },
  {
    "path": "yaks/spec/fixture_helpers.rb",
    "chars": 280,
    "preview": "require 'yaml'\nrequire 'json'\n\nmodule FixtureHelpers\n  module_function\n\n  def load_json_fixture(name)\n    JSON.parse(Yak"
  },
  {
    "path": "yaks/spec/integration/dynamic_form_fields_spec.rb",
    "chars": 882,
    "preview": "RSpec.describe 'dynamic form fields' do\n  let(:mapper) do\n    Class.new(Yaks::Mapper) do\n      type :awesome\n      form "
  },
  {
    "path": "yaks/spec/integration/fieldset_spec.rb",
    "chars": 1407,
    "preview": "RSpec.describe 'dynamic form fields' do\n  let(:mapper) do\n    Class.new(Yaks::Mapper) do\n      type :awesome\n      form "
  },
  {
    "path": "yaks/spec/integration/map_to_resource_spec.rb",
    "chars": 1700,
    "preview": "RSpec.describe 'Mapping domain models to Resource objects' do\n  include_context 'fixtures'\n  include_context 'yaks conte"
  },
  {
    "path": "yaks/spec/json/confucius.collection_json.json",
    "chars": 833,
    "preview": "{\n  \"collection\": {\n    \"version\": \"1.0\",\n    \"href\": \"http://literature.example.com/authors/kongzi\",\n    \"items\": [\n   "
  },
  {
    "path": "yaks/spec/json/confucius.hal.json",
    "chars": 1716,
    "preview": "{\n  \"id\": 9,\n  \"name\": \"孔子\",\n  \"pinyin\": \"Kongzi\",\n  \"latinized\": \"Confucius\",\n  \"_links\": {\n    \"self\":    { \"href\": \"h"
  },
  {
    "path": "yaks/spec/json/confucius.halo.json",
    "chars": 2111,
    "preview": "{\n  \"id\": 9,\n  \"name\": \"孔子\",\n  \"pinyin\": \"Kongzi\",\n  \"latinized\": \"Confucius\",\n  \"_links\": {\n    \"self\":    { \"href\": \"h"
  },
  {
    "path": "yaks/spec/json/confucius.json_api.json",
    "chars": 1967,
    "preview": "{\n  \"data\": {\n    \"id\": \"9\",\n    \"type\": \"scholars\",\n    \"attributes\": {\n      \"name\": \"孔子\",\n      \"pinyin\": \"Kongzi\",\n "
  },
  {
    "path": "yaks/spec/json/john.hal.json",
    "chars": 483,
    "preview": "{\n  \"id\": 1,\n  \"name\": \"john\",\n  \"_links\": {\n    \"copyright\": [\n      {\n        \"href\": \"/api/copyright/2024\"\n      }\n  "
  },
  {
    "path": "yaks/spec/json/list_of_quotes.collection_json.json",
    "chars": 706,
    "preview": "{\n  \"collection\": {\n    \"version\": \"1.0\",\n    \"items\": [\n      {\n        \"data\": [\n          {\n            \"name\": \"id\","
  },
  {
    "path": "yaks/spec/json/list_of_quotes.hal.json",
    "chars": 262,
    "preview": "{\n  \"_embedded\": {\n    \"rel:quotes\": [\n      {\n        \"id\": 17,\n        \"chinese\": \"廄焚。子退朝,曰:“傷人乎?” 不問馬。\"\n      },\n    "
  },
  {
    "path": "yaks/spec/json/list_of_quotes.json_api.json",
    "chars": 382,
    "preview": " {\n  \"data\": [\n    {\n      \"type\": \"quotes\",\n      \"id\": \"17\",\n      \"attributes\": {\n        \"chinese\": \"廄焚。子退朝,曰:“傷人乎?”"
  },
  {
    "path": "yaks/spec/json/plant_collection.collection.json",
    "chars": 1179,
    "preview": "{\n  \"collection\": {\n    \"version\": \"1.0\",\n    \"href\": \"http://api.example.com/plants\",\n    \"items\": [\n      {\n        \"h"
  },
  {
    "path": "yaks/spec/json/plant_collection.hal.json",
    "chars": 1086,
    "preview": "{\n   \"_links\" : {\n      \"profile\" : { \"href\" : \"http://api.example.com/doc/plant_collection\" },\n      \"self\" : { \"href\" "
  },
  {
    "path": "yaks/spec/json/youtypeitwepostit.collection_json.json",
    "chars": 1079,
    "preview": "{\n  \"collection\": {\n    \"version\": \"1.0\",\n    \"href\": \"http://www.youtypeitwepostit.com/api/\",\n\n    \"links\": [\n      { \""
  },
  {
    "path": "yaks/spec/sanity_spec.rb",
    "chars": 516,
    "preview": "RSpec.describe 'assorted sanity checks' do\n  let(:resource_methods)            { Yaks::Resource.public_instance_methods."
  },
  {
    "path": "yaks/spec/spec_helper.rb",
    "chars": 670,
    "preview": "require 'yaks'\nrequire 'yaks-html'\nrequire 'virtus'\n\nrequire_relative '../../shared/rspec_config'\n\nrequire 'fixture_help"
  },
  {
    "path": "yaks/spec/support/classes_for_policy_testing.rb",
    "chars": 1203,
    "preview": "# Used by Yaks::DefaultPolicy* tests to test various name inference schemes\n\nclass SoyMapper; end\nclass Soy; end\nclass W"
  },
  {
    "path": "yaks/spec/support/deep_eql.rb",
    "chars": 3875,
    "preview": "# When comparing deep nested structures, it can be really hard to figure out what\n# the actual differences are looking a"
  },
  {
    "path": "yaks/spec/support/fixtures.rb",
    "chars": 451,
    "preview": "RSpec.shared_context 'fixtures' do\n  let(:john)       { Friend.new(id: 1, name: 'john', pets: [boingboing, wassup], pet_"
  },
  {
    "path": "yaks/spec/support/friends_mapper.rb",
    "chars": 1365,
    "preview": "class FriendMapper < Yaks::Mapper\n  attributes :id, :name\n\n  link :copyright, '/api/copyright/{year}'\n\n  def year\n    20"
  },
  {
    "path": "yaks/spec/support/models.rb",
    "chars": 369,
    "preview": "class Pet\n  include Virtus.model\n\n  attribute :id, Integer\n  attribute :name, String\n  attribute :species, String\nend\n\nc"
  },
  {
    "path": "yaks/spec/support/pet_mapper.rb",
    "chars": 161,
    "preview": "class PetMapper < Yaks::Mapper\n  attributes :id, :name, :species\nend\nclass GreatPetMapper < Yaks::Mapper; end\nclass Grea"
  },
  {
    "path": "yaks/spec/support/pet_peeve_mapper.rb",
    "chars": 64,
    "preview": "class PetPeeveMapper < Yaks::Mapper\n  attributes :id, :type\nend\n"
  },
  {
    "path": "yaks/spec/support/shared_contexts.rb",
    "chars": 1827,
    "preview": "RSpec.shared_context 'collection resource' do\n  let(:resource) do\n    Yaks::CollectionResource.new(\n      links: links,\n"
  },
  {
    "path": "yaks/spec/support/youtypeit_models_mappers.rb",
    "chars": 491,
    "preview": "module Youtypeitwepostit\n  class Message\n    include Virtus.model\n\n    attribute :id, Integer\n    attribute :text, Strin"
  },
  {
    "path": "yaks/spec/unit/yaks/behaviour/optional_includes_spec.rb",
    "chars": 2262,
    "preview": "require \"yaks/behaviour/optional_includes\"\n\nRSpec.describe Yaks::Behaviour::OptionalIncludes do\n  include_context 'yaks "
  },
  {
    "path": "yaks/spec/unit/yaks/builder_spec.rb",
    "chars": 2171,
    "preview": "RSpec.describe Yaks::Builder do\n  class Buildable\n    include Attribs.new(:foo, :bar)\n\n    def self.create(foo, bar)\n   "
  },
  {
    "path": "yaks/spec/unit/yaks/collection_mapper_spec.rb",
    "chars": 4973,
    "preview": "RSpec.describe Yaks::CollectionMapper, '#call' do\n  include_context 'fixtures'\n\n  subject(:mapper) { mapper_class.new(co"
  },
  {
    "path": "yaks/spec/unit/yaks/collection_resource_spec.rb",
    "chars": 2171,
    "preview": "RSpec.describe Yaks::CollectionResource do\n  subject(:collection) { described_class.new(init_opts) }\n  let(:init_opts) {"
  },
  {
    "path": "yaks/spec/unit/yaks/config_spec.rb",
    "chars": 7077,
    "preview": "RSpec.describe Yaks::Config do\n  include_context 'fixtures'\n\n  def self.configure(&blk)\n    subject(:config) { Yaks::Con"
  },
  {
    "path": "yaks/spec/unit/yaks/configurable_spec.rb",
    "chars": 4238,
    "preview": "require 'securerandom'\n\nclass Kitten\n  include Attribs.new(:furriness)\n\n  def self.create(opts, &block)\n    level = opts"
  },
  {
    "path": "yaks/spec/unit/yaks/default_policy/derive_mapper_from_collection_spec.rb",
    "chars": 1618,
    "preview": "RSpec.describe Yaks::DefaultPolicy, '#derive_mapper_from_collection' do\n  subject(:policy) { described_class.new(options"
  },
  {
    "path": "yaks/spec/unit/yaks/default_policy/derive_mapper_from_item_spec.rb",
    "chars": 3998,
    "preview": "RSpec.describe Yaks::DefaultPolicy, '#derive_mapper_from_item' do\n  subject(:policy) { described_class.new(options) }\n\n "
  },
  {
    "path": "yaks/spec/unit/yaks/default_policy/derive_mapper_from_object_spec.rb",
    "chars": 1651,
    "preview": "RSpec.describe Yaks::DefaultPolicy, '#derive_mapper_from_object' do\n  subject(:policy) { described_class.new }\n\n  contex"
  },
  {
    "path": "yaks/spec/unit/yaks/default_policy_spec.rb",
    "chars": 1682,
    "preview": "RSpec.describe Yaks::DefaultPolicy do\n  subject(:policy) { described_class.new(options) }\n\n  let(:options) { {} }\n  let("
  },
  {
    "path": "yaks/spec/unit/yaks/format/collection_json_spec.rb",
    "chars": 10801,
    "preview": "RSpec.describe Yaks::Format::CollectionJson do\n  context 'with the plant collection resource' do\n    include_context 'pl"
  },
  {
    "path": "yaks/spec/unit/yaks/format/hal_spec.rb",
    "chars": 1085,
    "preview": "RSpec.describe Yaks::Format::Hal do\n  context 'with the plant collection resource' do\n    include_context 'plant collect"
  },
  {
    "path": "yaks/spec/unit/yaks/format/halo_spec.rb",
    "chars": 41,
    "preview": "RSpec.describe Yaks::Format::Halo do\nend\n"
  },
  {
    "path": "yaks/spec/unit/yaks/format/html_spec.rb",
    "chars": 46,
    "preview": "# RSpec.describe Yaks::Format::HTML do\n\n# end\n"
  },
  {
    "path": "yaks/spec/unit/yaks/format/json_api_spec.rb",
    "chars": 5893,
    "preview": "# Mainly tested through the acceptance tests, here covering a few specific edge cases\nRSpec.describe Yaks::Format::JsonA"
  },
  {
    "path": "yaks/spec/unit/yaks/format_spec.rb",
    "chars": 1850,
    "preview": "RSpec.describe Yaks::Format do\n  describe '.by_name' do\n    specify do\n      expect(Yaks::Format.by_name(:hal)).to eql Y"
  },
  {
    "path": "yaks/spec/unit/yaks/fp/callable_spec.rb",
    "chars": 271,
    "preview": "RSpec.describe Yaks::FP::Callable, \"#to_proc\" do\n  it 'should delegate to_proc to method(:call)' do\n    obj = Class.new "
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/association_mapper_spec.rb",
    "chars": 2497,
    "preview": "RSpec.describe Yaks::Mapper::AssociationMapper do\n  include_context 'yaks context'\n\n  subject(:association_mapper) {\n   "
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/association_spec.rb",
    "chars": 5474,
    "preview": "RSpec.describe Yaks::Mapper::Association do\n  include_context 'yaks context'\n\n  let(:association_class) {\n    Class.new("
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/attribute_spec.rb",
    "chars": 4370,
    "preview": "RSpec.describe Yaks::Mapper::Attribute do\n  include_context 'yaks context'\n\n  let(:attribute_with_block) { described_cla"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/config_spec.rb",
    "chars": 350,
    "preview": "RSpec.describe Yaks::Mapper::Config do\n  describe '#add_attributes' do\n    it 'should add attributes' do\n      expect(su"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form/config_spec.rb",
    "chars": 2302,
    "preview": "RSpec.describe Yaks::Mapper::Form::Config do\n  let(:config) { described_class.new }\n\n  describe \".create\" do\n    let(:co"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form/dynamic_field_spec.rb",
    "chars": 883,
    "preview": "RSpec.describe Yaks::Mapper::Form::DynamicField do\n  describe \".create\" do\n    it \"take a block\" do\n      expect(describ"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form/field/option_spec.rb",
    "chars": 1528,
    "preview": "RSpec.describe Yaks::Mapper::Form::Field::Option do\n  include_context \"yaks context\"\n\n  let(:mapper_class) do\n    Class."
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form/field_spec.rb",
    "chars": 4137,
    "preview": "RSpec.describe Yaks::Mapper::Form::Field do\n  include_context 'yaks context'\n\n  let(:field)     { described_class.new(fu"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form/fieldset_spec.rb",
    "chars": 2316,
    "preview": "RSpec.describe Yaks::Mapper::Form::Fieldset do\n  include_context 'yaks context'\n  let(:mapper) { Yaks::Mapper.new(yaks_c"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form/legend_spec.rb",
    "chars": 1664,
    "preview": "RSpec.describe Yaks::Mapper::Form::Legend do\n  subject(:legend) { described_class.create(\"a legend\") }\n\n  describe \".cre"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/form_spec.rb",
    "chars": 3707,
    "preview": "RSpec.describe Yaks::Mapper::Form do\n  include_context 'yaks context'\n\n  let(:form) { described_class.create(*full_args,"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/has_many_spec.rb",
    "chars": 3034,
    "preview": "RSpec.describe Yaks::Mapper::HasMany do\n  include_context 'yaks context'\n\n  let(:closet_mapper) { closet_mapper_class.ne"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/has_one_spec.rb",
    "chars": 1547,
    "preview": "RSpec.describe Yaks::Mapper::HasOne do\n  include_context 'yaks context'\n\n  AuthorMapper = Class.new(Yaks::Mapper) { attr"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper/link_spec.rb",
    "chars": 8153,
    "preview": "RSpec.describe Yaks::Mapper::Link do\n  include_context 'yaks context'\n\n  subject(:link) { described_class.create(rel, te"
  },
  {
    "path": "yaks/spec/unit/yaks/mapper_spec.rb",
    "chars": 12840,
    "preview": "RSpec.describe Yaks::Mapper do\n  include_context 'yaks context'\n\n  subject(:mapper)   { mapper_class.new(yaks_context) }"
  },
  {
    "path": "yaks/spec/unit/yaks/null_resource_spec.rb",
    "chars": 3279,
    "preview": "RSpec.describe Yaks::NullResource do\n  subject(:null_resource) { described_class.new }\n\n  describe '#initialize' do\n    "
  },
  {
    "path": "yaks/spec/unit/yaks/pipeline_spec.rb",
    "chars": 4646,
    "preview": "RSpec.describe Yaks::Pipeline do\n  subject(:pipeline) { described_class.new(steps) }\n  let(:steps) {\n    [\n      [:step1"
  },
  {
    "path": "yaks/spec/unit/yaks/primitivize_spec.rb",
    "chars": 2365,
    "preview": "RSpec.describe Yaks::Primitivize do\n  subject(:primitivizer) { described_class.create }\n\n  describe \"#initialize\" do\n   "
  },
  {
    "path": "yaks/spec/unit/yaks/resource/form/field_spec.rb",
    "chars": 2954,
    "preview": "RSpec.describe Yaks::Resource::Form::Field do\n  subject do\n    described_class.new(type: 'text', name: 'foo', value: 123"
  },
  {
    "path": "yaks/spec/unit/yaks/resource/form/fieldset_spec.rb",
    "chars": 181,
    "preview": "RSpec.describe Yaks::Resource::Form::Fieldset do\n  subject(:fieldset) { described_class.new(fields: []) }\n\n  describe '#"
  },
  {
    "path": "yaks/spec/unit/yaks/resource/form/legend_spec.rb",
    "chars": 230,
    "preview": "RSpec.describe Yaks::Resource::Form::Legend do\n  subject(:legend) { described_class.new(label: 'a legend') }\n\n  describe"
  },
  {
    "path": "yaks/spec/unit/yaks/resource/form_spec.rb",
    "chars": 2019,
    "preview": "RSpec.describe Yaks::Resource::Form do\n  let(:fields) {\n    [\n      Yaks::Resource::Form::Field.new(name: :foo, value: '"
  },
  {
    "path": "yaks/spec/unit/yaks/resource/has_fields_spec.rb",
    "chars": 3617,
    "preview": "RSpec.describe Yaks::Resource::HasFields do\n  let(:class_with_fields) do\n    Class.new do\n      include Yaks::Resource::"
  },
  {
    "path": "yaks/spec/unit/yaks/resource/link_spec.rb",
    "chars": 994,
    "preview": "RSpec.describe Yaks::Resource::Link do\n  subject(:link) { described_class.new(rel: rel, uri: uri, options: options) }\n\n "
  },
  {
    "path": "yaks/spec/unit/yaks/resource_spec.rb",
    "chars": 4439,
    "preview": "RSpec.describe Yaks::Resource do\n  subject(:resource) { described_class.new(init_opts) }\n  let(:init_opts) { {} }\n\n  des"
  },
  {
    "path": "yaks/spec/unit/yaks/runner_spec.rb",
    "chars": 9555,
    "preview": "RSpec.describe Yaks::Runner do\n  subject(:runner) {\n    described_class.new(object: object, config: config, options: opt"
  },
  {
    "path": "yaks/spec/unit/yaks/serializer_spec.rb",
    "chars": 628,
    "preview": "RSpec.describe Yaks::Serializer do\n  after do\n    Yaks::Serializer.instance_variable_set(\"@serializers\", nil)\n  end\n\n  i"
  },
  {
    "path": "yaks/spec/unit/yaks/util_spec.rb",
    "chars": 3223,
    "preview": "RSpec.describe Yaks::Util do\n  include Yaks::Util\n\n  describe '#Resolve' do\n    it 'should return non-proc-values' do\n  "
  },
  {
    "path": "yaks/spec/yaml/confucius.yaml",
    "chars": 928,
    "preview": "--- !ruby/object:Scholar\nid: 9\nname: \"孔子\"\npinyin: \"Kongzi\"\nlatinized: \"Confucius\"\nworks:\n  - !ruby/object:Work\n    id: 1"
  },
  {
    "path": "yaks/spec/yaml/list_of_quotes.yaml",
    "chars": 606,
    "preview": "- !ruby/object:Quote\n  id: 17\n  chinese: \"廄焚。子退朝,曰:“傷人乎?” 不問馬。\"\n  english: \"When the stables were burnt down, on returni"
  },
  {
    "path": "yaks/spec/yaml/youtypeitwepostit.yaml",
    "chars": 253,
    "preview": "---\n- !ruby/object:Youtypeitwepostit::Message\n  id: 12091295723803341\n  text: \"massage\"\n  date_posted: \"2014-05-29T07:56"
  },
  {
    "path": "yaks/yaks.gemspec",
    "chars": 1949,
    "preview": "# encoding: utf-8\n\nrequire 'English'\nrequire File.expand_path('../lib/yaks/version', __FILE__)\nrequire File.expand_path("
  },
  {
    "path": "yaks-html/README.md",
    "chars": 1265,
    "preview": "A HTML output format for Yaks.\n\nBrowse your hypermedia API like a good old fashioned web site.\n\nYou can see an example o"
  },
  {
    "path": "yaks-html/Rakefile",
    "chars": 56,
    "preview": "load '../shared/rake_tasks.rb'\n\ngem_tasks(:\"yaks-html\")\n"
  },
  {
    "path": "yaks-html/lib/yaks/format/html.rb",
    "chars": 5696,
    "preview": "# -*- coding: utf-8 -*-\n\nmodule Yaks\n  class Format\n    class HTML < self\n      include Util\n\n      register :html, :htm"
  },
  {
    "path": "yaks-html/lib/yaks/format/template.html",
    "chars": 3668,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n   "
  },
  {
    "path": "yaks-html/lib/yaks-html/rspec.rb",
    "chars": 2102,
    "preview": "require 'yaks-html'\nrequire 'capybara/rspec'\n\nmodule YaksHTML\n  # This is a bit of a hack. The only way to add custom ra"
  },
  {
    "path": "yaks-html/lib/yaks-html.rb",
    "chars": 210,
    "preview": "require 'hexp'\nrequire 'yaks'\nrequire 'yaks/format/html'\n\n-> do\n  unparser = Hexp::Unparser.new(no_escape: [:script, :st"
  },
  {
    "path": "yaks-html/spec/smoke_test_spec.rb",
    "chars": 766,
    "preview": "require 'spec_helper'\nrequire_relative 'support/test_app'\n\nRSpec.describe Yaks::Format::HTML, type: :yaks_integration do"
  },
  {
    "path": "yaks-html/spec/spec_helper.rb",
    "chars": 110,
    "preview": "require 'yaks'\nrequire 'yaks-html/rspec'\nrequire 'yaks-sinatra'\n\nrequire_relative '../../shared/rspec_config'\n"
  },
  {
    "path": "yaks-html/spec/support/test_app.rb",
    "chars": 849,
    "preview": "class TestApp < Sinatra::Base\n  register Yaks::Sinatra\n\n  class HomeMapper < Yaks::Mapper\n    link 'http://myapi.example"
  },
  {
    "path": "yaks-html/yaks-html.gemspec",
    "chars": 981,
    "preview": "# encoding: utf-8\n\nrequire 'English'\nrequire File.expand_path('../../yaks/lib/yaks/version', __FILE__)\n\nGem::Specificati"
  },
  {
    "path": "yaks-sinatra/.rspec",
    "chars": 14,
    "preview": "-r spec_helper"
  },
  {
    "path": "yaks-sinatra/README.md",
    "chars": 996,
    "preview": "Provide basic integration for using Yaks in sinatra. It gives you a top level `configure_yaks` method, and a `yaks` help"
  },
  {
    "path": "yaks-sinatra/Rakefile",
    "chars": 59,
    "preview": "load '../shared/rake_tasks.rb'\n\ngem_tasks(:\"yaks-sinatra\")\n"
  },
  {
    "path": "yaks-sinatra/lib/yaks-sinatra.rb",
    "chars": 867,
    "preview": "require 'sinatra/base'\nrequire 'yaks'\n\nmodule Yaks\n  module Sinatra\n    class << self\n      attr_accessor :yaks_config\n "
  },
  {
    "path": "yaks-sinatra/spec/integration/classic_app.rb",
    "chars": 240,
    "preview": "require 'sinatra'\nrequire 'yaks-sinatra'\n\nRoot = Class.new(Struct.new(:name))\n\nclass RootMapper < Yaks::Mapper\n  link :s"
  },
  {
    "path": "yaks-sinatra/spec/integration/classic_spec.rb",
    "chars": 696,
    "preview": "require 'integration_helper'\nrequire_relative 'classic_app.rb'\n\nRSpec.describe 'Sinatra Classic app integration', type: "
  },
  {
    "path": "yaks-sinatra/spec/integration/modular_spec.rb",
    "chars": 662,
    "preview": "require 'integration_helper'\n\nRSpec.describe 'Sinatra Modular app integration', type: :integration do\n  include Yaks::Si"
  },
  {
    "path": "yaks-sinatra/spec/integration_helper.rb",
    "chars": 2469,
    "preview": "# encoding: utf-8\n\nrequire 'spec_helper'\nrequire 'yaks-sinatra'\nrequire 'rack/test'\n\nENV['RACK_ENV'] = 'test'\n\nclass Med"
  },
  {
    "path": "yaks-sinatra/spec/spec_helper.rb",
    "chars": 45,
    "preview": "require_relative '../../shared/rspec_config'\n"
  },
  {
    "path": "yaks-sinatra/yaks-sinatra.gemspec",
    "chars": 863,
    "preview": "# encoding: utf-8\n\nrequire 'English'\nrequire File.expand_path('../../yaks/lib/yaks/version', __FILE__)\n\nGem::Specificati"
  },
  {
    "path": "yaks-transit/README.md",
    "chars": 315,
    "preview": "# Yaks Transit\n\nMap the Yaks data model directly to Transit.\n\nTransit is a bit different than other formats like HAL,\nCo"
  },
  {
    "path": "yaks-transit/lib/yaks-transit.rb",
    "chars": 1144,
    "preview": "require 'yaks'\nrequire 'transit'\n\nYaks::Serializer.register(:transit, ->(i, _env = {}) {i})\n\nmodule Yaks\n  class Format\n"
  },
  {
    "path": "yaks-transit/yaks-transit.gemspec",
    "chars": 806,
    "preview": "# encoding: utf-8\n\nrequire 'English'\nrequire File.expand_path('../../yaks/lib/yaks/version', __FILE__)\n\nGem::Specificati"
  }
]

About this extraction

This page contains the full source code of the plexus/yaks GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 174 files (364.3 KB), approximately 103.2k tokens, and a symbol index with 576 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!