representors 0.0.5
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.
- 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
|