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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12895524e08462462b15f197216f20f3fd24a53e7e6a60e7c5876639cd72dba5
4
- data.tar.gz: b74e9c2084b7652447ee5bf90a56212fd5fc3829ee0e7a842187889b71ded750
3
+ metadata.gz: af641721e6b137d280c31c60efc6b76e7e1a22dd441a9b65aee3b929954290ec
4
+ data.tar.gz: 784293b830aeed98f2f1ac1157b0526a8febe8ccbbe40a54dcee85acb8111294
5
5
  SHA512:
6
- metadata.gz: c6f920f2d359fba3dd088f217dd94b7ecce8a5023a2f59ddf5daf3be012f81205663f6be10263ba7d67435a6c220f6904aa121fc13381eeda1d890444ada3ee2
7
- data.tar.gz: '08041d1884286f31bfd1e2b14014b518ec2979081c222703f435b0b26c83d13c53f630cd9da4593c4e6be5b14af60289e0e66d7ac8e50e40a0783ad9aee664be'
6
+ metadata.gz: 829846a3e6a6d86e537817585ccfdc929efc758cf9a748b73c4a9c5d54d0760f8735301a7606a90d6279c2dba9efd1b3b9998a646c336a1656628f42ba779c56
7
+ data.tar.gz: fd10fbc76409d2b1738b2e5abdd75ba68e6a34592ae6b7ed56f2fcac0f83079eba33b524704de4c29ff168040147e89d82fc0466746049c84b4f1df87bafaaeb
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(*args, **kwargs, &block)
8
+ def attribute(name, value = Property::NO_VALUE, **kwargs, &block)
9
9
  kwargs[:context] ||= self
10
- attributes.delete_if { |attr| attr.name == args.first }
11
- Property.new(*args, **kwargs, &block).tap do |attr|
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
- def self.from_hal(presenter, payload, resource = nil)
6
- presenter.from_hal(payload, resource)
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 = nil, **kwargs, &block)
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(*args, **kwargs, &block)
17
+ def embed(name, value = Property::NO_VALUE, **kwargs, &block)
18
18
  kwargs[:context] ||= self
19
- embedded.delete_if { |embed| embed.name == args.first }
20
- Embed.new(*args, **kwargs, &block).tap do |embed|
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(:evaluate_block, &block)
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
- evaluate_block
19
+ @options = (options || {}).dup
20
+ call
21
21
  ensure
22
- @resource = nil
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
 
@@ -3,14 +3,16 @@ require 'hal_presenter/super_init'
3
3
 
4
4
  module HALPresenter
5
5
 
