yaks 0.3.1 → 0.4.0.rc1

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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile +0 -2
  5. data/LICENSE +7 -0
  6. data/README.md +160 -35
  7. data/Rakefile +2 -1
  8. data/lib/yaks/collection_mapper.rb +25 -18
  9. data/lib/yaks/collection_resource.rb +11 -17
  10. data/lib/yaks/config.rb +96 -0
  11. data/lib/yaks/default_policy.rb +34 -4
  12. data/lib/yaks/fp.rb +18 -0
  13. data/lib/yaks/mapper/association.rb +19 -27
  14. data/lib/yaks/mapper/class_methods.rb +4 -2
  15. data/lib/yaks/mapper/config.rb +24 -39
  16. data/lib/yaks/mapper/has_many.rb +7 -6
  17. data/lib/yaks/mapper/has_one.rb +4 -3
  18. data/lib/yaks/mapper/link.rb +52 -55
  19. data/lib/yaks/mapper.rb +38 -26
  20. data/lib/yaks/null_resource.rb +3 -3
  21. data/lib/yaks/primitivize.rb +29 -27
  22. data/lib/yaks/resource/link.rb +4 -0
  23. data/lib/yaks/resource.rb +18 -7
  24. data/lib/yaks/serializer/collection_json.rb +38 -0
  25. data/lib/yaks/serializer/hal.rb +55 -0
  26. data/lib/yaks/serializer/json_api.rb +61 -0
  27. data/lib/yaks/serializer.rb +25 -4
  28. data/lib/yaks/util.rb +2 -42
  29. data/lib/yaks/version.rb +1 -1
  30. data/lib/yaks.rb +10 -32
  31. data/notes.org +72 -0
  32. data/shaved_yak.gif +0 -0
  33. data/spec/acceptance/acceptance_spec.rb +46 -0
  34. data/spec/acceptance/models.rb +28 -0
  35. data/spec/integration/map_to_resource_spec.rb +11 -15
  36. data/spec/json/confucius.hal.json +23 -0
  37. data/spec/json/confucius.json_api.json +22 -0
  38. data/spec/json/john.hal.json +29 -0
  39. data/spec/json/youtypeitwepostit.collection.json +45 -0
  40. data/spec/spec_helper.rb +12 -1
  41. data/spec/support/shared_contexts.rb +7 -10
  42. data/spec/support/youtypeit_models_mappers.rb +20 -0
  43. data/spec/unit/yaks/collection_mapper_spec.rb +84 -0
  44. data/spec/unit/yaks/collection_resource_spec.rb +72 -0
  45. data/spec/unit/yaks/config_spec.rb +129 -0
  46. data/spec/unit/yaks/fp_spec.rb +31 -0
  47. data/spec/unit/yaks/mapper/association_spec.rb +80 -0
  48. data/spec/{yaks → unit/yaks}/mapper/class_methods_spec.rb +4 -4
  49. data/spec/unit/yaks/mapper/config_spec.rb +191 -0
  50. data/spec/unit/yaks/mapper/has_many_spec.rb +46 -0
  51. data/spec/unit/yaks/mapper/has_one_spec.rb +34 -0
  52. data/spec/unit/yaks/mapper/link_spec.rb +152 -0
  53. data/spec/unit/yaks/mapper_spec.rb +177 -0
  54. data/spec/unit/yaks/resource_spec.rb +40 -0
  55. data/spec/{yaks/hal_serializer_spec.rb → unit/yaks/serializer/hal_spec.rb} +2 -2
  56. data/spec/unit/yaks/serializer_spec.rb +12 -0
  57. data/spec/unit/yaks/util_spec.rb +43 -0
  58. data/spec/yaml/confucius.yaml +10 -0
  59. data/spec/yaml/youtypeitwepostit.yaml +9 -0
  60. data/yaks.gemspec +7 -8
  61. metadata +92 -53
  62. data/Gemfile.lock +0 -111
  63. data/lib/yaks/hal_serializer.rb +0 -59
  64. data/lib/yaks/json_api_serializer.rb +0 -59
  65. data/lib/yaks/link_lookup.rb +0 -23
  66. data/lib/yaks/mapper/lookup.rb +0 -19
  67. data/lib/yaks/mapper/map_links.rb +0 -17
  68. data/lib/yaks/profile_registry.rb +0 -60
  69. data/lib/yaks/rel_registry.rb +0 -20
  70. data/lib/yaks/shared_options.rb +0 -15
  71. data/spec/support/shorthands.rb +0 -22
  72. data/spec/yaks/collection_resource_spec.rb +0 -9
  73. data/spec/yaks/mapper/association_spec.rb +0 -21
  74. data/spec/yaks/mapper/config_spec.rb +0 -77
  75. data/spec/yaks/mapper/has_one_spec.rb +0 -16
  76. data/spec/yaks/mapper/link_spec.rb +0 -38
  77. data/spec/yaks/mapper/map_links_spec.rb +0 -46
  78. data/spec/yaks/mapper_spec.rb +0 -65
  79. data/spec/yaks/resource_spec.rb +0 -23
