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,162 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'representors/serialization/deserializer_base'
|
3
|
+
require 'representors/errors'
|
4
|
+
require 'representor_support/utilities'
|
5
|
+
|
6
|
+
|
7
|
+
module Representors
|
8
|
+
# Class for a Hale document deserializer.
|
9
|
+
# Built against Hale version 0.0.1, https://github.com/mdsol/hale/tree/0-0-stable
|
10
|
+
# @since 0.0.2
|
11
|
+
class HaleDeserializer < DeserializerBase
|
12
|
+
media_symbol :hale
|
13
|
+
media_type 'application/vnd.hale+json'
|
14
|
+
|
15
|
+
META_KEY = '_meta'.freeze
|
16
|
+
REF_KEY = '_ref'.freeze
|
17
|
+
DATA_KEY = 'data'.freeze
|
18
|
+
OPTIONS_KEY = 'options'.freeze
|
19
|
+
LINKS_KEY = '_links'.freeze
|
20
|
+
EMBEDDED_KEY = '_embedded'.freeze
|
21
|
+
CURIE_KEY = 'curies'.freeze
|
22
|
+
HREF = 'href'.freeze
|
23
|
+
@reserved_keys = [LINKS_KEY, EMBEDDED_KEY, META_KEY, REF_KEY]
|
24
|
+
|
25
|
+
RESERVED_FIELD_VALUES = Field::SIMPLE_METHODS + [Field::NAME_KEY, Field::SCOPE_KEY, Field::OPTIONS_KEY, Field::VALIDATORS_KEY, Field::DESCRIPTORS_KEY, DATA_KEY]
|
26
|
+
|
27
|
+
# This need to be public to support embedded data
|
28
|
+
# TODO: make this private
|
29
|
+
def to_representor_hash
|
30
|
+
media = @target.is_a?(Hash) ? @target : JSON.parse(@target)
|
31
|
+
builder_add_from_deserialized(RepresentorBuilder.new, media).to_representor_hash
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def self.reserved_keys
|
37
|
+
@reserved_keys
|
38
|
+
end
|
39
|
+
|
40
|
+
def builder_add_from_deserialized(builder, media)
|
41
|
+
media = dereference_meta_media(media)
|
42
|
+
builder = deserialize_properties(builder, media)
|
43
|
+
builder = deserialize_links(builder, media)
|
44
|
+
builder = deserialize_embedded(builder, media)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Properties are normal JSON keys in the Hale document. Create properties in the resulting object
|
48
|
+
def deserialize_properties(builder, media)
|
49
|
+
media.each do |k,v|
|
50
|
+
builder = builder.add_attribute(k, v) unless (self.class.reserved_keys.include?(k))
|
51
|
+
end
|
52
|
+
builder
|
53
|
+
end
|
54
|
+
|
55
|
+
# links are under '_links' in the original document. Links always have a key (its name) but
|
56
|
+
# the value can be a hash with its properties or an array with several links.
|
57
|
+
# TODO: Figure out error handling for malformed documents
|
58
|
+
def deserialize_links(builder, media)
|
59
|
+
links = media[LINKS_KEY] || {}
|
60
|
+
|
61
|
+
links.each do |link_rel,link_values|
|
62
|
+
raise(DeserializationError, 'CURIE support not implemented for HAL') if link_rel.eql?(CURIE_KEY)
|
63
|
+
if link_values.is_a?(Array)
|
64
|
+
if link_values.any? { |link| link[HREF].nil? }
|
65
|
+
raise DeserializationError, 'All links must contain the href attribute'
|
66
|
+
end
|
67
|
+
builder = builder.add_transition_array(link_rel, link_values)
|
68
|
+
else
|
69
|
+
href = link_values[HREF]
|
70
|
+
raise DeserializationError, 'All links must contain the href attribute' unless href
|
71
|
+
builder = builder.add_transition(link_rel, href, link_values )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
builder
|
76
|
+
end
|
77
|
+
|
78
|
+
# embedded resources are under '_embedded' in the original document, similarly to links they can
|
79
|
+
# contain an array or a single embedded resource. An embedded resource is a full document so
|
80
|
+
# we create a new HaleDeserializer for each.
|
81
|
+
def deserialize_embedded(builder, media)
|
82
|
+
make_embedded_resource = ->(x) { self.class.new(x).to_representor_hash.to_h }
|
83
|
+
(media[EMBEDDED_KEY] || {}).each do |name, value|
|
84
|
+
resource_hash = map_or_apply(make_embedded_resource, value)
|
85
|
+
builder = builder.add_embedded(name, resource_hash)
|
86
|
+
end
|
87
|
+
builder
|
88
|
+
end
|
89
|
+
|
90
|
+
def deserialize_links(builder, media)
|
91
|
+
(media[LINKS_KEY] || {}).each do |link_rel, link_values|
|
92
|
+
link_values = [link_values] unless link_values.is_a?(Array)
|
93
|
+
ensure_valid_links!(link_rel, link_values)
|
94
|
+
link_values = parse_validators(link_values)
|
95
|
+
link_values = parse_options(link_values)
|
96
|
+
builder = builder.add_transition_array(link_rel, link_values)
|
97
|
+
end
|
98
|
+
builder
|
99
|
+
end
|
100
|
+
|
101
|
+
def ensure_valid_links!(link_rel, link_values_array)
|
102
|
+
raise(DeserializationError, 'CURIE support not implemented for HAL') if link_rel.eql?(CURIE_KEY)
|
103
|
+
|
104
|
+
if link_values_array.map { |link| link[HREF] }.any?(&:nil?)
|
105
|
+
raise DeserializationError, 'All links must contain the href attribute'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def deep_find_and_transform!(obj, target_key, &blk)
|
110
|
+
if obj.respond_to?(:key) && obj.key?(target_key)
|
111
|
+
deep_find_and_transform!(obj[target_key], target_key, &blk)
|
112
|
+
yield obj
|
113
|
+
elsif [Array, Hash].include?(obj.class)
|
114
|
+
obj.each { |*el| deep_find_and_transform!(el.last, target_key, &blk) }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def dereference_meta_media(media)
|
119
|
+
media = deep_dup(media)
|
120
|
+
# Remove _meta from media to prevent serialization as property
|
121
|
+
if meta_info = media.delete(META_KEY)
|
122
|
+
deep_find_and_transform!(media, REF_KEY) do |media|
|
123
|
+
media.delete(REF_KEY).each { |ref| media[ref] = meta_info[ref] }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
media
|
127
|
+
end
|
128
|
+
|
129
|
+
def parse_options(media)
|
130
|
+
media = deep_dup(media)
|
131
|
+
deep_find_and_transform!(media, OPTIONS_KEY) { |media| parse_options!(media) }
|
132
|
+
media
|
133
|
+
end
|
134
|
+
|
135
|
+
def parse_options!(media)
|
136
|
+
if media[OPTIONS_KEY].is_a?(Array) && media[OPTIONS_KEY].first.is_a?(Hash)
|
137
|
+
new_options = media[OPTIONS_KEY].reduce({}) { |memo, hash| memo.merge!(hash) }
|
138
|
+
media[OPTIONS_KEY] = { 'hash' => new_options }
|
139
|
+
elsif !media[OPTIONS_KEY].is_a?(Hash)
|
140
|
+
media[OPTIONS_KEY] = { 'list' => deep_dup(media[OPTIONS_KEY]) }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def parse_validators(media)
|
145
|
+
media = deep_dup(media)
|
146
|
+
deep_find_and_transform!(media, DATA_KEY) { |media| parse_data!(media) }
|
147
|
+
media
|
148
|
+
end
|
149
|
+
|
150
|
+
def parse_data!(media)
|
151
|
+
media[DATA_KEY].each do |field_key, field_value|
|
152
|
+
arr = []
|
153
|
+
field_value.each do |k,v|
|
154
|
+
arr << {k => media[DATA_KEY][field_key].delete(k)} unless RESERVED_FIELD_VALUES.include?(k.to_sym)
|
155
|
+
end
|
156
|
+
media[DATA_KEY][field_key][Field::VALIDATORS_KEY] = arr unless arr.empty?
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'representors/serialization/hal_serializer'
|
2
|
+
|
3
|
+
module Representors
|
4
|
+
module Serialization
|
5
|
+
class HaleSerializer < HalSerializer
|
6
|
+
media_symbol :hale_json
|
7
|
+
media_type 'application/vnd.hale+json'
|
8
|
+
|
9
|
+
SEMANTIC_TYPES = {
|
10
|
+
select: "text", #No way in Crichton to distinguish [Int] and [String]
|
11
|
+
search:"text",
|
12
|
+
text: "text",
|
13
|
+
boolean: "bool", #a Server should accept ?cat&dog or ?cat=cat&dog=dog
|
14
|
+
number: "number",
|
15
|
+
email: "text",
|
16
|
+
tel: "text",
|
17
|
+
datetime: "text",
|
18
|
+
time: "text",
|
19
|
+
date: "text",
|
20
|
+
month: "text",
|
21
|
+
week: "text",
|
22
|
+
object: "object",
|
23
|
+
:"datetime-local" => "text"
|
24
|
+
}
|
25
|
+
# This is public and returning a hash to be able to implement embedded resources
|
26
|
+
# serialization
|
27
|
+
# TODO: make this private and merge with to_media_type
|
28
|
+
# The name is quite misleading,
|
29
|
+
def to_hash(options ={})
|
30
|
+
base_hash, links, embedded_hales = common_serialization(@target)
|
31
|
+
meta = get_data_lists(@target)
|
32
|
+
base_hash.merge!(meta).merge!(links).merge!(embedded_hales.(options))
|
33
|
+
base_hash
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# This is the main entry of this class. It returns a serialization of the data
|
38
|
+
# in a given media type.
|
39
|
+
def to_media_type(options = {})
|
40
|
+
to_hash(options).to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def get_data_lists(representor)
|
46
|
+
meta = {}
|
47
|
+
representor.datalists.each do |datalist|
|
48
|
+
meta[datalist.id] = datalist.to_data
|
49
|
+
end
|
50
|
+
meta.empty? ? {} : {'_meta' => meta }
|
51
|
+
end
|
52
|
+
|
53
|
+
def elemental_renderer(etype)
|
54
|
+
{
|
55
|
+
type: ->(element) { render_type(element.field_type,element.type) if element.field_type || element.type },
|
56
|
+
scope: ->(element) { element.scope unless element.scope == 'attribute' },
|
57
|
+
value: ->(element) { element.value unless element.value.nil? },
|
58
|
+
multi: ->(element) { true if element.cardinality == "multiple" },
|
59
|
+
data: ->(element) { render_data_elements(element.descriptors) if element.type == 'object' },
|
60
|
+
}[etype]
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_data_validators(element)
|
64
|
+
element.validators.reduce({}) do |results, validator|
|
65
|
+
results.merge( validator.is_a?(Hash) ? validator : {validator => true} )
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_data_properties(element)
|
70
|
+
[:type, :scope, :value, :multi, :data].reduce({}) do |result, symbol|
|
71
|
+
elemental = elemental_renderer(symbol).call(element)
|
72
|
+
result.merge( elemental.nil? ? {} : {symbol => elemental} )
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_data_element(element)
|
77
|
+
options = if element.options.datalist?
|
78
|
+
{ '_ref' => [element.options.id] }
|
79
|
+
elsif element.options.type == Representors::Options::HASH_TYPE
|
80
|
+
element.options.to_hash.map { |option| Hash[*option] }
|
81
|
+
else
|
82
|
+
element.options.to_list
|
83
|
+
end
|
84
|
+
element_data = get_data_validators(element)
|
85
|
+
elementals = get_data_properties(element)
|
86
|
+
elementals[:options] = options unless options.empty?
|
87
|
+
{ element.name => element_data.merge(elementals) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def render_data_elements(elements)
|
91
|
+
elements.reduce({}) do |results, element|
|
92
|
+
results.merge( get_data_element(element) )
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_links_for_this_media_type(transition)
|
97
|
+
link = super(transition) #default Hal serialization
|
98
|
+
# below add fields specific for Hale
|
99
|
+
data_elements = render_data_elements(transition.descriptors)
|
100
|
+
link[:data] = data_elements unless data_elements.empty?
|
101
|
+
link[:method] = transition.interface_method unless transition.interface_method == Transition::DEFAULT_METHOD
|
102
|
+
link
|
103
|
+
end
|
104
|
+
|
105
|
+
def render_type(field_type, type = SEMANTIC_TYPES[field_type.to_sym])
|
106
|
+
field_type ? "#{type}:#{field_type}" : "#{type}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'representor_support/utilities'
|
2
|
+
|
3
|
+
module Representors
|
4
|
+
class SerializationBase
|
5
|
+
include RepresentorSupport::Utilities
|
6
|
+
|
7
|
+
attr_reader :target
|
8
|
+
|
9
|
+
def self.media_symbols
|
10
|
+
@media_symbols ||= Set.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.media_types
|
14
|
+
@media_types ||= Set.new
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def self.media_symbol(*symbol)
|
19
|
+
@media_symbols = media_symbols | symbol
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.media_type(*media)
|
23
|
+
@media_types = media_types | media
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'representors/errors'
|
2
|
+
|
3
|
+
module Representors
|
4
|
+
##
|
5
|
+
# Base class for factories that manages the registration of serialization classes and the common factory interface.
|
6
|
+
class SerializationFactoryBase
|
7
|
+
def self.build(media_type, object)
|
8
|
+
klass = serialization_class(media_type)
|
9
|
+
if klass
|
10
|
+
klass.new(object)
|
11
|
+
else
|
12
|
+
raise UnknownMediaTypeError, "Unknown media-type: #{media_type}."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.media_symbol_mapping
|
17
|
+
@media_symbol_mapping ||= registered_serialization_classes.map do |serialization_class|
|
18
|
+
serialization_class.media_symbols.map { |media_symbol| { media_symbol => serialization_class } }.reduce(:merge)
|
19
|
+
end.reduce(:merge)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.media_type_mapping
|
23
|
+
@media_type_mapping ||= registered_serialization_classes.map do |serializer|
|
24
|
+
serializer.media_types.map { |media_type| { media_type => serializer.media_symbols.first } }.reduce(:merge)
|
25
|
+
end.reduce(:merge)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
private
|
30
|
+
def self.register_serialization_classes(*serializers)
|
31
|
+
clear_memoization
|
32
|
+
@_registered_serialization_classes ||= []
|
33
|
+
@_registered_serialization_classes |= serializers
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.clear_memoization
|
37
|
+
@registered_serialization_classes = nil
|
38
|
+
@media_symbol_mapping = nil
|
39
|
+
@media_type_mapping = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.registered_serialization_classes
|
43
|
+
@registered_serialization_classes ||= @_registered_serialization_classes.dup.freeze
|
44
|
+
end
|
45
|
+
|
46
|
+
# If a client send directly a Content-Type it may have encodings or other things so we want
|
47
|
+
# to be more flexible
|
48
|
+
def self.serialization_class(media_type)
|
49
|
+
symbol = media_type.is_a?(Symbol) ? media_type : media_type_mapping[media_type]
|
50
|
+
media_symbol_mapping[symbol]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'representors/serialization/serialization_base'
|
2
|
+
require 'representors/serialization/serializer_factory'
|
3
|
+
|
4
|
+
module Representors
|
5
|
+
class SerializerBase < SerializationBase
|
6
|
+
|
7
|
+
def initialize(target)
|
8
|
+
@target = target.empty? ? Representor.new({}) : target
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.inherited(subclass)
|
12
|
+
SerializerFactory.register_serializers(subclass)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_hash(options = {})
|
16
|
+
raise "Abstract method #to_hash not implemented in #{self.class.to_s} serializer class."
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'representors/serialization/serialization_factory_base'
|
2
|
+
|
3
|
+
module Representors
|
4
|
+
class SerializerFactory < SerializationFactoryBase
|
5
|
+
def self.register_serializers(*serializers)
|
6
|
+
register_serialization_classes(*serializers)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.registered_serializers
|
10
|
+
registered_serialization_classes
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.serializer?(serializer_name)
|
14
|
+
registered_serializers.any? { |serializer| serializer.media_symbol.include?(serializer_name.to_sym) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'addressable/template'
|
2
|
+
|
3
|
+
module Representors
|
4
|
+
##
|
5
|
+
# Manages the respresentation of link elements for hypermedia messages.
|
6
|
+
class Transition
|
7
|
+
REL_KEY = :rel
|
8
|
+
HREF_KEY = :href
|
9
|
+
LINKS_KEY = :links
|
10
|
+
METHOD_KEY = :method
|
11
|
+
DESCRIPTORS_KEY = :descriptors
|
12
|
+
DEFAULT_METHOD = 'GET'
|
13
|
+
PARAMETER_FIELDS = 'href'
|
14
|
+
ATTRIBUTE_FIELDS = 'attribute'
|
15
|
+
URL_TEMPLATE = "%s{?%s}"
|
16
|
+
|
17
|
+
# @example
|
18
|
+
# hash = {rel: "self", href: "http://example.org"}
|
19
|
+
# Transition.new(hash)
|
20
|
+
# Must contain at least the property :href
|
21
|
+
# @param [Hash] the abstract representor hash defining a transition
|
22
|
+
def initialize(transition_hash)
|
23
|
+
@transition_hash = transition_hash
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String] so the user can 'puts' this object
|
27
|
+
def to_s
|
28
|
+
@transition_hash.inspect
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Hash] useful in cucumber steps where the feature file provides a hash
|
32
|
+
def to_hash
|
33
|
+
Hash[@transition_hash.map{ |k, v| [k.to_s, v] }]
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String] The name of the Relationship
|
37
|
+
def rel
|
38
|
+
retrieve(REL_KEY)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String] The URI for the object
|
42
|
+
def uri(data={})
|
43
|
+
template = Addressable::Template.new(retrieve(HREF_KEY))
|
44
|
+
template.expand(data).to_str
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [String] key on the transitions hash to retrieve
|
48
|
+
# @return [String] with the value of the key
|
49
|
+
def [](key)
|
50
|
+
retrieve(key)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param [String] key on the transitions hash to retrieve
|
54
|
+
# @return [Bool] false if there is no key
|
55
|
+
def has_key?(key)
|
56
|
+
!retrieve(key).nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [String] The URI for the object templated against #parameters
|
60
|
+
def templated_uri
|
61
|
+
#URL as it is, it will be the templated URL of the document if it was templated
|
62
|
+
retrieve(HREF_KEY)
|
63
|
+
end
|
64
|
+
|
65
|
+
def templated?
|
66
|
+
# if we have any variable then it is not a templated url
|
67
|
+
!Addressable::Template.new(retrieve(HREF_KEY)).variables.empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Array] who's elements are all <Crichton:Transition> objects
|
71
|
+
def meta_links
|
72
|
+
@meta_links ||= (retrieve(LINKS_KEY) || []).map do |link_key, link_href|
|
73
|
+
Transition.new({rel: link_key, href: link_href})
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [String] representing the Uniform Interface Method
|
78
|
+
def interface_method
|
79
|
+
retrieve(METHOD_KEY) || DEFAULT_METHOD
|
80
|
+
end
|
81
|
+
# The Parameters (i.e. GET variables)
|
82
|
+
#
|
83
|
+
# @return [Array] who's elements are all <Crichton:Field> objects
|
84
|
+
# Variables in the URI template rules this method, we are going to return a field for each of them
|
85
|
+
# if we find a field inside the 'data' of the document describing that variable, we use that information
|
86
|
+
# else we return a field with default information about a variable.
|
87
|
+
def parameters
|
88
|
+
data_fields = descriptors_fields.select{|field| field.scope == PARAMETER_FIELDS }
|
89
|
+
Addressable::Template.new(retrieve(HREF_KEY)).variables.map do |template_variable_name|
|
90
|
+
field_specified = data_fields.find{|field| field.name.to_s == template_variable_name.to_s}
|
91
|
+
if field_specified
|
92
|
+
field_specified
|
93
|
+
else
|
94
|
+
Field.new({template_variable_name.to_sym => {type: 'string', scope: 'href'}})
|
95
|
+
end
|
96
|
+
end
|
97
|
+
# descriptors_fields.select{|field| field.scope == PARAMETER_FIELDS }
|
98
|
+
end
|
99
|
+
|
100
|
+
# The Parameters (i.e. POST variables)
|
101
|
+
#
|
102
|
+
# @return [Array] who's elements are all <Crichton:Field> objects
|
103
|
+
def attributes
|
104
|
+
@attributes ||= descriptors_fields.select{|field| field.scope == ATTRIBUTE_FIELDS }
|
105
|
+
end
|
106
|
+
|
107
|
+
# The Parameters (i.e. GET variables)
|
108
|
+
#
|
109
|
+
# @return [Array] who's elements are all <Crichton:Field> objects
|
110
|
+
def descriptors
|
111
|
+
@descriptions ||= (attributes + parameters)
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def descriptors_fields
|
117
|
+
@fields_hash ||= descriptors_hash.map { |k, v| Field.new({k => v }) }
|
118
|
+
end
|
119
|
+
|
120
|
+
def descriptors_hash
|
121
|
+
@transition_hash[DESCRIPTORS_KEY] || []
|
122
|
+
end
|
123
|
+
|
124
|
+
# accept retrieving keys by symbol or string
|
125
|
+
def retrieve(key)
|
126
|
+
@transition_hash[key.to_sym] || @transition_hash[key.to_s]
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|