yaks 0.8.3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +12 -0
- data/lib/yaks.rb +4 -2
- data/lib/yaks/attributes.rb +1 -1
- data/lib/yaks/builder.rb +10 -10
- data/lib/yaks/collection_mapper.rb +1 -1
- data/lib/yaks/config.rb +1 -1
- data/lib/yaks/configurable.rb +41 -2
- data/lib/yaks/format/json_api.rb +21 -35
- data/lib/yaks/mapper.rb +2 -1
- data/lib/yaks/mapper/form.rb +6 -29
- data/lib/yaks/mapper/form/config.rb +37 -3
- data/lib/yaks/mapper/form/dynamic_field.rb +17 -0
- data/lib/yaks/mapper/form/field.rb +13 -10
- data/lib/yaks/mapper/form/field/option.rb +2 -1
- data/lib/yaks/mapper/form/fieldset.rb +7 -29
- data/lib/yaks/reader/hal.rb +0 -1
- data/lib/yaks/reader/json_api.rb +49 -0
- data/lib/yaks/version.rb +1 -1
- data/spec/acceptance/acceptance_spec.rb +1 -0
- data/spec/json/confucius.json_api.json +19 -20
- data/spec/unit/yaks/attributes_spec.rb +37 -1
- data/spec/unit/yaks/builder_spec.rb +34 -3
- data/spec/unit/yaks/collection_mapper_spec.rb +20 -1
- data/spec/unit/yaks/collection_resource_spec.rb +7 -0
- data/spec/unit/yaks/configurable_spec.rb +84 -0
- data/spec/unit/yaks/format/json_api_spec.rb +91 -2
- data/spec/unit/yaks/mapper/form/field_spec.rb +63 -5
- data/spec/unit/yaks/mapper/form/fieldset_spec.rb +30 -0
- data/spec/unit/yaks/mapper/form_spec.rb +25 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e1351b2c236dc9fb68ff5bbe9c365f7addb38cad
|
4
|
+
data.tar.gz: d8baafb1e55bc4bf5e926e012dac9bd20d7284f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f5732d430846954a651d3aff1145cf07610098d23c0ba2b28bfe7626b3e3c2c3c741d6f50d84459360a1986d820f10604d115bd3336ccad6dac2386bf808142
|
7
|
+
data.tar.gz: 7ce7ea4c6bdf3c0e0fc2ae11ecee61813c4be3b8eefe01316153ba427d6522496f2d92e3efcbf2c29f60bea9db3a93b158caf4d667472835438c284ae14df08f
|
data/README.md
CHANGED
@@ -12,8 +12,15 @@
|
|
12
12
|
|
13
13
|
# Yaks
|
14
14
|
|
15
|
+

