yaks 0.3.1 → 0.4.0.rc1

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