representors 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +126 -0
- data/LICENSE.md +19 -0
- data/README.md +28 -0
- data/Rakefile +10 -0
- data/lib/representor_support/utilities.rb +39 -0
- data/lib/representors.rb +5 -0
- data/lib/representors/errors.rb +7 -0
- data/lib/representors/field.rb +108 -0
- data/lib/representors/options.rb +67 -0
- data/lib/representors/representor.rb +161 -0
- data/lib/representors/representor_builder.rb +64 -0
- data/lib/representors/representor_hash.rb +59 -0
- data/lib/representors/serialization.rb +4 -0
- data/lib/representors/serialization/deserializer_base.rb +29 -0
- data/lib/representors/serialization/deserializer_factory.rb +13 -0
- data/lib/representors/serialization/hal_deserializer.rb +44 -0
- data/lib/representors/serialization/hal_serializer.rb +91 -0
- data/lib/representors/serialization/hale_deserializer.rb +162 -0
- data/lib/representors/serialization/hale_serializer.rb +110 -0
- data/lib/representors/serialization/serialization_base.rb +27 -0
- data/lib/representors/serialization/serialization_factory_base.rb +54 -0
- data/lib/representors/serialization/serializer_base.rb +20 -0
- data/lib/representors/serialization/serializer_factory.rb +17 -0
- data/lib/representors/transition.rb +130 -0
- data/lib/representors/version.rb +4 -0
- data/spec/fixtures/complex_hal.json +92 -0
- data/spec/fixtures/complex_hale_document.json +81 -0
- data/spec/fixtures/drds_hash.rb +120 -0
- data/spec/fixtures/hale_spec_examples/basic.json +77 -0
- data/spec/fixtures/hale_spec_examples/complex_reference_objects.json +157 -0
- data/spec/fixtures/hale_spec_examples/data.json +17 -0
- data/spec/fixtures/hale_spec_examples/data_objects.json +96 -0
- data/spec/fixtures/hale_spec_examples/link_objects.json +18 -0
- data/spec/fixtures/hale_spec_examples/nested_ref.json +43 -0
- data/spec/fixtures/hale_spec_examples/reference_objects.json +89 -0
- data/spec/fixtures/hale_tutorial_examples/basic_links.json +85 -0
- data/spec/fixtures/hale_tutorial_examples/basic_links_with_orders.json +96 -0
- data/spec/fixtures/hale_tutorial_examples/basic_links_with_references.json +108 -0
- data/spec/fixtures/hale_tutorial_examples/embedded.json +182 -0
- data/spec/fixtures/hale_tutorial_examples/empty.json +1 -0
- data/spec/fixtures/hale_tutorial_examples/enctype.json +14 -0
- data/spec/fixtures/hale_tutorial_examples/final.json +141 -0
- data/spec/fixtures/hale_tutorial_examples/get_link.json +17 -0
- data/spec/fixtures/hale_tutorial_examples/get_link_with_data.json +29 -0
- data/spec/fixtures/hale_tutorial_examples/links.json +11 -0
- data/spec/fixtures/hale_tutorial_examples/links_only.json +3 -0
- data/spec/fixtures/hale_tutorial_examples/meta.json +208 -0
- data/spec/fixtures/hale_tutorial_examples/self_link.json +7 -0
- data/spec/fixtures/single_drd.rb +266 -0
- data/spec/lib/representors/complex_representor_spec.rb +288 -0
- data/spec/lib/representors/field_spec.rb +141 -0
- data/spec/lib/representors/representor_builder_spec.rb +223 -0
- data/spec/lib/representors/representor_spec.rb +285 -0
- data/spec/lib/representors/serialization/deserializer_factory_spec.rb +118 -0
- data/spec/lib/representors/serialization/hal_deserializer_spec.rb +34 -0
- data/spec/lib/representors/serialization/hal_serializer_spec.rb +171 -0
- data/spec/lib/representors/serialization/hale_deserializer_spec.rb +59 -0
- data/spec/lib/representors/serialization/hale_roundtrip_spec.rb +34 -0
- data/spec/lib/representors/serialization/hale_serializer_spec.rb +659 -0
- data/spec/lib/representors/serialization/serializer_factory_spec.rb +108 -0
- data/spec/lib/representors/transition_spec.rb +349 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/basic-hale.json +12 -0
- data/spec/support/hal_representor_shared.rb +206 -0
- data/spec/support/helpers.rb +8 -0
- data/tasks/benchmark.rake +75 -0
- data/tasks/complex_hal_document.json +98 -0
- data/tasks/test_specs.rake +37 -0
- data/tasks/yard.rake +22 -0
- metadata +232 -0
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'enumerable/lazy' if RUBY_VERSION < '2.0'
|
3
|
+
require 'representors/field'
|
4
|
+
require 'representors/transition'
|
5
|
+
require 'representors/serialization/serializer_factory'
|
6
|
+
|
7
|
+
module Representors
|
8
|
+
##
|
9
|
+
# Manages the respresentation of hypermedia messages for different media-types.
|
10
|
+
class Representor
|
11
|
+
DEFAULT_PROTOCOL = 'http'
|
12
|
+
PROTOCOL_TEMPLATE = "%s://%s"
|
13
|
+
UNKNOWN_PROTOCOL = 'ruby_id'
|
14
|
+
VALUE_KEY = :value
|
15
|
+
META_LINK_FIELDS = ['profile', 'help', 'type', 'self']
|
16
|
+
|
17
|
+
# @example
|
18
|
+
# representor = Representors::Representor.new do |builder|
|
19
|
+
# builder.add_attribute({ name: => 'Bob' })
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @param [Hash] hash the abstract representor hash defining a resource
|
23
|
+
def initialize(hash = {}, builder = nil)
|
24
|
+
builder ||= RepresentorBuilder.new(hash)
|
25
|
+
builder = yield builder if block_given?
|
26
|
+
@representor_hash = builder.to_representor_hash
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param format to convert this representor to
|
30
|
+
# @return the representor serialized to a particular media-type like application/hal+json
|
31
|
+
def to_media_type(format, options={})
|
32
|
+
SerializerFactory.build(format, self).to_media_type(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def empty?
|
36
|
+
@representor_hash.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
def ==(other)
|
40
|
+
other.is_a?(Hash) ? to_hash == other : to_hash == other.to_hash
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the document for the representor
|
44
|
+
#
|
45
|
+
# @return [String] the document for the representor
|
46
|
+
def doc
|
47
|
+
@doc ||= @representor_hash.doc || ''
|
48
|
+
end
|
49
|
+
|
50
|
+
# The URI for the object
|
51
|
+
#
|
52
|
+
# @note If the URI can't be made from the provided information it constructs one from the Ruby ID
|
53
|
+
# @return [String]
|
54
|
+
def identifier
|
55
|
+
@identifier ||= begin
|
56
|
+
uri = @representor_hash.href || self.object_id
|
57
|
+
protocol = @representor_hash.protocol || (uri == self.object_id ? UNKNOWN_PROTOCOL : DEFAULT_PROTOCOL)
|
58
|
+
PROTOCOL_TEMPLATE % [protocol, uri]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Hash] The hash representation of the object
|
63
|
+
def to_hash
|
64
|
+
@to_hash ||= @representor_hash.to_h
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [String] the yaml representation of the object
|
68
|
+
def to_yaml
|
69
|
+
@to_yaml ||= YAML.dump(to_hash)
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [String] so the user can 'puts' this object
|
73
|
+
def to_s
|
74
|
+
to_hash.inspect
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Hash] the resource attributes inferred from representor[:semantics]
|
78
|
+
def properties
|
79
|
+
@properties ||= Hash[@representor_hash.attributes.map { |k, v| [ k, v[VALUE_KEY]] }]
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Enumerable] who's elements are all <Representors:Representor> objects
|
83
|
+
def embedded
|
84
|
+
@embedded ||= begin
|
85
|
+
embedded_representors = @representor_hash.embedded.map do |name, values|
|
86
|
+
if values.is_a?(Array)
|
87
|
+
several_representors = values.map do |value|
|
88
|
+
Representor.new(value)
|
89
|
+
end
|
90
|
+
[name, several_representors]
|
91
|
+
else
|
92
|
+
[name, Representor.new(values)]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
Hash[embedded_representors]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [Array] who's elements are all <Representors:Transition> objects
|
100
|
+
def meta_links
|
101
|
+
@meta_links ||= begin
|
102
|
+
links_from_transitions = {}
|
103
|
+
|
104
|
+
transitions.each do |transition|
|
105
|
+
if META_LINK_FIELDS.include?(transition.rel)
|
106
|
+
links_from_transitions[transition.rel.to_sym] = transition.uri
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
@representor_hash.links.merge(links_from_transitions).map do |k, v|
|
111
|
+
Representors::Transition.new({rel: k, href: v})
|
112
|
+
end.uniq { |transition| [transition.rel, transition.uri] }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
# @return [Array] who's elements are all <Representors:Transition> objects
|
116
|
+
def transitions
|
117
|
+
@transitions ||= begin
|
118
|
+
transition_hashes = (@representor_hash.transitions + embedded_transitions_hashes).uniq do |hash|
|
119
|
+
[hash[:rel], hash[:href]]
|
120
|
+
end
|
121
|
+
transition_hashes.map { |hash| Transition.new(hash) }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [Array] who's elements are all <Representors:Transition> objects from the self links of
|
126
|
+
# embedded items, updating the rel to reflect the embedded items key
|
127
|
+
def embedded_transitions
|
128
|
+
embedded_transitions_hashes.map { |hash| Transition.new(hash) }
|
129
|
+
end
|
130
|
+
|
131
|
+
# @return [Array] who's elements are all <Representors:Option> objects
|
132
|
+
def datalists
|
133
|
+
@datalists ||= begin
|
134
|
+
attributes = transitions.map { |transition| transition.attributes }
|
135
|
+
parameters = transitions.map { |transition| transition.parameters }
|
136
|
+
fields = [attributes, parameters].flatten
|
137
|
+
options = fields.map { |field| field.options }
|
138
|
+
options.select { |o| o.datalist? }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def embedded_transitions_hashes
|
145
|
+
@representor_hash.embedded.flat_map do |k,*v|
|
146
|
+
v.flatten.map do |item|
|
147
|
+
trans_hash = item[:transitions].find { |t| t[:rel] == "self" }
|
148
|
+
if trans_hash
|
149
|
+
profile_href = item[:links]["profile"] if item[:links]
|
150
|
+
trans_hash = trans_hash.merge(profile: profile_href) if profile_href
|
151
|
+
trans_hash.merge(rel: k)
|
152
|
+
else
|
153
|
+
{}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'representor_support/utilities'
|
2
|
+
require 'representors/representor_hash'
|
3
|
+
|
4
|
+
module Representors
|
5
|
+
|
6
|
+
##
|
7
|
+
# Builder has methods to abstract the construction of Representor objects
|
8
|
+
# In the present implementation it will create a hash of a specific format to
|
9
|
+
# Initialize the Representor with, this will create classess with it.
|
10
|
+
class RepresentorBuilder
|
11
|
+
include RepresentorSupport::Utilities
|
12
|
+
|
13
|
+
HREF_KEY = :href
|
14
|
+
DATA_KEY = :data
|
15
|
+
|
16
|
+
def initialize(representor_hash = {})
|
17
|
+
@representor_hash = RepresentorHash.new(representor_hash)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a hash usable by the representor class
|
21
|
+
def to_representor_hash
|
22
|
+
@representor_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adds an attribute to the Representor. We are creating a hash where the keys are the
|
26
|
+
# names of the attributes
|
27
|
+
def add_attribute(name, value, options={})
|
28
|
+
new_representor_hash = RepresentorHash.new(deep_dup(@representor_hash.to_h))
|
29
|
+
new_representor_hash.attributes[name] = options.merge({value: value})
|
30
|
+
RepresentorBuilder.new(new_representor_hash)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Adds a transition to the Representor, each transition is a hash of values
|
34
|
+
# The transition collection is an Array
|
35
|
+
def add_transition(rel, href, options={})
|
36
|
+
new_representor_hash = RepresentorHash.new(deep_dup(@representor_hash.to_h))
|
37
|
+
options = symbolize_keys(options)
|
38
|
+
options.delete(:method) if options[:method] == Transition::DEFAULT_METHOD
|
39
|
+
link_values = options.merge({href: href, rel: rel})
|
40
|
+
|
41
|
+
if options[DATA_KEY]
|
42
|
+
link_values[Transition::DESCRIPTORS_KEY] = link_values.delete(DATA_KEY)
|
43
|
+
end
|
44
|
+
|
45
|
+
new_representor_hash.transitions.push(link_values)
|
46
|
+
RepresentorBuilder.new(new_representor_hash)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Adds directly an array to our array of transitions
|
50
|
+
def add_transition_array(rel, array_of_hashes)
|
51
|
+
array_of_hashes.reduce(RepresentorBuilder.new(@representor_hash)) do |memo, transition|
|
52
|
+
transition = symbolize_keys(transition)
|
53
|
+
href = transition.delete(:href)
|
54
|
+
memo = memo.add_transition(rel, href, transition)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_embedded(name, embedded_resource)
|
59
|
+
new_representor_hash = RepresentorHash.new(deep_dup(@representor_hash.to_h))
|
60
|
+
new_representor_hash.embedded[name] = embedded_resource
|
61
|
+
RepresentorBuilder.new(new_representor_hash)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'representor_support/utilities'
|
2
|
+
|
3
|
+
module Representors
|
4
|
+
# This is the structure shared between the builder and the representor.
|
5
|
+
# This class allows to pass all the data to the representor without polluting it with methods
|
6
|
+
# It is supposed to be a fast class (Struct is implemented in C)
|
7
|
+
# The structure looks like this:
|
8
|
+
# id: [string]
|
9
|
+
# doc: [string]
|
10
|
+
# href: [string]
|
11
|
+
# protocol: [string]
|
12
|
+
# attributes: [hash] { key => value }
|
13
|
+
# links: [array of hashes]
|
14
|
+
# transitions: [array of hashes]
|
15
|
+
# embedded: [hash] where each value can be recursively defined by this same structure
|
16
|
+
RepresentorHash = Struct.new(:id, :doc, :href, :protocol, :attributes, :embedded, :links, :transitions) do
|
17
|
+
include RepresentorSupport::Utilities
|
18
|
+
|
19
|
+
DEFAULT_HASH_VALUES = {
|
20
|
+
id: nil,
|
21
|
+
doc: nil,
|
22
|
+
href: nil,
|
23
|
+
#TODO protocol doesnt belong in representors, remove it
|
24
|
+
protocol: nil,
|
25
|
+
#TODO fix this, make it the same interface as transitions
|
26
|
+
links: {},
|
27
|
+
attributes: {},
|
28
|
+
transitions: [],
|
29
|
+
embedded: {}
|
30
|
+
}.each_value(&:freeze).freeze
|
31
|
+
|
32
|
+
|
33
|
+
# be able to create from a hash
|
34
|
+
def initialize(hash = {})
|
35
|
+
DEFAULT_HASH_VALUES.each { |k, v| self[k] = dup_or_self(hash[k] || v) }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Be able to generate a new structure with myself and a hash
|
39
|
+
def merge(hash)
|
40
|
+
new_representor_hash = RepresentorHash.new(to_h)
|
41
|
+
hash.each_pair { |k, v| new_representor_hash[k] = v }
|
42
|
+
new_representor_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
# to_h does not exists in Ruby < 2.0
|
46
|
+
def to_h
|
47
|
+
members.each_with_object({}) { |member, hash| hash[member] = self[member] }
|
48
|
+
end
|
49
|
+
|
50
|
+
def empty?
|
51
|
+
members.all? { |k| self[k].nil? || self[k].empty? }
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(other)
|
55
|
+
members.all? { |k| self[k] == other[k] }
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'representors/serialization/serialization_base'
|
2
|
+
require 'representors/serialization/deserializer_factory'
|
3
|
+
|
4
|
+
module Representors
|
5
|
+
class DeserializerBase < SerializationBase
|
6
|
+
|
7
|
+
def initialize(target)
|
8
|
+
@target = target.empty? ? {} : target
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.inherited(subclass)
|
12
|
+
DeserializerFactory.register_deserializers(subclass)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns back a class with all the information of the document and with convenience methods
|
16
|
+
# to access it.
|
17
|
+
# TODO: Yield builder to factor out builder dependency.
|
18
|
+
def to_representor
|
19
|
+
Representor.new(to_representor_hash)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a hash representation of the data. This is useful to merge with new data which may
|
23
|
+
# be built by different builders. In this class we use it to support embedded resources.
|
24
|
+
def to_representor_hash(options = {})
|
25
|
+
raise "Abstract method #to_representor_hash not implemented in #{self.name} deserializer class."
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'representors/serialization/serialization_factory_base'
|
2
|
+
|
3
|
+
module Representors
|
4
|
+
class DeserializerFactory < SerializationFactoryBase
|
5
|
+
def self.register_deserializers(*serializers)
|
6
|
+
register_serialization_classes(*serializers)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.registered_deserializers
|
10
|
+
registered_serialization_classes
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
require 'representors/serialization/hale_deserializer'
|
3
|
+
|
4
|
+
module Representors
|
5
|
+
##
|
6
|
+
# Deserializes the HAL format as specified in http://stateless.co/hal_specification.html
|
7
|
+
# For examples of how this format looks like check the files under spec/fixtures/hal
|
8
|
+
# TODO: support Curies http://www.w3.org/TR/2010/NOTE-curie-20101216/
|
9
|
+
class HalDeserializer < HaleDeserializer
|
10
|
+
media_symbol :hal
|
11
|
+
media_type 'application/hal+json', 'application/json'
|
12
|
+
|
13
|
+
HAL_LINK_KEYS = %w(href templated type deprecation name profile title hreflang)
|
14
|
+
@reserved_keys = [LINKS_KEY, EMBEDDED_KEY]
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def builder_add_from_deserialized(builder, media)
|
19
|
+
builder = deserialize_properties(builder, media)
|
20
|
+
builder = deserialize_links(builder, media)
|
21
|
+
builder = deserialize_embedded(builder, media)
|
22
|
+
end
|
23
|
+
|
24
|
+
def deserialize_links(builder, media)
|
25
|
+
(media[LINKS_KEY] || {}).each do |link_rel, link_values|
|
26
|
+
link_values = [link_values] unless link_values.is_a?(Array)
|
27
|
+
link_values = link_values.map do |hash|
|
28
|
+
hash.select { |k,_| HAL_LINK_KEYS.include?(k) }
|
29
|
+
end
|
30
|
+
ensure_valid_links!(link_rel, link_values)
|
31
|
+
builder = builder.add_transition_array(link_rel, link_values)
|
32
|
+
end
|
33
|
+
builder
|
34
|
+
end
|
35
|
+
|
36
|
+
def deserialize_properties(builder, media)
|
37
|
+
media.each do |k,v|
|
38
|
+
builder = builder.add_attribute(k, v) unless (self.class.reserved_keys.include?(k))
|
39
|
+
end
|
40
|
+
builder
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'representors/serialization/serializer_base'
|
2
|
+
|
3
|
+
module Representors
|
4
|
+
module Serialization
|
5
|
+
class HalSerializer < SerializerBase
|
6
|
+
LINKS_KEY = "_links"
|
7
|
+
EMBEDDED_KEY = "_embedded"
|
8
|
+
LINKS_ONLY_OPTION = :embed_links_only
|
9
|
+
|
10
|
+
media_symbol :hal_json
|
11
|
+
media_type 'application/hal+json'
|
12
|
+
|
13
|
+
# This is public and returning a hash to be able to implement embedded resources
|
14
|
+
# serialization
|
15
|
+
# TODO: make this private and merge with to_media_type
|
16
|
+
# The name is quite misleading,
|
17
|
+
def to_hash(options = {})
|
18
|
+
base_hash, links, embedded_hals = common_serialization(@target)
|
19
|
+
base_hash.merge!(links.merge(embedded_hals.(options)))
|
20
|
+
base_hash
|
21
|
+
end
|
22
|
+
|
23
|
+
# This is the main entry of this class. It returns a serialization of the data
|
24
|
+
# in a given media type.
|
25
|
+
def to_media_type(options = {})
|
26
|
+
to_hash.to_json
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def common_serialization(representor)
|
32
|
+
base_hash = get_semantics(representor)
|
33
|
+
embedded_hals = ->(options) { get_embedded_objects(representor, options) }
|
34
|
+
# we want to group by rel name because it is possible to have several transitions with the same
|
35
|
+
# rel name. This will become an array in the output. For instance an items array
|
36
|
+
# with links to each item
|
37
|
+
grouped_transitions = (representor.transitions + representor.meta_links).group_by { |transition| transition[:rel] }
|
38
|
+
links = build_links(grouped_transitions)
|
39
|
+
links = links.empty? ? {} : { LINKS_KEY => links.reduce({}, :merge) }
|
40
|
+
[base_hash, links, embedded_hals]
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_semantics(representor)
|
44
|
+
representor.properties
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_embedded_objects(representor, options)
|
48
|
+
@get_embedded_objects ||= if representor.embedded.empty? || options.has_key?(LINKS_ONLY_OPTION)
|
49
|
+
{}
|
50
|
+
else
|
51
|
+
embedded_elements = representor.embedded.map { |k, v| build_embedded_objects(k, v) }
|
52
|
+
{ EMBEDDED_KEY => embedded_elements.reduce({}, :merge) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Lambda used in this case to DRY code. Allows 'is array' functionality to be handled elsewhere
|
57
|
+
def build_embedded_objects(key, embedded)
|
58
|
+
make_media_type = ->(obj) { self.class.new(obj).to_hash }
|
59
|
+
embed = map_or_apply(make_media_type, embedded)
|
60
|
+
{ key => embed}
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param [Hash] transitions. A hash on the shape "rel_name" => [Transition]
|
64
|
+
# The value for the rel_name usually will have only one transition but when we are building an
|
65
|
+
# array of transitions will have many.
|
66
|
+
# @return [Array] Array of hashes with the format [ { rel_name => {link_info1}}, {rel_name2 => ... }]
|
67
|
+
def build_links(transitions)
|
68
|
+
transitions.map do |rel_name, transition_array|
|
69
|
+
links = transition_array.map{|transition| build_links_for_this_media_type(transition)}
|
70
|
+
if links.size > 1
|
71
|
+
{rel_name => links}
|
72
|
+
else
|
73
|
+
{rel_name => links.first}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# This method can be overriden by other classes
|
79
|
+
# @param transition , a single tansition
|
80
|
+
def build_links_for_this_media_type(transition)
|
81
|
+
link = if transition.templated?
|
82
|
+
{ href: transition.templated_uri, templated: true }
|
83
|
+
else
|
84
|
+
{ href: transition.uri }
|
85
|
+
end
|
86
|
+
[:name, :profile].each { |key| link[key] = transition[key] if transition.has_key?(key) }
|
87
|
+
link
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|