@@ -1,48 +1,40 @@
1
1
  module Yaks
2
2
  class Mapper
3
3
  class Association
4
- include Equalizer.new(:name, :_mapper, :links)
5
- include SharedOptions
4
+ include Equalizer.new(:name, :mapper, :rel)
6
5
 
7
- attr_reader :name, :key, :links, :options
8
- private :links, :options
6
+ attr_reader :name, :mapper, :rel, :collection_mapper
9
7
 
10
- def initialize(name, key, mapper, links, options)
11
- @name = name
12
- @key = key
13
- @mapper = mapper
14
- @links = links
15
- @options = options
8
+ def initialize(name, mapper, rel, collection_mapper)
9
+ @name, @mapper, @rel, @collection_mapper =
10
+ name, mapper, rel, collection_mapper
16
11
  end
17
12
 
18
- def self_link
19
- links.detect {|link| link.rel? :self }
20
- end
21
-
22
- # @param [Symbol] src_type
23
- # The profile type of the resource that contains the association
24
- # @param [#call] loader
13
+ # @param [#call] lookup
25
14
  # A callable that can retrieve an association by its name
26
- # @param [Hash] options
27
15
  # @return Array[rel, resource]
28
16
  # Returns the rel (registered type or URI) + the associated, mapped resource
29
- def map_to_resource_pair(src_type, loader, options)
17
+ def create_subresource(parent_mapper, lookup, context)
30
18
  [
31
- options[:rel_registry].lookup(src_type, key),
32
- map_resource(loader.(name), options)
19
+ map_rel(parent_mapper, context.fetch(:policy)),
20
+ map_resource(lookup[name], context)
33
21
  ]
34
22
  end
35
23
 
36
- private
24
+ def map_rel(parent_mapper, policy)
25
+ return @rel unless @rel.equal?(Undefined)
26
+ policy.derive_rel_from_association(parent_mapper, self)
27
+ end
37
28
 
38
- def mapper(opts = nil)
39
- return _mapper unless _mapper == Undefined
40
- opts[:policy].derive_missing_mapper_for_association(self)
29
+ def association_mapper(policy)
30
+ return @mapper unless @mapper.equal?(Undefined)
31
+ policy.derive_mapper_from_association(self)
41
32
  end
42
33
 
43
- def _mapper
44
- @mapper
34
+ # @abstract
35
+ def map_resource(object, context)
45
36
  end
37
+
46
38
  end
47
39
  end
48
40
  end
@@ -5,8 +5,10 @@ module Yaks
5
5
  module ClassMethods
6
6
  include Forwardable
7
7
  include Util
8
+ include FP
8
9
 
9
10
  CONFIG_METHODS = [
11
+ :type,
10
12
  :attributes,
11
13
  :link,
12
14
  :profile,
@@ -16,7 +18,7 @@ module Yaks
16
18
  ]