|
16
|
+
|
15
17
|
The library that understands hypermedia.
|
16
18
|
|
19
|
+
Yaks takes your data and transforms it into hypermedia formats such as
|
20
|
+
HAL, JSON-API, or HTML. It allows you to build APIs that are
|
21
|
+
discoverable and browsable. It is built from the ground up around
|
22
|
+
linked resources, a concept central to the architecture of the web.
|
23
|
+
|
17
24
|
Yaks consists of a resource representation that is independent of any
|
18
25
|
output type. A Yaks mapper transforms an object into a resource, which
|
19
26
|
can then be serialized into whichever output format the client
|
@@ -482,6 +489,11 @@ implemented. It is also not the most suitable format for Yaks
|
|
482
489
|
feature-set due to its strong convention-driven nature and weak
|
483
490
|
support for hypermedia.
|
484
491
|
|
492
|
+
At this time, The JSON-API specification has not reached a 1.0 release.
|
493
|
+
Some changes to the Yaks JSON-API formatter may still be required
|
494
|
+
before it is completely compatible with the latest version of the
|
495
|
+
specification.
|
496
|
+
|
485
497
|
If you would like to see better JSON-API support, get in touch. We
|
486
498
|
might be able to work something out.
|
487
499
|
|
data/lib/yaks.rb
CHANGED
@@ -12,11 +12,11 @@ require 'rack/accept'
|
|
12
12
|
|
13
13
|
require 'yaks/version'
|
14
14
|
require 'yaks/util'
|
15
|
+
require 'yaks/attributes'
|
15
16
|
require 'yaks/configurable'
|
16
17
|
require 'yaks/fp'
|
17
18
|
require 'yaks/fp/callable'
|
18
19
|
require 'yaks/primitivize'
|
19
|
-
require 'yaks/attributes'
|
20
20
|
require 'yaks/builder'
|
21
21
|
require 'yaks/errors'
|
22
22
|
|
@@ -75,10 +75,11 @@ require 'yaks/mapper/has_one'
|
|
75
75
|
require 'yaks/mapper/has_many'
|
76
76
|
require 'yaks/mapper/attribute'
|
77
77
|
require 'yaks/mapper/link'
|
78
|
-
require 'yaks/mapper/form/config'
|
79
78
|
require 'yaks/mapper/form/field/option'
|
80
79
|
require 'yaks/mapper/form/field'
|
81
80
|
require 'yaks/mapper/form/fieldset'
|
81
|
+
require 'yaks/mapper/form/dynamic_field'
|
82
|
+
require 'yaks/mapper/form/config'
|
82
83
|
require 'yaks/mapper/form'
|
83
84
|
require 'yaks/mapper/config'
|
84
85
|
require 'yaks/mapper'
|
@@ -98,5 +99,6 @@ require 'yaks/format/json_api'
|
|
98
99
|
require 'yaks/format/collection_json'
|
99
100
|
|
100
101
|
require 'yaks/reader/hal'
|
102
|
+
require 'yaks/reader/json_api'
|
101
103
|
require 'yaks/pipeline'
|
102
104
|
require 'yaks/runner'
|
data/lib/yaks/attributes.rb
CHANGED
data/lib/yaks/builder.rb
CHANGED
@@ -7,8 +7,8 @@ module Yaks
|
|
7
7
|
#
|
8
8
|
# # This code
|
9
9
|
# Form.create(:search)
|
10
|
-
#
|
11
|
-
#
|
10
|
+
# .method("POST")
|
11
|
+
# .action("/search")
|
12
12
|
#
|
13
13
|
# # Can be written as
|
14
14
|
# Builder.new(Form, [:method, :action]).create(:search) do
|
@@ -19,6 +19,13 @@ module Yaks
|
|
19
19
|
class Builder
|
20
20
|
include Configurable
|
21
21
|
|
22
|
+
def initialize(klass, methods = [], &block)
|
23
|
+
@klass = klass
|
24
|
+
@methods = methods
|
25
|
+
def_forward *methods if methods.any?
|
26
|
+
instance_eval(&block) if block
|
27
|
+
end
|
28
|
+
|
22
29
|
def create(*args, &block)
|
23
30
|
build(@klass.create(*args), &block)
|
24
31
|
end
|
@@ -29,15 +36,8 @@ module Yaks
|
|
29
36
|
@config
|
30
37
|
end
|
31
38
|
|
32
|
-
def initialize(klass, methods = [], &block)
|
33
|
-
@klass = klass
|
34
|
-
@methods = methods
|
35
|
-
def_forward *methods if methods.any?
|
36
|
-
instance_eval(&block) if block
|
37
|
-
end
|
38
|
-
|
39
39
|
def inspect
|
40
|
-
"#<Builder #{@klass} #{@methods
|
40
|
+
"#<Builder #{@klass} #{@methods}>"
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
data/lib/yaks/config.rb
CHANGED
@@ -18,7 +18,7 @@ module Yaks
|
|
18
18
|
|
19
19
|
deprecated_alias :namespace, :mapper_namespace
|
20
20
|
|
21
|
-
def format_options(format
|
21
|
+
def format_options(format, options = Undefined)
|
22
22
|
with(format_options_hash: format_options_hash.merge(format => options))
|
23
23
|
end
|
24
24
|
|
data/lib/yaks/configurable.rb
CHANGED
@@ -1,4 +1,22 @@
|
|
1
1
|
module Yaks
|
2
|
+
# A "Configurable" class is one that keeps a configuration in a
|
3
|
+
# separate immutable object, of type class::Config. say you have
|
4
|
+
#
|
5
|
+
# class MyMapper < Yaks::Mapper
|
6
|
+
# # use yaks configuration DSL in here
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# The links, associations, etc, that you set up for MyMapper, will
|
10
|
+
# be available in MyMapper.config, which is an instance of
|
11
|
+
# Yaks::Mapper::Config.
|
12
|
+
#
|
13
|
+
# Each configuration step, like `link`, `has_many`, will replace
|
14
|
+
# MyMapper.config with an updated version, discarding the old
|
15
|
+
# config.
|
16
|
+
#
|
17
|
+
# By extending Configurable, a number of "macros" become available
|
18
|
+
# to describe the DSL that subclasses can use. See the docs for
|
19
|
+
# `def_set`. `def_forward`, and `def_add`.
|
2
20
|
module Configurable
|
3
21
|
attr_accessor :config
|
4
22
|
|
@@ -10,14 +28,28 @@ module Yaks
|
|
10
28
|
child.config = config
|
11
29
|
end
|
12
30
|
|
31
|
+
# Create a DSL method to set a certain config property. The
|
32
|
+
# generated method will take either a plain value, or a block,
|
33
|
+
# which will be captured and stored instead.
|
13
34
|
def def_set(*method_names)
|
14
35
|
method_names.each do |method_name|
|
15
|
-
define_singleton_method method_name do |arg|
|
16
|
-
|
36
|
+
define_singleton_method method_name do |arg = Undefined, &block|
|
37
|
+
if arg == Undefined && block
|
38
|
+
self.config = config.update(method_name => block)
|
39
|
+
elsif arg == Undefined
|
40
|
+
raise ArgumentError, "wrong number of arguments (0 for 1)"
|
41
|
+
else
|
42
|
+
self.config = config.update(method_name => arg)
|
43
|
+
end
|
17
44
|
end
|
18
45
|
end
|
19
46
|
end
|
20
47
|
|
48
|
+
# Forward a method to the config object. This assumes the method
|
49
|
+
# will return an updated config instance.
|
50
|
+
#
|
51
|
+
# Either takes a list of methods to forward, or a mapping (hash)
|
52
|
+
# of source to destination method name.
|
21
53
|
def def_forward(method_names, *args)
|
22
54
|
unless method_names.is_a? Hash
|
23
55
|
def_forward([method_names, *args].map{|name| {name => name}}.inject(:merge))
|
@@ -30,6 +62,13 @@ module Yaks
|
|
30
62
|
end
|
31
63
|
end
|
32
64
|
|
65
|
+
# Generate a DSL method that creates a certain type of domain
|
66
|
+
# object, and adds it to a list on the config.
|
67
|
+
#
|
68
|
+
# def_add :fieldset, create: Fieldset, append_to: :fields
|
69
|
+
#
|
70
|
+
# This will generate a `fieldset` method, which will call
|
71
|
+
# `Fieldset.create`, and append the result to `config.fields`
|
33
72
|
def def_add(name, options)
|
34
73
|
define_singleton_method name do |*args, &block|
|
35
74
|
defaults = options.fetch(:defaults, {})
|
data/lib/yaks/format/json_api.rb
CHANGED
@@ -7,12 +7,12 @@ module Yaks
|
|
7
7
|
|
8
8
|
# @param [Yaks::Resource] resource
|
9
9
|
# @return [Hash]
|
10
|
-
def call(resource,
|
10
|
+
def call(resource, _env = nil)
|
11
11
|
main_collection = resource.seq.map(&method(:serialize_resource))
|
12
12
|
|
13
|
-
{
|
14
|
-
linked = resource.seq.each_with_object(
|
15
|
-
serialize_linked_subresources(res.subresources,
|
13
|
+
{ data: main_collection }.tap do |serialized|
|
14
|
+
linked = resource.seq.each_with_object([]) do |res, array|
|
15
|
+
serialize_linked_subresources(res.subresources, array)
|
16
16
|
end
|
17
17
|
serialized.merge!(linked: linked) unless linked.empty?
|
18
18
|
end
|
@@ -21,11 +21,10 @@ module Yaks
|
|
21
21
|
# @param [Yaks::Resource] resource
|
22
22
|
# @return [Hash]
|
23
23
|
def serialize_resource(resource)
|
24
|
-
result = resource.attributes
|
24
|
+
result = {type: pluralize(resource.type).to_sym}.merge(resource.attributes)
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
end
|
26
|
+
links = serialize_links(resource.subresources)
|
27
|
+
result[:links] = links unless links.empty?
|
29
28
|
|
30
29
|
if resource.self_link && !result.key?(:href)
|
31
30
|
result[:href] = resource.self_link.uri
|
@@ -39,29 +38,32 @@ module Yaks
|
|
39
38
|
def serialize_links(subresources)
|
40
39
|
subresources.each_with_object({}) do |resource, hsh|
|
41
40
|
next if resource.null_resource?
|
42
|
-
|
43
|
-
hsh[key] = serialize_link(resource)
|
41
|
+
hsh[resource.type] = serialize_link(resource)
|
44
42
|
end
|
45
43
|
end
|
46
44
|
|
47
45
|
# @param [Yaks::Resource] resource
|
48
46
|
# @return [Array, String]
|
49
47
|
def serialize_link(resource)
|
50
|
-
resource.collection?
|
48
|
+
if resource.collection?
|
49
|
+
{type: resource.type, ids: resource.map(&send_with_args(:[], :id))}
|
50
|
+
else
|
51
|
+
{type: pluralize(resource.type), id: resource[:id]}
|
52
|
+
end
|
51
53
|
end
|
52
54
|
|
53
55
|
# @param [Hash] subresources
|
54
|
-
# @param [
|
55
|
-
# @return [
|
56
|
-
def serialize_linked_subresources(subresources,
|
56
|
+
# @param [Array] array
|
57
|
+
# @return [Array]
|
58
|
+
def serialize_linked_subresources(subresources, array)
|
57
59
|
subresources.each do |resources|
|
58
|
-
serialize_linked_resources(resources,
|
60
|
+
serialize_linked_resources(resources, array)
|
59
61
|
end
|
60
62
|
end
|
61
63
|
|
62
64
|
# @param [Array] resources
|
63
|
-
# @param [
|
64
|
-
# @return [
|
65
|
+
# @param [Array] linked
|
66
|
+
# @return [Array]
|
65
67
|
def serialize_linked_resources(subresource, linked)
|
66
68
|
subresource.seq.each_with_object(linked) do |resource, memo|
|
67
69
|
serialize_subresource(resource, memo)
|
@@ -74,32 +76,16 @@ module Yaks
|
|
74
76
|
# @param [Hash] linked
|
75
77
|
# @return [Hash]
|
76
78
|
def serialize_subresource(resource, linked)
|
77
|
-
|
78
|
-
set = linked.fetch(key) { Set.new }
|
79
|
-
linked[key] = (set << serialize_resource(resource))
|
79
|
+
linked << serialize_resource(resource)
|
80
80
|
serialize_linked_subresources(resource.subresources, linked)
|
81
81
|
end
|
82
82
|
|
83
83
|
def inverse
|
84
|
-
|
84
|
+
Yaks::Reader::JsonAPI.new
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
88
|
class Reader
|
89
|
-
def call(data, env)
|
90
|
-
type = data.detect do |key, value|
|
91
|
-
key unless key == "links"
|
92
|
-
end
|
93
|
-
|
94
|
-
CollectionResource.new(
|
95
|
-
type: type,
|
96
|
-
members: map_to_resource(data[type], )
|
97
|
-
)
|
98
|
-
end
|
99
|
-
|
100
|
-
def inverse
|
101
|
-
JsonApi.new
|
102
|
-
end
|
103
89
|
end
|
104
90
|
end
|
105
91
|
end
|
data/lib/yaks/mapper.rb
CHANGED
data/lib/yaks/mapper/form.rb
CHANGED
@@ -1,22 +1,8 @@
|
|
1
1
|
module Yaks
|
2
2
|
class Mapper
|
3
3
|
class Form
|
4
|
-
extend
|
4
|
+
extend Forwardable, Util
|
5
5
|
|
6
|
-
ConfigBuilder = Builder.new(Config) do
|
7
|
-
def_set :action, :title, :method, :media_type
|
8
|
-
def_add :field, create: Field::Builder, append_to: :fields
|
9
|
-
def_add :fieldset, create: Fieldset, append_to: :fields
|
10
|
-
HTML5Forms::INPUT_TYPES.each do |type|
|
11
|
-
def_add(type,
|
12
|
-
create: Field::Builder,
|
13
|
-
append_to: :fields,
|
14
|
-
defaults: { type: type }
|
15
|
-
)
|
16
|
-
end
|
17
|
-
def_forward :dynamic
|
18
|
-
def_forward :condition
|
19
|
-
end
|
20
6
|
|
21
7
|
def_delegators :config, :name, :action, :title, :method,
|
22
8
|
:media_type, :fields, :dynamic_blocks
|
@@ -28,28 +14,22 @@ module Yaks
|
|
28
14
|
options[:name] = args.first
|
29
15
|
end
|
30
16
|
|
31
|
-
new(
|
17
|
+
new(config: Config.build(options, &block))
|
32
18
|
end
|
33
19
|
|
34
20
|
############################################################
|
35
21
|
# instance
|
36
22
|
|
37
|
-
include
|
23
|
+
include Attributes.new(:config)
|
38
24
|
|
39
25
|
def add_to_resource(resource, mapper, _context)
|
40
26
|
return resource if config.if && !mapper.expand_value(config.if)
|
41
|
-
resource.add_form(
|
27
|
+
resource.add_form(to_resource_form(mapper))
|
42
28
|
end
|
43
29
|
|
44
|
-
|
45
|
-
|
46
|
-
def to_resource(mapper)
|
47
|
-
config = dynamic_blocks.inject(self.config) do |config, block|
|
48
|
-
ConfigBuilder.build(config, mapper.object, &block)
|
49
|
-
end
|
50
|
-
|
30
|
+
def to_resource_form(mapper)
|
51
31
|
attrs = {
|
52
|
-
fields:
|
32
|
+
fields: config.to_resource_fields(mapper),
|
53
33
|
action: mapper.expand_uri(config.action, true)
|
54
34
|
}
|
55
35
|
|
@@ -60,9 +40,6 @@ module Yaks
|
|
60
40
|
Resource::Form.new(attrs)
|
61
41
|
end
|
62
42
|
|
63
|
-
def resource_fields(fields, mapper)
|
64
|
-
fields.map { |field| field.to_resource(mapper) }
|
65
|
-
end
|
66
43
|
end
|
67
44
|
end
|
68
45
|
end
|
@@ -9,17 +9,51 @@ module Yaks
|
|
9
9
|
method: nil,
|
10
10
|
media_type: nil,
|
11
11
|
fields: [],
|
12
|
-
dynamic_blocks: [],
|
13
12
|
if: nil
|
14
13
|
)
|
15
14
|
|
16
|
-
def
|
17
|
-
|
15
|
+
def self.builder
|
16
|
+
@builder ||= Builder.new(self) do
|
17
|
+
def_set :action, :title, :method, :media_type
|
18
|
+
def_add :field, create: Field::Builder, append_to: :fields
|
19
|
+
def_add :fieldset, create: Fieldset, append_to: :fields
|
20
|
+
HTML5Forms::INPUT_TYPES.each do |type|
|
21
|
+
def_add(type,
|
22
|
+
create: Field::Builder,
|
23
|
+
append_to: :fields,
|
24
|
+
defaults: { type: type })
|
25
|
+
end
|
26
|
+
def_add :dynamic, create: DynamicField, append_to: :fields
|
27
|
+
def_forward :condition
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Builder expects a `create' method. Alias to constructor
|
32
|
+
def self.create(options)
|
33
|
+
new(options)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Build up a configuration based on an initial set of
|
37
|
+
# attributes, and a configuration block
|
38
|
+
def self.build(options = {}, &block)
|
39
|
+
builder.create(options, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Build up a configuration based on a config block. Provide an
|
43
|
+
# object to be supplied to the block
|
44
|
+
def self.build_with_object(object, &block)
|
45
|
+
builder.build(new, object, &block)
|
18
46
|
end
|
19
47
|
|
20
48
|
def condition(prc = nil, &blk)
|
21
49
|
with(if: prc || blk)
|
22
50
|
end
|
51
|
+
|
52
|
+
def to_resource_fields(mapper)
|
53
|
+
fields.flat_map do |field|
|
54
|
+
field.to_resource_fields(mapper)
|
55
|
+
end
|
56
|
+
end
|
23
57
|
end
|
24
58
|
end
|
25
59
|
end
|