hal_presenter 1.2.1 → 1.6.0
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/lib/hal_presenter/attributes.rb +3 -3
- data/lib/hal_presenter/curie_collection.rb +158 -0
- data/lib/hal_presenter/deserializer.rb +8 -3
- data/lib/hal_presenter/embedded.rb +4 -4
- data/lib/hal_presenter/lazy_evaluator.rb +9 -5
- data/lib/hal_presenter/links.rb +26 -17
- data/lib/hal_presenter/model.rb +27 -21
- data/lib/hal_presenter/pagination.rb +4 -3
- data/lib/hal_presenter/policy/dsl.rb +50 -69
- data/lib/hal_presenter/policy/rules.rb +87 -0
- data/lib/hal_presenter/profile.rb +1 -1
- data/lib/hal_presenter/property.rb +15 -2
- data/lib/hal_presenter/serializer.rb +61 -54
- data/lib/hal_presenter/super_init.rb +1 -1
- metadata +24 -23
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af641721e6b137d280c31c60efc6b76e7e1a22dd441a9b65aee3b929954290ec
|
4
|
+
data.tar.gz: 784293b830aeed98f2f1ac1157b0526a8febe8ccbbe40a54dcee85acb8111294
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 829846a3e6a6d86e537817585ccfdc929efc758cf9a748b73c4a9c5d54d0760f8735301a7606a90d6279c2dba9efd1b3b9998a646c336a1656628f42ba779c56
|
7
|
+
data.tar.gz: fd10fbc76409d2b1738b2e5abdd75ba68e6a34592ae6b7ed56f2fcac0f83079eba33b524704de4c29ff168040147e89d82fc0466746049c84b4f1df87bafaaeb
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
@@ -5,10 +5,10 @@ module HALPresenter
|
|
5
5
|
module Attributes
|
6
6
|
include SuperInit
|
7
7
|
|
8
|
-
def attribute(
|
8
|
+
def attribute(name, value = Property::NO_VALUE, **kwargs, &block)
|
9
9
|
kwargs[:context] ||= self
|
10
|
-
attributes.delete_if { |attr| attr.name ==
|
11
|
-
Property.new(
|
10
|
+
attributes.delete_if { |attr| attr.name == name }
|
11
|
+
Property.new(name, value, **kwargs, &block).tap do |attr|
|
12
12
|
attributes << attr
|
13
13
|
end
|
14
14
|
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module HALPresenter
|
4
|
+
class CurieCollection
|
5
|
+
class CurieWithReferences
|
6
|
+
attr_reader :name, :href, :templated, :rels, :references
|
7
|
+
|
8
|
+
def initialize(curie)
|
9
|
+
@name = curie.fetch(:name)
|
10
|
+
@href = curie.fetch(:href)
|
11
|
+
@templated = curie.fetch(:templated, true)
|
12
|
+
@rels = Hash.new { |hash, key| hash[key] = Set.new }
|
13
|
+
@references = Hash.new do |hash, key|
|
14
|
+
hash[key] = Set.new.compare_by_identity
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_reference(rel, reference, type)
|
19
|
+
rels[type] << rel
|
20
|
+
references[type] << reference
|
21
|
+
end
|
22
|
+
|
23
|
+
def <<(other)
|
24
|
+
other.rels.each do |type, rels|
|
25
|
+
self.rels[type] += rels
|
26
|
+
end
|
27
|
+
other.references.each do |type, references|
|
28
|
+
self.references[type] += references
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def rename(name)
|
33
|
+
self.name = name
|
34
|
+
|
35
|
+
rels.each do |type, rels|
|
36
|
+
rels.each do |rel|
|
37
|
+
new_rel = replace_curie(name, rel)
|
38
|
+
references[type].each do |reference|
|
39
|
+
reference[new_rel] = reference.delete(rel)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_h
|
46
|
+
{
|
47
|
+
name: name,
|
48
|
+
href: href,
|
49
|
+
templated: templated
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
attr_writer :name
|
56
|
+
|
57
|
+
def replace_curie(name, rel)
|
58
|
+
_, rest = rel.to_s.split(':', 2)
|
59
|
+
:"#{name}:#{rest}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_reader :collection
|
64
|
+
|
65
|
+
def self.extract_from!(hash, resolve_collisions: true)
|
66
|
+
new.tap do |curies|
|
67
|
+
curies.add_curies(hash[:_links]&.delete(:curies))
|
68
|
+
curies.send(:add_references, hash[:_links], :links)
|
69
|
+
curies.send(:add_references, hash[:_embedded], :embedded)
|
70
|
+
curies.resolve_collisions! if resolve_collisions
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize
|
75
|
+
@collection = []
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_curies(curies)
|
79
|
+
return unless curies
|
80
|
+
|
81
|
+
curies.each do |curie|
|
82
|
+
next if find(curie[:name])
|
83
|
+
collection << CurieWithReferences.new(curie)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def generate_curie_name(base)
|
88
|
+
name = "#{base}0"
|
89
|
+
name = name.next while find(name.to_sym)
|
90
|
+
name.to_sym
|
91
|
+
end
|
92
|
+
|
93
|
+
def resolve_collisions!
|
94
|
+
collection.reverse_each do |curie|
|
95
|
+
next if collection.none? { |c| c.name == curie.name && c.href != curie.href }
|
96
|
+
new_name = generate_curie_name(curie.name)
|
97
|
+
curie.rename new_name
|
98
|
+
end
|
99
|
+
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_a
|
104
|
+
collection.map(&:to_h)
|
105
|
+
end
|
106
|
+
|
107
|
+
def empty?
|
108
|
+
collection.empty?
|
109
|
+
end
|
110
|
+
|
111
|
+
def each
|
112
|
+
return collection.each unless block_given?
|
113
|
+
collection.each { |c| yield c }
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def find(name, href = nil)
|
119
|
+
return unless name
|
120
|
+
|
121
|
+
collection.find do |c|
|
122
|
+
next unless c.name.to_sym == name.to_sym
|
123
|
+
href.nil? || c.href == href
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def curie_from(rel)
|
128
|
+
parts = rel.to_s.split(':')
|
129
|
+
parts.first if parts.size > 1
|
130
|
+
end
|
131
|
+
|
132
|
+
def concat(other)
|
133
|
+
other.each do |curie|
|
134
|
+
if existing = find(curie.name, curie.href)
|
135
|
+
existing << curie
|
136
|
+
else
|
137
|
+
collection << curie
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def add_references(reference, type)
|
143
|
+
return unless reference
|
144
|
+
|
145
|
+
reference.each do |rel, values|
|
146
|
+
curie_name = curie_from(rel)
|
147
|
+
curie = find(curie_name)
|
148
|
+
curie&.add_reference(rel, reference, type)
|
149
|
+
|
150
|
+
values = [values] if values.is_a? Hash
|
151
|
+
values.each do |value|
|
152
|
+
nested = self.class.extract_from!(value, resolve_collisions: false)
|
153
|
+
concat nested.collection
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -1,15 +1,20 @@
|
|
1
1
|
require 'json'
|
2
2
|
|
3
3
|
module HALPresenter
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
module ClassMethods
|
5
|
+
def from_hal(presenter, payload, resource = nil)
|
6
|
+
presenter.from_hal(payload, resource)
|
7
|
+
end
|
7
8
|
end
|
8
9
|
|
9
10
|
module Deserializer
|
10
11
|
|
11
12
|
class Error < StandardError; end
|
12
13
|
|
14
|
+
def self.included(base)
|
15
|
+
base.extend ClassMethods
|
16
|
+
end
|
17
|
+
|
13
18
|
def from_hal(payload, resource = nil)
|
14
19
|
return if payload.nil? || payload.empty?
|
15
20
|
hash = JSON.parse(payload)
|
@@ -8,16 +8,16 @@ module HALPresenter
|
|
8
8
|
class Embed < HALPresenter::Property
|
9
9
|
attr_reader :presenter_class
|
10
10
|
|
11
|
-
def initialize(name, value =
|
11
|
+
def initialize(name, value = NO_VALUE, **kwargs, &block)
|
12
12
|
@presenter_class = kwargs.delete(:presenter_class)
|
13
13
|
super(name, value, **kwargs, &block)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
def embed(
|
17
|
+
def embed(name, value = Property::NO_VALUE, **kwargs, &block)
|
18
18
|
kwargs[:context] ||= self
|
19
|
-
embedded.delete_if { |embed| embed.name ==
|
20
|
-
Embed.new(
|
19
|
+
embedded.delete_if { |embed| embed.name == name }
|
20
|
+
Embed.new(name, value, **kwargs, &block).tap do |embed|
|
21
21
|
embedded << embed
|
22
22
|
end
|
23
23
|
end
|
@@ -7,7 +7,7 @@ module HALPresenter
|
|
7
7
|
|
8
8
|
def initialize(block, context)
|
9
9
|
@context = context
|
10
|
-
define_singleton_method(:
|
10
|
+
define_singleton_method(:call, &block)
|
11
11
|
end
|
12
12
|
|
13
13
|
def update_context(context)
|
@@ -16,17 +16,21 @@ module HALPresenter
|
|
16
16
|
|
17
17
|
def evaluate(resource, options)
|
18
18
|
@resource = resource
|
19
|
-
@options = options || {}
|
20
|
-
|
19
|
+
@options = (options || {}).dup
|
20
|
+
call
|
21
21
|
ensure
|
22
|
-
|
23
|
-
@options = nil
|
22
|
+
clear_state
|
24
23
|
end
|
25
24
|
|
26
25
|
private
|
27
26
|
|
28
27
|
attr_reader :context
|
29
28
|
|
29
|
+
def clear_state
|
30
|
+
@resource = nil
|
31
|
+
@options = nil
|
32
|
+
end
|
33
|
+
|
30
34
|
def method_missing(method, *args, &block)
|
31
35
|
return super unless context.respond_to?(method)
|
32
36
|
|
data/lib/hal_presenter/links.rb
CHANGED
@@ -3,14 +3,16 @@ require 'hal_presenter/super_init'
|
|
3
3
|
|
4
4
|
module HALPresenter
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
module ClassMethods
|
7
|
+
def base_href=(base)
|
8
|
+
@base_href = base&.sub(%r(/*$), '')
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
def href(href)
|
12
|
+
return href if (@base_href ||= '').empty?
|
13
|
+
return href if href =~ %r(\A(\w+://)?[^/])
|
14
|
+
@base_href + href
|
15
|
+
end
|
14
16
|
end
|
15
17
|
|
16
18
|
module Links
|
@@ -22,7 +24,7 @@ module HALPresenter
|
|
22
24
|
|
23
25
|
alias rel name
|
24
26
|
|
25
|
-
def initialize(rel, value =
|
27
|
+
def initialize(rel, value = NO_VALUE, **kwargs, &block)
|
26
28
|
@type = kwargs[:type].freeze
|
27
29
|
@deprecation = kwargs[:deprecation].freeze
|
28
30
|
@profile = kwargs[:profile].freeze
|
@@ -44,25 +46,32 @@ module HALPresenter
|
|
44
46
|
href = value(resource, options)
|
45
47
|
return {} unless href
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
{href: HALPresenter.href(href)}.tap do |hash|
|
50
|
+
hash[:type] = type if type
|
51
|
+
hash[:deprecation] = deprecation if deprecation
|
52
|
+
hash[:profile] = profile if profile
|
53
|
+
hash[:title] = title if title
|
54
|
+
hash[:templated] = templated if templated
|
53
55
|
end
|
54
|
-
|
55
|
-
{rel => hash}
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
+
def self.included(base)
|
60
|
+
base.extend ClassMethods
|
61
|
+
end
|
62
|
+
|
59
63
|
def link(rel, value = nil, **kwargs, &block)
|
60
64
|
if value.nil? && !block_given?
|
61
65
|
raise 'link must be called with non nil value or be given a block'
|
62
66
|
end
|
63
67
|
|
64
68
|
kwargs[:context] ||= self
|
65
|
-
|
69
|
+
rel = rel.to_sym
|
70
|
+
|
71
|
+
if rel == :self || kwargs[:replace_parent]
|
72
|
+
links.delete_if { |link| link.rel == rel }
|
73
|
+
end
|
74
|
+
|
66
75
|
Link.new(rel, value, **kwargs, &block).tap do |link|
|
67
76
|
links << link
|
68
77
|
end
|
data/lib/hal_presenter/model.rb
CHANGED
@@ -1,34 +1,40 @@
|
|
1
1
|
module HALPresenter
|
2
2
|
@presenters = {}
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module ClassMethods
|
5
|
+
def register(model:, presenter:)
|
6
|
+
return unless presenter && model
|
7
|
+
@presenters[presenter] = model
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def unregister(presenter)
|
11
|
+
@presenters.delete_if { |d,_| d == presenter }
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def lookup_model(presenter)
|
15
|
+
@presenters[presenter]
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
def lookup_presenter(model)
|
19
|
+
presenters = lookup_presenters(model)
|
20
|
+
return presenters.last unless presenters.empty?
|
21
|
+
lookup_presenters(model.first).last if model.respond_to? :first
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
def lookup_presenters(model)
|
25
|
+
clazz = model.is_a?(Class) ? model : model.class
|
26
|
+
presenters = @presenters.select { |_d, m| m == clazz }.keys
|
27
|
+
return presenters unless presenters.empty?
|
28
|
+
return [] unless clazz < BasicObject
|
29
|
+
lookup_presenters(clazz.superclass)
|
30
|
+
end
|
29
31
|
end
|
30
32
|
|
31
33
|
module Model
|
34
|
+
def self.included(base)
|
35
|
+
base.extend ClassMethods
|
36
|
+
end
|
37
|
+
|
32
38
|
def model(clazz)
|
33
39
|
HALPresenter.register(model: clazz, presenter: self)
|
34
40
|
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module HALPresenter
|
2
|
-
|
2
|
+
module ClassMethods
|
3
3
|
attr_accessor :paginate
|
4
4
|
end
|
5
5
|
|
6
|
-
# TODO: Support Kaminari and Will_paginate
|
7
|
-
|
8
6
|
class Pagination
|
7
|
+
def self.included(base)
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
9
10
|
|
10
11
|
class Uri
|
11
12
|
def self.parse(str)
|
@@ -1,86 +1,43 @@
|
|
1
|
+
require 'hal_presenter/policy/rules'
|
2
|
+
|
1
3
|
module HALPresenter
|
2
4
|
module Policy
|
3
5
|
module DSL
|
4
|
-
|
5
|
-
class Rules
|
6
|
-
|
7
|
-
def attributes
|
8
|
-
@attributes ||= Hash.new(Proc.new { false })
|
9
|
-
end
|
10
|
-
|
11
|
-
def links
|
12
|
-
@links ||= Hash.new(Proc.new { false })
|
13
|
-
end
|
14
|
-
|
15
|
-
def embedded
|
16
|
-
@embedded ||= Hash.new(Proc.new { false })
|
17
|
-
end
|
18
|
-
|
19
|
-
private :attributes, :links, :embedded
|
20
|
-
|
21
|
-
def defaults(*types, value: false)
|
22
|
-
types.each do |t|
|
23
|
-
send(t).default= Proc.new { value }
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def attribute_rule_for(name)
|
28
|
-
attributes[name]
|
29
|
-
end
|
30
|
-
|
31
|
-
def add_attribute(name, block)
|
32
|
-
attributes[name] = block
|
33
|
-
end
|
34
|
-
|
35
|
-
def link_rule_for(rel)
|
36
|
-
return links[rel] if links.key? rel
|
37
|
-
links[strip_curie(rel)]
|
38
|
-
end
|
39
|
-
|
40
|
-
def add_link(rel, block)
|
41
|
-
links[rel] = block
|
42
|
-
end
|
43
|
-
|
44
|
-
def embed_rule_for(name)
|
45
|
-
return embedded[name] if embedded.key? name
|
46
|
-
embedded[strip_curie(name)]
|
47
|
-
end
|
48
|
-
|
49
|
-
def add_embed(name, block)
|
50
|
-
embedded[name] = block
|
51
|
-
end
|
52
|
-
|
53
|
-
def strip_curie(rel)
|
54
|
-
rel.to_s.split(':', 2)[1]&.to_sym
|
55
|
-
end
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
6
|
module ClassMethods
|
7
|
+
def inherited(child)
|
8
|
+
child.instance_variable_set(:@rules, rules.dup)
|
9
|
+
end
|
60
10
|
|
61
11
|
def allow_by_default(*types)
|
62
12
|
rules.defaults(*types, value: true)
|
63
13
|
end
|
64
14
|
|
65
|
-
def attribute(*names)
|
66
|
-
|
67
|
-
names.each { |name| rules.add_attribute(name,
|
15
|
+
def attribute(*names, &block)
|
16
|
+
block ||= Proc.new { true }
|
17
|
+
names.each { |name| rules.add_attribute(name, block) }
|
68
18
|
end
|
69
19
|
|
70
|
-
def link(*rels)
|
71
|
-
|
72
|
-
rels.each { |rel| rules.add_link(rel,
|
20
|
+
def link(*rels, &block)
|
21
|
+
block ||= Proc.new { true }
|
22
|
+
rels.each { |rel| rules.add_link(rel, block) }
|
73
23
|
end
|
74
24
|
|
75
|
-
def embed(*names)
|
76
|
-
|
77
|
-
names.each { |name| rules.add_embed(name,
|
25
|
+
def embed(*names, &block)
|
26
|
+
block ||= Proc.new { true }
|
27
|
+
names.each { |name| rules.add_embed(name, block) }
|
78
28
|
end
|
79
29
|
|
80
30
|
def rules
|
81
31
|
@rules ||= Rules.new
|
82
32
|
end
|
83
33
|
|
34
|
+
def no_transform_rels
|
35
|
+
rules.transform_rels = false
|
36
|
+
end
|
37
|
+
|
38
|
+
def transform_rels(value = true)
|
39
|
+
rules.transform_rels = !!value
|
40
|
+
end
|
84
41
|
end
|
85
42
|
|
86
43
|
def self.included(mod)
|
@@ -94,26 +51,50 @@ module HALPresenter
|
|
94
51
|
end
|
95
52
|
|
96
53
|
def attribute?(name)
|
97
|
-
|
54
|
+
__check __rules.attribute_rule_for(name)
|
98
55
|
end
|
99
56
|
|
100
57
|
def link?(rel)
|
101
58
|
return true if rel == :self
|
102
|
-
|
59
|
+
__check __rules.link_rule_for(rel)
|
103
60
|
end
|
104
61
|
|
105
62
|
def embed?(name)
|
106
|
-
|
63
|
+
__check __rules.embed_rule_for(name)
|
107
64
|
end
|
108
65
|
|
109
66
|
private
|
110
67
|
|
111
68
|
attr_reader :current_user, :resource, :options
|
112
69
|
|
113
|
-
def
|
114
|
-
|
70
|
+
def delegate_attribute(policy_class, attr, **opts)
|
71
|
+
delegate_to(policy_class, :attribute?, args: attr, **opts)
|
72
|
+
end
|
73
|
+
|
74
|
+
def delegate_link(policy_class, rel, **opts)
|
75
|
+
delegate_to(policy_class, :link?, args: rel, **opts)
|
76
|
+
end
|
77
|
+
|
78
|
+
def delegate_embed(policy_class, rel, **opts)
|
79
|
+
delegate_to(policy_class, :embed?, args: rel, **opts)
|
115
80
|
end
|
116
81
|
|
82
|
+
def delegate_to(policy_class, method, resource: nil, args: nil, **opts)
|
83
|
+
resource ||= send(:resource)
|
84
|
+
opts = options.merge(opts)
|
85
|
+
policy = policy_class.new(current_user, resource, opts)
|
86
|
+
args = Array(args)
|
87
|
+
args.unshift(method)
|
88
|
+
policy.send(*args)
|
89
|
+
end
|
90
|
+
|
91
|
+
def __rules
|
92
|
+
self.class.rules
|
93
|
+
end
|
94
|
+
|
95
|
+
def __check(block)
|
96
|
+
!!instance_eval(&block)
|
97
|
+
end
|
117
98
|
end
|
118
99
|
end
|
119
100
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module HALPresenter
|
2
|
+
module Policy
|
3
|
+
class Rules
|
4
|
+
DEFAULT_PROC = Proc.new { false }
|
5
|
+
|
6
|
+
attr_accessor :transform_rels
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@transform_rels = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def dup
|
13
|
+
super.tap do |copy|
|
14
|
+
copy.instance_variable_set(:@attributes, attributes.dup)
|
15
|
+
copy.instance_variable_set(:@links, links.dup)
|
16
|
+
copy.instance_variable_set(:@embedded, embedded.dup)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def defaults(*types, value: false)
|
21
|
+
types.each do |t|
|
22
|
+
send(t).default= Proc.new { value }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def attribute_rule_for(name)
|
27
|
+
attributes[name]
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_attribute(name, block)
|
31
|
+
attributes[name] = block
|
32
|
+
end
|
33
|
+
|
34
|
+
def link_rule_for(rel)
|
35
|
+
rel = transform(rel)
|
36
|
+
return links[rel] if links.key? rel
|
37
|
+
|
38
|
+
links[strip_curie(rel)]
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_link(rel, block)
|
42
|
+
rel = transform(rel)
|
43
|
+
links[rel] = block
|
44
|
+
end
|
45
|
+
|
46
|
+
def embed_rule_for(name)
|
47
|
+
name = transform(name)
|
48
|
+
return embedded[name] if embedded.key? name
|
49
|
+
|
50
|
+
embedded[strip_curie(name)]
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_embed(name, block)
|
54
|
+
name = transform(name)
|
55
|
+
embedded[name] = block
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def attributes
|
61
|
+
@attributes ||= Hash.new(DEFAULT_PROC)
|
62
|
+
end
|
63
|
+
|
64
|
+
def links
|
65
|
+
@links ||= Hash.new(DEFAULT_PROC)
|
66
|
+
end
|
67
|
+
|
68
|
+
def embedded
|
69
|
+
@embedded ||= Hash.new(DEFAULT_PROC)
|
70
|
+
end
|
71
|
+
|
72
|
+
def strip_curie(rel)
|
73
|
+
rel.to_s.split(':', 2)[1]&.to_sym
|
74
|
+
end
|
75
|
+
|
76
|
+
def transform_rels?
|
77
|
+
@transform_rels
|
78
|
+
end
|
79
|
+
|
80
|
+
def transform(rel)
|
81
|
+
return rel unless transform_rels?
|
82
|
+
|
83
|
+
rel.to_s.tr('-', '_').to_sym
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -4,7 +4,7 @@ module HALPresenter
|
|
4
4
|
module Profile
|
5
5
|
include SuperInit
|
6
6
|
|
7
|
-
def profile(value =
|
7
|
+
def profile(value = Property::NO_VALUE, **kwargs, &block)
|
8
8
|
if value.nil? && !block_given?
|
9
9
|
raise 'profile must be called with non nil value or be given a block'
|
10
10
|
end
|
@@ -2,9 +2,11 @@ require 'hal_presenter/lazy_evaluator'
|
|
2
2
|
|
3
3
|
module HALPresenter
|
4
4
|
class Property
|
5
|
+
NO_VALUE = Object.new.freeze
|
6
|
+
|
5
7
|
attr_reader :name, :embed_depth
|
6
8
|
|
7
|
-
def initialize(name, value =
|
9
|
+
def initialize(name, value = NO_VALUE, **kwargs, &block)
|
8
10
|
@name = name.to_sym
|
9
11
|
@value = value.freeze
|
10
12
|
@embed_depth = kwargs[:embed_depth].freeze
|
@@ -15,10 +17,16 @@ module HALPresenter
|
|
15
17
|
def value(resource = nil, options = {})
|
16
18
|
if @lazy
|
17
19
|
@lazy.evaluate(resource, options)
|
18
|
-
elsif @value
|
20
|
+
elsif @value != NO_VALUE
|
19
21
|
@value
|
20
22
|
elsif resource&.respond_to? name_without_curie
|
21
23
|
resource.public_send(name_without_curie)
|
24
|
+
else
|
25
|
+
raise ArgumentError, <<~ERR
|
26
|
+
Cannot serialize #{name.inspect}.
|
27
|
+
No value given and resource does not respond to #{name_without_curie}. Resource:
|
28
|
+
#{resource.inspect}"
|
29
|
+
ERR
|
22
30
|
end
|
23
31
|
end
|
24
32
|
|
@@ -28,6 +36,11 @@ module HALPresenter
|
|
28
36
|
self
|
29
37
|
end
|
30
38
|
|
39
|
+
def nested_depth_ok?(level)
|
40
|
+
return true unless embed_depth
|
41
|
+
level <= embed_depth
|
42
|
+
end
|
43
|
+
|
31
44
|
private
|
32
45
|
|
33
46
|
def initialize_copy(source)
|
@@ -1,45 +1,63 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'hal_presenter/pagination'
|
3
|
+
require 'hal_presenter/curie_collection'
|
3
4
|
|
4
5
|
module HALPresenter
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
module ClassMethods
|
8
|
+
def to_hal(resource, **options)
|
9
|
+
options = options.dup
|
10
|
+
presenter!(resource, options).to_hal(resource, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_collection(resources, **options)
|
14
|
+
options = options.dup
|
15
|
+
presenter!(resources, options).to_collection(resources, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
13
19
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
def presenter!(resources, **options)
|
21
|
+
raise Serializer::Error, "resources is nil" if resources.nil?
|
22
|
+
presenter = options.delete(:presenter)
|
23
|
+
presenter ||= HALPresenter.lookup_presenter(resources)
|
24
|
+
raise Serializer::Error, "No presenter for #{resources.first.class}" unless presenter
|
25
|
+
|
26
|
+
presenter
|
27
|
+
end
|
20
28
|
end
|
21
29
|
|
22
30
|
module Serializer
|
23
31
|
|
24
32
|
class Error < StandardError; end
|
25
33
|
|
26
|
-
def
|
34
|
+
def self.included(base)
|
35
|
+
base.extend(ClassMethods)
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_hal(resource = nil, **options)
|
39
|
+
options = options.dup
|
27
40
|
options[:_depth] ||= 0
|
28
41
|
hash = to_hash(resource, options)
|
29
|
-
|
42
|
+
move_curies_to_root! hash
|
43
|
+
return hash if options[:as_hash]
|
44
|
+
|
30
45
|
JSON.generate(hash)
|
31
46
|
end
|
32
47
|
|
33
|
-
def to_collection(resources = [], options
|
48
|
+
def to_collection(resources = [], **options)
|
34
49
|
unless can_serialize_collection?
|
35
50
|
raise Error,
|
36
51
|
"Trying to serialize a collection using #{self} which has no collection info. " \
|
37
52
|
"Add a 'collection' spec to the serializer or use another serializer"
|
38
53
|
end
|
54
|
+
options = options.dup
|
39
55
|
options[:paginate] = HALPresenter.paginate unless options.key? :paginate
|
40
56
|
options[:_depth] ||= 0
|
41
57
|
hash = to_collection_hash(resources, options)
|
42
|
-
|
58
|
+
move_curies_to_root! hash
|
59
|
+
return hash if options[:as_hash]
|
60
|
+
|
43
61
|
JSON.generate(hash)
|
44
62
|
end
|
45
63
|
|
@@ -72,8 +90,7 @@ module HALPresenter
|
|
72
90
|
serialized.merge! _serialize_embedded(embedded, resources, policy, options)
|
73
91
|
|
74
92
|
# Embedded resources
|
75
|
-
options
|
76
|
-
serialized_resources = resources.map { |resource| to_hash(resource, options) }
|
93
|
+
serialized_resources = resources.map { |resource| to_hash(resource, options.dup) }
|
77
94
|
serialized[:_embedded] ||= {}
|
78
95
|
serialized[:_embedded].merge!(properties.name => serialized_resources)
|
79
96
|
end
|
@@ -97,32 +114,16 @@ module HALPresenter
|
|
97
114
|
|
98
115
|
private
|
99
116
|
|
100
|
-
def
|
101
|
-
|
102
|
-
find_curies(hash).each do |curie|
|
103
|
-
name = curie[:name]
|
104
|
-
curies[name] = curie
|
105
|
-
end
|
117
|
+
def move_curies_to_root!(hash)
|
118
|
+
return if Hash(hash).empty?
|
106
119
|
|
107
|
-
|
120
|
+
curie_collection = CurieCollection.extract_from!(hash)
|
121
|
+
return if curie_collection.empty?
|
108
122
|
|
109
123
|
hash[:_links] ||= {}
|
110
|
-
hash[:_links][:curies] =
|
124
|
+
hash[:_links][:curies] = curie_collection.to_a
|
111
125
|
end
|
112
126
|
|
113
|
-
def find_curies(hash)
|
114
|
-
return [] if Hash(hash).empty?
|
115
|
-
|
116
|
-
curies = hash[:_links].delete(:curies) if hash.key? :_links
|
117
|
-
curies ||= []
|
118
|
-
|
119
|
-
hash.fetch(:_embedded, {}).values.each do |embedded|
|
120
|
-
collection = embedded.is_a?(Array) ? embedded : [embedded]
|
121
|
-
collection.each { |resrc| curies += find_curies(resrc) }
|
122
|
-
end
|
123
|
-
|
124
|
-
curies
|
125
|
-
end
|
126
127
|
|
127
128
|
def run_post_serialize_hook!(resource, options, serialized)
|
128
129
|
hook = post_serialize_hook
|
@@ -131,7 +132,7 @@ module HALPresenter
|
|
131
132
|
|
132
133
|
def _serialize_attributes(attributes, resource, policy, options)
|
133
134
|
attributes.each_with_object({}) do |attribute, hash|
|
134
|
-
next unless nested_depth_ok?
|
135
|
+
next unless attribute.nested_depth_ok? options[:_depth]
|
135
136
|
next if policy && !policy.attribute?(attribute.name)
|
136
137
|
hash[attribute.name] = attribute.value(resource, options)
|
137
138
|
end
|
@@ -139,9 +140,19 @@ module HALPresenter
|
|
139
140
|
|
140
141
|
def _serialize_links(links, curies, resource, policy, options)
|
141
142
|
serialized = links.each_with_object({}) do |link, hash|
|
142
|
-
|
143
|
-
next
|
144
|
-
|
143
|
+
rel = link.rel
|
144
|
+
next unless link.nested_depth_ok? options[:_depth]
|
145
|
+
next if policy && !policy.link?(rel)
|
146
|
+
|
147
|
+
link_hash = link.to_h(resource, options)
|
148
|
+
next if link_hash.empty?
|
149
|
+
|
150
|
+
if hash.key? rel
|
151
|
+
hash[rel] = [hash[rel]] unless hash[rel].is_a? Array
|
152
|
+
hash[rel] << link_hash
|
153
|
+
else
|
154
|
+
hash.merge!(rel => link_hash)
|
155
|
+
end
|
145
156
|
end
|
146
157
|
curies = _serialize_curies(curies, resource, options)
|
147
158
|
serialized[:curies] = curies if curies.any?
|
@@ -151,7 +162,7 @@ module HALPresenter
|
|
151
162
|
|
152
163
|
def _serialize_curies(curies, resource, options)
|
153
164
|
curies.each_with_object([]) do |curie, array|
|
154
|
-
next unless nested_depth_ok?
|
165
|
+
next unless curie.nested_depth_ok? options[:_depth]
|
155
166
|
hash = curie.to_h(resource, options)
|
156
167
|
array << hash unless hash.empty?
|
157
168
|
end
|
@@ -159,17 +170,18 @@ module HALPresenter
|
|
159
170
|
|
160
171
|
def _serialize_embedded(embedded, object, policy, options)
|
161
172
|
serialized = embedded.each_with_object({}) do |embed, hash|
|
162
|
-
next unless nested_depth_ok?
|
173
|
+
next unless embed.nested_depth_ok? options[:_depth]
|
163
174
|
next if policy && !policy.embed?(embed.name)
|
164
175
|
resource = embed.value(object, options) or next
|
165
176
|
presenter = embed.presenter_class
|
166
|
-
|
177
|
+
embed_options = options.dup
|
178
|
+
embed_options[:_depth] += 1
|
167
179
|
hash[embed.name] =
|
168
180
|
if resource.is_a? Array
|
169
|
-
_serialize_embedded_collection(resource, presenter,
|
181
|
+
_serialize_embedded_collection(resource, presenter, embed_options)
|
170
182
|
else
|
171
183
|
presenter ||= HALPresenter.lookup_presenter(resource)
|
172
|
-
presenter.to_hash(resource,
|
184
|
+
presenter.to_hash(resource, embed_options)
|
173
185
|
end
|
174
186
|
end
|
175
187
|
return {} if serialized.empty?
|
@@ -195,10 +207,5 @@ module HALPresenter
|
|
195
207
|
def policy_for(resource, options)
|
196
208
|
policy_class&.new(options[:current_user], resource, options)
|
197
209
|
end
|
198
|
-
|
199
|
-
def nested_depth_ok?(property, level)
|
200
|
-
return true unless embed_depth = property.embed_depth
|
201
|
-
level <= embed_depth
|
202
|
-
end
|
203
210
|
end
|
204
211
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hal_presenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sammy Henningsson
|
@@ -10,27 +10,26 @@ bindir: bin
|
|
10
10
|
cert_chain:
|
11
11
|
- |
|
12
12
|
-----BEGIN CERTIFICATE-----
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
M40=
|
13
|
+
MIIDVjCCAj6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAqMSgwJgYDVQQDDB9zYW1t
|
14
|
+
eS5oZW5uaW5nc3Nvbi9EQz1oZXkvREM9Y29tMB4XDTIwMDgwNDE4MTAyOVoXDTIx
|
15
|
+
MDgwNDE4MTAyOVowKjEoMCYGA1UEAwwfc2FtbXkuaGVubmluZ3Nzb24vREM9aGV5
|
16
|
+
L0RDPWNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK+SDC1mfyhu
|
17
|
+
cJ6Va21rIHUGscEtQrdvyBqxFG1s2TgPMAv4RbqwdJVPa7kjtbCzslADlUE1oru2
|
18
|
+
C+rcJsMtVGX02ukMIPHT1OjTyy0/EMqLqSy3WeRI8APyDSxCVbe+h5BMf3zZnYfd
|
19
|
+
dR6AeG7ln09T1P/tX+9lTMc+I+DW1fUlQgY48CNUayvtJR61svXvXMrhLhi29SQi
|
20
|
+
g1qmH6Zoe22/JgH+m2JksPndY5Ep3gqfDc6Imwu2vGvmGErJD63FB0XQ/wb4WVH4
|
21
|
+
l7sHQSTfKDp8SImCt1xqNgIyjw578ZG2geGLoncuxgDrbQ/UFIJ11lDZd4vLevMh
|
22
|
+
nIxTSJpPr2cCAwEAAaOBhjCBgzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNV
|
23
|
+
HQ4EFgQUukjj1Cd2ea6IOHDLZe0ymzs2jWkwJAYDVR0RBB0wG4EZc2FtbXkuaGVu
|
24
|
+
bmluZ3Nzb25AaGV5LmNvbTAkBgNVHRIEHTAbgRlzYW1teS5oZW5uaW5nc3NvbkBo
|
25
|
+
ZXkuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAEtv48barHOksR+CEF+Nu5ZjzXksi0
|
26
|
+
rI2JMmuDTlQBw0/m79+Om1cPzBKKR7ODGytjQ7NAj9VWQcw8Oc/K4z63JbD13G3Y
|
27
|
+
45/GTTCCae5YyO/g8BpkFeFwZTvJBwD1HQvMBjBi8WMk+Yr00EUoiLExuL0VSjJ9
|
28
|
+
grmwk+vA+68PWAkYOAkNOonQ6nT3UgmbK9XGoINSxHwn1Njxyw0dXsuZeaznJzhy
|
29
|
+
D4OCgJwXzne+Jeqc60ISM5a6/eFZGVyfI15X2fnxXXVrGu3EMnrgKuHxTNUfjaBL
|
30
|
+
Xs+28o/7SxG1CNyMq1HAmDvUPP3bMyzXTtVr61Kd2uyS1StS5s9XFedR
|
32
31
|
-----END CERTIFICATE-----
|
33
|
-
date:
|
32
|
+
date: 2020-08-04 00:00:00.000000000 Z
|
34
33
|
dependencies:
|
35
34
|
- !ruby/object:Gem::Dependency
|
36
35
|
name: rake
|
@@ -155,7 +154,7 @@ dependencies:
|
|
155
154
|
description: |
|
156
155
|
A DSL for serializing resources according to
|
157
156
|
HypertextApplicationLanguage.
|
158
|
-
email: sammy.henningsson@
|
157
|
+
email: sammy.henningsson@hey.com
|
159
158
|
executables: []
|
160
159
|
extensions: []
|
161
160
|
extra_rdoc_files: []
|
@@ -163,6 +162,7 @@ files:
|
|
163
162
|
- lib/hal_presenter.rb
|
164
163
|
- lib/hal_presenter/attributes.rb
|
165
164
|
- lib/hal_presenter/collection.rb
|
165
|
+
- lib/hal_presenter/curie_collection.rb
|
166
166
|
- lib/hal_presenter/curies.rb
|
167
167
|
- lib/hal_presenter/deserializer.rb
|
168
168
|
- lib/hal_presenter/embedded.rb
|
@@ -173,6 +173,7 @@ files:
|
|
173
173
|
- lib/hal_presenter/pagination.rb
|
174
174
|
- lib/hal_presenter/policy.rb
|
175
175
|
- lib/hal_presenter/policy/dsl.rb
|
176
|
+
- lib/hal_presenter/policy/rules.rb
|
176
177
|
- lib/hal_presenter/profile.rb
|
177
178
|
- lib/hal_presenter/property.rb
|
178
179
|
- lib/hal_presenter/serialize_hooks.rb
|
@@ -197,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
198
|
- !ruby/object:Gem::Version
|
198
199
|
version: '0'
|
199
200
|
requirements: []
|
200
|
-
rubygems_version: 3.0.
|
201
|
+
rubygems_version: 3.0.3
|
201
202
|
signing_key:
|
202
203
|
specification_version: 4
|
203
204
|
summary: JSON HAL serializer
|
metadata.gz.sig
CHANGED
Binary file
|