17
19
 
18
20
  def config
19
- @config ||= Config.new
21
+ @config ||= Config.new nil, [], [], []
20
22
  @config = yield(@config) if block_given?
21
23
  @config
22
24
  end
@@ -27,7 +29,7 @@ module Yaks
27
29
 
28
30
  CONFIG_METHODS.each do |method_name|
29
31
  define_method method_name do |*args|
30
- config (method_name, *args)
32
+ config &send_with_args(method_name, *args)
31
33
  end
32
34
  end
33
35
 
@@ -1,50 +1,44 @@
1
1
  module Yaks
2
2
  class Mapper
3
3
  class Config
4
- include Equalizer.new(:attributes)
4
+ include Equalizer.new(:type, :attributes, :links, :associations)
5
5
 
6
- def initialize(attributes = Hamster.list, links = Hamster.list, associations = Hamster.list, profile = nil)
6
+ attr_reader :links, :associations
7
+
8
+ def initialize(type, attributes, links, associations)
9
+ @type = type
7
10
  @attributes = attributes
8
11
  @links = links
9
12
  @associations = associations
10
- @profile = profile
11
- freeze
12
13
  end
13
14
 
14
- def new(updates)
15
+ def updated(updates)
15
16
  self.class.new(
17
+ updates.fetch(:type) { type },
16
18
  updates.fetch(:attributes) { attributes },
17
19
  updates.fetch(:links) { links },
18
- updates.fetch(:associations) { associations },
19
- updates.fetch(:profile) { profile },
20
+ updates.fetch(:associations) { associations }
20
21
  )
21
22
  end
22
23
 
24
+ def type(type = Undefined)
25
+ return @type if type.equal?(Undefined)
26
+ updated(type: type)
27
+ end
28
+
23
29
  def attributes(*attrs)
24
30
  return @attributes if attrs.empty?
