yaks 0.8.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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