6
- def self.base_href=(base)
7
- @base_href = base&.sub(%r(/*$), '')
8
- end
6
+ module ClassMethods
7
+ def base_href=(base)
8
+ @base_href = base&.sub(%r(/*$), '')
9
+ end
9
10
 
10
- def self.href(href)
11
- return href if (@base_href ||= '').empty?
12
- return href if href =~ %r(\A(\w+://)?[^/])
13
- @base_href + href
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 = nil, **kwargs, &block)
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
- hash = {href: HALPresenter.href(href)}.tap do |h|
48
- h[:type] = type if type
49
- h[:deprecation] = deprecation if deprecation
50
- h[:profile] = profile if profile
51
- h[:title] = title if title
52
- h[:templated] = templated if templated
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
- links.delete_if { |link| link.rel == rel }
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
@@ -1,34 +1,40 @@
1
1
  module HALPresenter
2
2
  @presenters = {}
3
3
 
4
- def self.register(model:, presenter:)
5
- return unless presenter && model
6
- @presenters[presenter] = model
7
- end
4
+ module ClassMethods
5
+ def register(model:, presenter:)
6
+ return unless presenter && model
7
+ @presenters[presenter] = model
8
+ end
8
9
 
9
- def self.unregister(presenter)
10
- @presenters.delete_if { |d,_| d == presenter }
11
- end
10
+ def unregister(presenter)
11
+ @presenters.delete_if { |d,_| d == presenter }
12
+ end
12
13
 
13
- def self.lookup_model(presenter)
14
- @presenters[presenter]
15
- end
14
+ def lookup_model(presenter)
15
+ @presenters[presenter]
16
+ end
16
17
 
17
- def self.lookup_presenter(model)
18
- presenters = lookup_presenters(model)
19
- return presenters.last unless presenters.empty?
20
- lookup_presenters(model.first).last if model.respond_to? :first
21
- end
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
- def self.lookup_presenters(model)
24
- clazz = model.is_a?(Class) ? model : model.class
25
- presenters = @presenters.select { |_d, m| m == clazz }.keys
26
- return presenters unless presenters.empty?
27
- return [] unless clazz < BasicObject
28
- lookup_presenters(clazz.superclass)
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
- class << self
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
- b = block_given? ? Proc.new : Proc.new { true }
67
- names.each { |name| rules.add_attribute(name, b) }
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
- b = block_given? ? Proc.new : Proc.new { true }
72
- rels.each { |rel| rules.add_link(rel, b) }
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
- b = block_given? ? Proc.new : Proc.new { true }
77
- names.each { |name| rules.add_embed(name, b) }
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
- run self.class.rules.attribute_rule_for(name)
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
- run self.class.rules.link_rule_for(rel)
59
+ __check __rules.link_rule_for(rel)
103
60
  end
104
61
 
105
62
  def embed?(name)
106
- run self.class.rules.embed_rule_for(name)
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 run(block)
114
- instance_eval(&block) && true || false
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 = nil, **kwargs, &block)
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 = nil, **kwargs, &block)
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
- def self.to_hal(resource, options = {})
7
- raise Serializer::Error, "Resource is nil" if resource.nil?
8
- presenter = options.delete(:presenter)
9
- presenter ||= HALPresenter.lookup_presenter(resource)
10
- raise Serializer::Error, "No presenter for #{resource.class}" unless presenter
11
- presenter.to_hal(resource, options)
12
- end
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
- def self.to_collection(resources, options = {})
15
- raise Serializer::Error, "resources is nil" if resources.nil?
16
- presenter = options.delete(:presenter)
17
- presenter ||= HALPresenter.lookup_presenter(resources)
18
- raise Serializer::Error, "No presenter for #{resources.first.class}" unless presenter
19
- presenter.to_collection(resources, options)
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 to_hal(resource = nil, options = {})
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
- move_curies_to_top! hash
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
- move_curies_to_top! hash
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[:_depth] += 1
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 move_curies_to_top!(hash)
101
- curies = {}
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
- return if curies.empty?
120
+ curie_collection = CurieCollection.extract_from!(hash)
121
+ return if curie_collection.empty?
108
122
 
109
123
  hash[:_links] ||= {}
110
- hash[:_links][:curies] = curies.values
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?(attribute, options[:_depth])
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
- next unless nested_depth_ok?(link, options[:_depth])
143
- next if policy && !policy.link?(link.rel)
144
- hash.merge! link.to_h(resource, options)
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?(curie, options[:_depth])
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?(embed, options[:_depth])
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
- options[:_depth] += 1
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, options)
181
+ _serialize_embedded_collection(resource, presenter, embed_options)
170
182
  else
171
183
  presenter ||= HALPresenter.lookup_presenter(resource)
172
- presenter.to_hash(resource, options)
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
@@ -7,7 +7,7 @@ module HALPresenter
7
7
  return default unless superclass.respond_to?(method, true)
8
8
 
9
9
  if default.respond_to? :each
10
- superclass.send(method).map do |prop|
10
+ Array(superclass.send(method)).map do |prop|
11
11
  prop.clone.change_context(self)
12
12
  end
13
13
  else
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.2.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
- MIIDXjCCAkagAwIBAgIBATANBgkqhkiG9w0BAQsFADAsMSowKAYDVQQDDCFzYW1t
14
- eS5oZW5uaW5nc3Nvbi9EQz1nbWFpbC9EQz1jb20wHhcNMTgwNzE2MTYzNTMzWhcN
15
- MjAwNzE2MTYzNTMzWjAsMSowKAYDVQQDDCFzYW1teS5oZW5uaW5nc3Nvbi9EQz1n
16
- bWFpbC9EQz1jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvkgwt
17
- Zn8obnCelWttayB1BrHBLUK3b8gasRRtbNk4DzAL+EW6sHSVT2u5I7Wws7JQA5VB
18
- NaK7tgvq3CbDLVRl9NrpDCDx09To08stPxDKi6kst1nkSPAD8g0sQlW3voeQTH98
19
- 2Z2H3XUegHhu5Z9PU9T/7V/vZUzHPiPg1tX1JUIGOPAjVGsr7SUetbL171zK4S4Y
20
- tvUkIoNaph+maHttvyYB/ptiZLD53WORKd4Knw3OiJsLtrxr5hhKyQ+txQdF0P8G
21
- +FlR+Je7B0Ek3yg6fEiJgrdcajYCMo8Oe/GRtoHhi6J3LsYA620P1BSCddZQ2XeL
22
- y3rzIZyMU0iaT69nAgMBAAGjgYowgYcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAw
23
- HQYDVR0OBBYEFLpI49QndnmuiDhwy2XtMps7No1pMCYGA1UdEQQfMB2BG3NhbW15
24
- Lmhlbm5pbmdzc29uQGdtYWlsLmNvbTAmBgNVHRIEHzAdgRtzYW1teS5oZW5uaW5n
25
- c3NvbkBnbWFpbC5jb20wDQYJKoZIhvcNAQELBQADggEBAFJZqH6sgeiTLvMLpxaK
26
- K1GaYSCyZbMutf5C3tIAgkmU5UD6B8R0bw6gTM1deM5NJ60LjzqY7rlK3YKDIbTn
27
- iXMCe9vd4yE/jb5Zi8Wk//9n8CMG68dQpBvmcQ58/M4gTtgsx+lIgXuI5dPQMmRi
28
- bhWQqqWqxT9X6njjfXFk4xn3z6mfFQNPAYqRVeTHUpXBQZPt+bYXRwHPFZGWkx4l
29
- BnuuhYKt3CR7YIgvnsQWlTAcU1Ipdayj6UfYqUtlc6cF3CL96NOx7mgZXV8URFiX
30
- ZMhjYR7sRczGJx+GxGU2EaR0bjRsPVlC4ywtFxoOfRG3WaJcpWGEoAoMJX6Z0bRv
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: 2019-08-31 00:00:00.000000000 Z
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@gmail.com
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.1
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