25
- new(
26
- attributes: @attributes + attrs.to_list
31
+ updated(
32
+ attributes: @attributes + attrs
27
33
  )
28
34
  end
29
35
 
30
36
  def link(rel, template, options = {})
31
- new(
32
- links: @links.cons(Link.new(rel, template, options))
33
- )
34
- end
35
-
36
- def profile(type = Undefined)
37
- return @profile if type == Undefined
38
- new(
39
- profile: type
37
+ updated(
38
+ links: @links + [Link.new(rel, template, options)]
40
39
  )
41
40
  end
42
41
 
43
- # key
44
- # embed_style
45
- # rel
46
- # (profile)
47
-
48
42
  def has_one(name, options = {})
49
43
  add_association(HasOne, name, options)
50
44
  end
@@ -53,27 +47,18 @@ module Yaks
53
47
  add_association(HasMany, name, options)
54
48
  end
55
49
 
56
- def add_association(type, name, options = {})
57
- new(
58
- associations: @associations.cons(
50
+ def add_association(type, name, options)
51
+ updated(
52
+ associations: @associations + [
59
53
  type.new(
60
54
  name,
61
- options.fetch(:as) { name },
62
- options.fetch(:mapper) { Undefined },
63
- options.fetch(:links) { Yaks::List() },
64
- options.reject {|k,v| [:as, :mapper, :links].include?(k) }
55
+ options.fetch(:mapper) { Undefined },
56
+ options.fetch(:rel) { Undefined },
57
+ options.fetch(:collection_mapper) { Undefined },
65
58
  )
66
- )
59
+ ]
67
60
  )
68
61
  end
69
-
70
- def links
71
- @links
72
- end
73
-
74
- def associations
75
- @associations
76
- end
77
62
  end
78
63
  end
79
64
  end
@@ -1,14 +1,15 @@
1
1
  module Yaks
2
2
  class Mapper
3
3
  class HasMany < Association
4
- def map_resource(collection, opts)
5
- opts = opts.merge(options)
6
- collection_mapper(opts).new(collection, mapper(opts), opts).to_resource
4
+ def map_resource(collection, context)
5
+ resource_mapper = association_mapper(context.fetch(:policy))
6
+ context = context.merge(resource_mapper: resource_mapper)
7
+ collection_mapper.new(collection, context).to_resource
7
8
  end
8
9
 
9
- def collection_mapper(opts)
10
- opts = opts.merge(options)
11
- opts.fetch(:collection_mapper) { CollectionMapper }
10
+ def collection_mapper
11
+ return @collection_mapper unless @collection_mapper.equal? Undefined
12
+ CollectionMapper
12
13
  end
13
14
  end
14
15
  end
@@ -1,9 +1,10 @@
1
1
  module Yaks
2
2
  class Mapper
3
3
  class HasOne < Association
4
- def map_resource(instance, opts)
5
- opts = opts.merge(options)
6
- mapper(opts).new(instance, opts).to_resource
4
+ def map_resource(instance, context)
5
+ association_mapper(context.fetch(:policy))
6
+ .new(instance, context)
7
+ .to_resource
7
8
  end
8
9
  end
9
10
  end
@@ -1,95 +1,92 @@
1
1
  module Yaks
2
2
  class Mapper
3
+ # A Yaks::Mapper::Link is part of a mapper's configuration. It captures
4
+ # what is set through the mapper's class level `#link` function, and is
5
+ # capable of generating a `Yaks::Resource::Link` for a given mapper
6
+ # instance (and hence subject).
7
+ #
8
+ # @example
9
+ # link :self, 'http://api.foo.org/users/{id}', title: ->{ "User #{object.name}" }
10
+ # link :profile, 'http://apidocs.foo.org/profiles/users'
11
+ # link 'http://apidocs.foo.org/rels/friends', 'http://api.foo.org/users/{id}/friends?page={page}', expand: [:id]
12
+ #
13
+ # It takes a relationship identifier, a URI template and an options hash.
14
+ #
15
+ # @param rel [Symbol|String] Either a registered relationship type (Symbol)
16
+ # or a relationship URI. See [RFC5988 Web Linking](http://tools.ietf.org/html/rfc5988)
17
+ # @param template [String] A [RFC6570](http://tools.ietf.org/html/rfc6570) URI template
18
+ # @param template [Symbol] A method name that generates the link. No more expansion is done afterwards
19
+ # @option expand [Boolean] pass false to pass on the URI template in the response,
20
+ # instead of expanding the variables
21
+ # @option expand [Array[Symbol]] pass a list of variable names to only expand those,
22
+ # and return a partially expanded URI template in the response
23
+ # @option title [String] Give the link a title
24
+ # @option title [#to_proc] Block that returns the title. If it takes an argument,
25
+ # it will receive the mapper instance as argument. Otherwise it is evaluated in the mapper context
3
26
  class Link
4
27
  extend Forwardable
5
28
  include Concord.new(:rel, :template, :options)
6
29
  include Util
7
30
 
8
- def_delegators :uri_template, :expand, :expand_partial
31
+ def_delegators :uri_template, :expand_partial
9
32
 
10
- def initialize(rel, template, options = {})
33
+ def initialize(rel, template, options)
11
34
  @rel, @template, @options = rel, template, options
12
35
  end
13
36
 
14
37
  def rel?(rel)
15
- self.rel == rel
38
+ rel().eql? rel
16
39
  end
17
40
 
18
- def expand
19
- options.fetch(:expand) { true }
41
+ # A link is templated if it does not expand, or only partially
42
+ def templated?
43
+ !options.fetch(:expand) { true }.equal? true
20
44
  end
21
- alias expand? expand
22
45
 
23
- # link 'http://{only}/{certain}/{variables}/{not_expanded}, expand: [:only, :certain, :variables]
24
- def expand_partial?
25
- expand.respond_to?(:map)
46
+ # Does this link need expansion, full or partially
47
+ def expand?
48
+ options.fetch(:expand) { true }
26
49
  end
27
50
 
28
- def expand_with(lookup)
29
- # link :method_that_returns_link
30
- return lookup.call(template) if template.is_a? Symbol
31
-
32
- # link 'http://link/{template}', expand: false
33
- # link 'http://link/{template}', expand: [:only, :some, :fields]
34
- return template unless expand?
51
+ def template_variables
52
+ options.fetch(:expand) { uri_template.variables }.map(&:to_sym)
53
+ end
35
54
 
36
- if expand_partial?
37
- uri_template.expand_partial(expansion_mapping(lookup)).to_s
38
- else
39
- uri_template.expand(expansion_mapping(lookup))
55
+ def expansion_mapping(lookup)
56
+ template_variables.map.with_object({}) do |name, hsh|
57
+ hsh[name] = lookup[name]
40
58
  end
41
59
  end
42
60
 
61
+ def uri_template
62
+ URITemplate.new(template)
63
+ end
64
+
43
65
  def map_to_resource_link(mapper)
44
- make_resource_link(
66
+ Resource::Link.new(
45
67
  rel,
46
68
  expand_with(mapper.method(:load_attribute)),
47
69
  resource_link_options(mapper)
48
70
  )
49
71
  end
50
72
 
51
- def uri_template
52
- @uri_template ||= URITemplate.new(template)
53
- end
73
+ def expand_with(lookup)
74
+ return lookup[template] if template.is_a? Symbol
54
75
 
55
- def template_variables
56
- if expand_partial?
57
- uri_template.variables & expand.map(&:to_s)
76
+ if expand?
77
+ expand_partial(expansion_mapping(lookup)).to_s
58
78
  else
59
- uri_template.variables
79
+ template
60
80
  end
61
81
  end
62
82
 
63
- def expansion_mapping(lookup)
64
- template_variables.map.with_object({}) do |var, hsh|
65
- hsh[var] = lookup.call(var)
66
- end
67
- end
68
-
69
- # Link properties defined in HAL
70
- # href
71
- # templated
72
- # typed
73
- # deprecation
74
- # name
75
- # profile
76
- # title
77
- # hreflang
78
-
79
83
  def resource_link_options(mapper)
80
- options = self.options
81
- options = options.merge(title: resolve_title(options[:title], mapper)) if options.has_key?(:title)
82
- options = options.merge( templated: true ) if !expand? || expand_partial?
83
- options.reject{|k,v| [:expand].include? k}
84
+ options = options()
85
+ options = options.merge(title: Resolve(options[:title], mapper)) if options.key?(:title)
86
+ options = options.merge(templated: true) if templated?
87
+ options.reject{|key| key.equal? :expand }
84
88
  end
85
89
 
86
- def resolve_title(title_proc, mapper)
87
- Resolve(title_proc, mapper)
88
- end
89
-
90
- def make_resource_link(rel, uri, options)
91
- Resource::Link.new(rel, uri, options)
92
- end
93
90
  end
94
91
  end
95
92
  end
data/lib/yaks/mapper.rb CHANGED
@@ -3,61 +3,73 @@
3
3
  module Yaks
4
4
  class Mapper
5
5
  extend ClassMethods, Forwardable
6
- include Util, MapLinks, SharedOptions
6
+ include Util, FP
7
7
 
8
8
  def_delegators 'self.class', :config
9
9
  def_delegators :config, :attributes, :links, :associations
10
10
 
11
- attr_reader :subject, :options
12
- private :subject, :options
13
- alias object subject
11
+ attr_reader :object, :context
14
12
 
15
- def initialize(subject, options = {})
16
- @subject = subject
17
- @options = YAKS_DEFAULT_OPTIONS.merge(options)
13
+ def initialize(object, context)
14
+ @object = object
15
+ @context = context
18
16
  end
19
17
 
20
- def to_resource
21
- return NullResource.new if subject.nil?
18
+ def policy
19
+ context.fetch(:policy)
20
+ end
21
+
22
+ def env
23
+ context.fetch(:env)
24
+ end
25
+
26
+ def call
27
+ return NullResource.new if object.nil?
22
28
 
23
29
  Resource.new(
24
- map_attributes,
25
- map_links,
26
- map_subresources
30
+ type: mapper_name,
31
+ attributes: map_attributes,
32
+ links: map_links,
33
+ subresources: map_subresources
27
34
  )
28
35
  end
36
+ alias to_resource call
29
37
 
30
- def profile_type
31
- config.profile || policy.derive_profile_from_mapper(self)
38
+ def map_attributes
39
+ filter(attributes).each_with_object({}) do |attr, memo|
40
+ memo[attr] = load_attribute(attr)
41
+ end
32
42
  end
33
43
 
34
- def map_attributes
35
- filter(attributes).map &juxt(identity_function, method(:load_attribute))
44
+ def map_links
45
+ links.map &send_with_args(:map_to_resource_link, self)
36
46
  end
37
47
 
38
48
  def map_subresources
39
- attributes = filter(associations.map(&:name))
40
- associations.select{|assoc| attributes.include? assoc.name}.map do |association|
41
- association.map_to_resource_pair(
42
- profile_type,
49
+ attributes = filter(associations.map(&:name))
50
+ associations = associations().select{|assoc| attributes.include? assoc.name }
51
+ associations.each_with_object({}) do |association, memo|
52
+ rel, subresource = association.create_subresource(
53
+ self,
43
54
  method(:load_association),
44
- options.merge(parent: subject)
55
+ context
45
56
  )
57
+ memo[rel] = subresource
46
58
  end
47
59
  end
48
60
 
49
61
  def load_attribute(name)
50
- respond_to?(name) ? send(name) : subject.send(name)
62
+ respond_to?(name) ? public_send(name) : object.public_send(name)
51
63
  end
52
64
  alias load_association load_attribute
53
65
 
54
- def profile
55
- config.profile || policy.derive_missing_profile_from_mapper(self)
56
- end
57
-
58
66
  def filter(attrs)
59
67
  attrs
60
68
  end
61
69
 
70
+ def mapper_name
71
+ config.type || policy.derive_type_from_mapper_class(self.class)
72
+ end
73
+
62
74
  end
63
75
  end
@@ -7,15 +7,15 @@ module Yaks
7
7
  end
8
8
 
9
9
  def attributes
10
- Yaks::Hash()
10
+ {}
11
11
  end
12
12
 
13
13
  def links
14
- Yaks::List()
14
+ []
15
15
  end
16
16
 
17
17
  def subresources
18
- Yaks::Hash()
18
+ {}
19
19
  end
20
20
 
21
21
  def [](*)
@@ -1,43 +1,45 @@
1
1
  module Yaks
2
2
  class Primitivize
3
- include Concord.new(:object)
4
3
 
5
- # TODO Global config, make this extensible in a per-instance way
6
- MAPPINGS = {}
4
+ attr_reader :mappings
7
5
 
8
- def self.call(object)
9
- new(object).call
6
+ def initialize
7
+ @mappings = {}
10
8
  end
11
9
 
12
- def self.map(*types, &blk)
13
- types.each do |type|
14
- MAPPINGS[type] = blk
10
+ def call(object)
11
+ mappings.each do |pattern, block|
12
+ return instance_exec(object, &block) if pattern === object
15
13
  end
14
+ raise "don't know how to turn #{object.class} (#{object.inspect}) into a primitive"
16
15
  end
17
16
 
18
- map String, TrueClass, FalseClass, NilClass, Numeric do
19
- object
20
- end
21
-
22
- map Symbol do
23
- object.to_s
24
- end
25
-
26
- map Hash, Hamster::Hash do
27
- object.to_enum(:each).with_object({}) do |(key, value), output|
28
- output[self.class.(key)] = self.class.(value)
17
+ def map(*types, &blk)
18
+ types.each do |type|
19
+ mappings[type] = blk
29
20
  end
30
21
  end
31
22
 
32
- map Enumerable, Hamster::Enumerable do
33
- object.map(&self.class.method(:call)).to_a
34
- end
35
-
36
- def call
37
- MAPPINGS.each do |pattern, block|
38
- return instance_eval(&block) if pattern === object
23
+ def self.create
24
+ new.tap do |p|
25
+ p.map String, TrueClass, FalseClass, NilClass, Numeric do |object|
26
+ object
27
+ end
28
+
29
+ p.map Symbol do |object|
30
+ object.to_s
31
+ end
32
+
33
+ p.map Hash do |object|
34
+ object.to_enum(:each).with_object({}) do |(key, value), output|
35
+ output[call(key)] = call(value)
36
+ end
37
+ end
38
+
39
+ p.map Enumerable do |object|
40
+ object.map(&method(:call)).to_a
41
+ end
39
42
  end
40
- raise "don't know how to turn #{object.class} (#{object.inspect}) into a primitive"
41
43
  end
42
44
  end
43
45
  end
@@ -13,6 +13,10 @@ module Yaks
13
13
  options[:name]
14
14
  end
15
15
 
16
+ def title
17
+ options[:title]
18
+ end
19
+
16
20
  def templated?
17
21
  options.fetch(:templated) { false }
18
22
  end
data/lib/yaks/resource.rb CHANGED
@@ -1,20 +1,24 @@
1
1
  module Yaks
2
2
  class Resource
3
- include Equalizer.new(:links, :attributes, :subresources)
4
- include Enumerable, LinkLookup
3
+ include Equalizer.new(:type, :links, :attributes, :subresources)
4
+ include Enumerable
5
5
 
6
- attr_reader :attributes, :links, :subresources
6
+ attr_reader :type, :attributes, :links, :subresources
7
7
 
8
- def initialize(attributes, links, subresources)
9
- @attributes = Yaks::Hash(attributes)
10
- @links = Yaks::List(links)
11
- @subresources = Yaks::Hash(subresources)
8
+ def initialize(options)
9
+ @type = options.fetch(:type, nil)
10
+ @attributes = options.fetch(:attributes, {})
11
+ @links = options.fetch(:links, [])
12
+ @subresources = options.fetch(:subresources, {})
12
13
  end
13
14
 
14
15
  def [](attr)
15
16
  attributes[attr]
16
17
  end
17
18
 
19
+ # def type
20
+ # end
21
+
18
22
  def each
19
23
  return to_enum unless block_given?
20
24
  yield self
@@ -23,5 +27,12 @@ module Yaks
23
27
  def collection?
24
28
  false
25
29
  end
30
+
31
+ def self_link
32
+ links.find do |link|
33
+ link.rel == :self
34
+ end
35
+ end
36
+
26
37
  end
27
38
  end
@@ -0,0 +1,38 @@
1
+ module Yaks
2
+ class Serializer
3
+ class CollectionJson < self
4
+ Serializer.register self, :collection_json, 'application/vnd.collection+json'
5
+
6
+ include FP
7
+
8
+ def serialize_resource(resource)
9
+ result = {
10
+ version: "1.0",
11
+ items: serialize_items(resource)
12
+ }
13
+ result[:href] = resource.self_link.uri if resource.self_link
14
+ {collection: result}
15
+ end
16
+
17
+ def serialize_items(resource)
18
+ resource.map do |item|
19
+ attrs = item.attributes.map do |name, value|
20
+ {
21
+ name: name,
22
+ value: value
23
+ }
24
+ end
25
+ result = { data: attrs }
26
+ result[:href] = item.self_link.uri if item.self_link
27
+ item.links.each do |link|
28
+ next if link.rel == :self
29
+ result[:links] ||= []
30
+ result[:links] << {rel: link.rel, href: link.uri}
31
+ result[:links].last[:name] = link.name if link.name
32
+ end
33
+ result
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end