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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a609ab5341e17172a098a296e8935d9abf4fa995
4
- data.tar.gz: 44f4ef2d6d7786bf09261b352ed9e253064ff31e
3
+ metadata.gz: e1351b2c236dc9fb68ff5bbe9c365f7addb38cad
4
+ data.tar.gz: d8baafb1e55bc4bf5e926e012dac9bd20d7284f8
5
5
  SHA512:
6
- metadata.gz: f65c4f9d62853543fc92fbe58fc9a3c4b057eac27473a951d8cfe38edf19cf0af461bd9e047e20cbce47ab771ded553b859937e6c8dff7e1a25aa2f1101f8a18
7
- data.tar.gz: be7d5c1edcc4e745a25897deae315000d1bdff1713b8a6d61876f36d62d4dc866792a5f362957c7218e64e7a9c346bec38bd064e7297cfa33be26702b451bd33
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
+ ![](logo.png)
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
 
@@ -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'
@@ -13,7 +13,7 @@ module Yaks
13
13
  end
14
14
 
15
15
  def remove(*attrs)
16
- self.class.new(*[*(names-attrs), @defaults.reject {|k,v| attrs.include?(k) }])
16
+ self.class.new(*[*(names-attrs), @defaults.reject {|k| attrs.include?(k) }])
17
17
  end
18
18
 
19
19
  def included(descendant)
@@ -7,8 +7,8 @@ module Yaks
7
7
  #
8
8
  # # This code
9
9
  # Form.create(:search)
10
- # .method("POST")
11
- # .action("/search")
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.inspect}>"
40
+ "#<Builder #{@klass} #{@methods}>"
41
41
  end
42
42
  end
43
43
  end
@@ -4,7 +4,7 @@ module Yaks
4
4
 
5
5
  # @param [Array] collection
6
6
  # @return [Array]
7
- def call(collection, env = {})
7
+ def call(collection, _env = nil)
8
8
  @object = collection
9
9
 
10
10
  attrs = {
@@ -18,7 +18,7 @@ module Yaks
18
18
 
19
19
  deprecated_alias :namespace, :mapper_namespace
20
20
 
21
- def format_options(format = Undefined, options = Undefined)
21
+ def format_options(format, options = Undefined)
22
22
  with(format_options_hash: format_options_hash.merge(format => options))
23
23
  end
24
24
 
@@ -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
- self.config = config.update(method_name => arg)
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, {})
@@ -7,12 +7,12 @@ module Yaks
7
7
 
8
8
  # @param [Yaks::Resource] resource
9
9
  # @return [Hash]
10
- def call(resource, env = {})
10
+ def call(resource, _env = nil)
11
11
  main_collection = resource.seq.map(&method(:serialize_resource))
12
12
 
13
- { pluralize(resource.type) => main_collection }.tap do |serialized|
14
- linked = resource.seq.each_with_object({}) do |res, hsh|
15
- serialize_linked_subresources(res.subresources, hsh)
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
- unless resource.subresources.empty?
27
- result[:links] = serialize_links(resource.subresources)
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
- key = resource.collection? ? pluralize(resource.type) : resource.type
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? ? resource.map(&send_with_args(:[], :id)) : resource[:id]
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 [Hash] hsh
55
- # @return [Hash]
56
- def serialize_linked_subresources(subresources, hsh)
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, hsh)
60
+ serialize_linked_resources(resources, array)
59
61
  end
60
62
  end
61
63
 
62
64
  # @param [Array] resources
63
- # @param [Hash] linked
64
- # @return [Hash]
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
- key = pluralize(resource.type)
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
- JsonApi::Reader.new
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
@@ -2,7 +2,8 @@ module Yaks
2
2
  class Mapper
3
3
  extend Configurable
4
4
 
5
- def_forward :type => :with_type
5
+ def_set :type
6
+
6
7
  def_forward :attributes => :add_attributes
7
8
  def_forward :append_to
8
9
 
@@ -1,22 +1,8 @@
1
1
  module Yaks
2
2
  class Mapper
3
3
  class Form
4
- extend Configurable, Forwardable, Util
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(ConfigBuilder.build(Config.new(options), &block))
17
+ new(config: Config.build(options, &block))
32
18
  end
33
19
 
34
20
  ############################################################
35
21
  # instance
36
22
 
37
- include Concord.new(:config)
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(to_resource(mapper))
27
+ resource.add_form(to_resource_form(mapper))
42
28
  end
43
29
 
44
- private
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: resource_fields(config.fields, mapper),
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 dynamic(&blk)
17
- append_to(:dynamic_blocks, blk)
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