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