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 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