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]
[][travis]
[][codeclimate]
[][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)

================================================
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": [
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
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]\n[\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.