shaf 1.5.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (165) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/iana_link_relations.csv.gz +0 -0
  5. data/lib/shaf.rb +6 -0
  6. data/lib/shaf/alps/attribute_serializer.rb +41 -0
  7. data/lib/shaf/alps/json_serializer.rb +50 -0
  8. data/lib/shaf/alps/relation_serializer.rb +70 -0
  9. data/lib/shaf/api_doc/link_relations.rb +77 -0
  10. data/lib/shaf/app.rb +12 -5
  11. data/lib/shaf/authenticator.rb +56 -0
  12. data/lib/shaf/authenticator/base.rb +161 -0
  13. data/lib/shaf/authenticator/basic_auth.rb +25 -0
  14. data/lib/shaf/authenticator/challenge.rb +32 -0
  15. data/lib/shaf/authenticator/parameter.rb +31 -0
  16. data/lib/shaf/authenticator/request.rb +17 -0
  17. data/lib/shaf/command/console.rb +1 -1
  18. data/lib/shaf/command/generate.rb +5 -2
  19. data/lib/shaf/command/new.rb +20 -7
  20. data/lib/shaf/command/templates/Gemfile.erb +1 -0
  21. data/{templates/config/settings.yml → lib/shaf/command/templates/config/settings.yml.erb} +1 -5
  22. data/lib/shaf/errors.rb +11 -0
  23. data/lib/shaf/extensions.rb +3 -3
  24. data/lib/shaf/extensions/api_routes.rb +60 -0
  25. data/lib/shaf/extensions/authorize.rb +11 -9
  26. data/lib/shaf/extensions/log.rb +1 -1
  27. data/lib/shaf/extensions/resource_uris.rb +139 -63
  28. data/lib/shaf/extensions/symbolic_routes.rb +22 -19
  29. data/lib/shaf/formable.rb +1 -2
  30. data/lib/shaf/formable/form.rb +1 -1
  31. data/lib/shaf/generator.rb +2 -0
  32. data/lib/shaf/generator/base.rb +2 -3
  33. data/lib/shaf/generator/controller.rb +11 -7
  34. data/lib/shaf/generator/doc.rb +17 -0
  35. data/lib/shaf/generator/forms.rb +1 -0
  36. data/lib/shaf/generator/helper.rb +2 -1
  37. data/lib/shaf/generator/migration/base.rb +7 -3
  38. data/lib/shaf/generator/migration/type.rb +4 -26
  39. data/lib/shaf/generator/migration/types.rb +45 -16
  40. data/lib/shaf/generator/model.rb +1 -2
  41. data/lib/shaf/generator/profile.rb +52 -0
  42. data/lib/shaf/generator/serializer.rb +38 -73
  43. data/lib/shaf/generator/templates/api/policy.rb.erb +2 -2
  44. data/lib/shaf/generator/templates/api/profile.rb.erb +16 -0
  45. data/lib/shaf/generator/templates/api/serializer.rb.erb +2 -2
  46. data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +1 -2
  47. data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +5 -5
  48. data/lib/shaf/helpers.rb +4 -0
  49. data/lib/shaf/helpers/authentication.rb +79 -0
  50. data/lib/shaf/helpers/cache_control.rb +1 -2
  51. data/lib/shaf/helpers/json_html.rb +58 -18
  52. data/lib/shaf/helpers/payload.rb +26 -41
  53. data/lib/shaf/helpers/vary.rb +8 -0
  54. data/lib/shaf/logger.rb +12 -0
  55. data/lib/shaf/parser.rb +65 -0
  56. data/lib/shaf/parser/base.rb +44 -0
  57. data/lib/shaf/parser/form_data.rb +15 -0
  58. data/lib/shaf/parser/json.rb +26 -0
  59. data/lib/shaf/profile.rb +115 -0
  60. data/lib/shaf/profile/attribute.rb +29 -0
  61. data/lib/shaf/profile/evaluator.rb +46 -0
  62. data/lib/shaf/profile/relation.rb +29 -0
  63. data/lib/shaf/profile/unique_id.rb +58 -0
  64. data/lib/shaf/profiles.rb +46 -0
  65. data/lib/shaf/profiles/shaf_basic.rb +20 -0
  66. data/lib/shaf/profiles/shaf_error.rb +49 -0
  67. data/lib/shaf/profiles/shaf_form.rb +110 -0
  68. data/lib/shaf/responder.rb +41 -2
  69. data/lib/shaf/responder/alps_json.rb +25 -0
  70. data/lib/shaf/responder/base.rb +20 -17
  71. data/lib/shaf/responder/hal.rb +63 -8
  72. data/lib/shaf/responder/html.rb +65 -9
  73. data/lib/shaf/responder/problem_json.rb +1 -1
  74. data/lib/shaf/serializer.rb +35 -0
  75. data/lib/shaf/settings.rb +25 -12
  76. data/lib/shaf/spec.rb +1 -0
  77. data/lib/shaf/spec/authenticator.rb +13 -0
  78. data/lib/shaf/spec/base.rb +1 -1
  79. data/lib/shaf/spec/http_method_utils.rb +1 -1
  80. data/lib/shaf/spec/integration_spec.rb +25 -13
  81. data/lib/shaf/spec/payload_utils.rb +2 -2
  82. data/lib/shaf/supported_http_methods.rb +15 -0
  83. data/lib/shaf/tasks/api_doc_task.rb +24 -3
  84. data/lib/shaf/tasks/routes_task.rb +14 -17
  85. data/lib/shaf/upgrade/manifest.rb +11 -2
  86. data/lib/shaf/upgrade/package.rb +78 -49
  87. data/lib/shaf/upgrade/version.rb +11 -10
  88. data/lib/shaf/utils.rb +19 -5
  89. data/lib/shaf/version.rb +3 -1
  90. data/lib/shaf/yard.rb +34 -0
  91. data/lib/shaf/yard/attribute_method_handler.rb +19 -0
  92. data/lib/shaf/yard/attribute_object.rb +30 -0
  93. data/lib/shaf/yard/base_method_handler.rb +30 -0
  94. data/lib/shaf/yard/link_method_handler.rb +39 -0
  95. data/lib/shaf/yard/link_object.rb +60 -0
  96. data/lib/shaf/yard/nested_attributes.rb +37 -0
  97. data/lib/shaf/yard/parser.rb +64 -0
  98. data/lib/shaf/yard/profile_method_handler.rb +51 -0
  99. data/lib/shaf/yard/profile_object.rb +21 -0
  100. data/lib/shaf/yard/resource_object.rb +55 -0
  101. data/lib/shaf/yard/serializer_handler.rb +27 -0
  102. data/templates/api/controllers/base_controller.rb +0 -10
  103. data/templates/api/controllers/docs_controller.rb +5 -3
  104. data/templates/api/controllers/root_controller.rb +7 -1
  105. data/templates/api/policies/base_policy.rb +2 -0
  106. data/templates/api/serializers/base_serializer.rb +1 -3
  107. data/templates/api/serializers/error_serializer.rb +1 -5
  108. data/templates/api/serializers/form_serializer.rb +1 -5
  109. data/templates/api/serializers/validation_error_serializer.rb +1 -5
  110. data/templates/config.ru +1 -1
  111. data/templates/config/bootstrap.rb +1 -2
  112. data/templates/config/directories.rb +52 -44
  113. data/templates/config/helpers.rb +1 -1
  114. data/templates/config/initializers.rb +52 -8
  115. data/templates/config/initializers/authentication.rb +18 -0
  116. data/templates/config/initializers/db_migrations.rb +2 -2
  117. data/templates/config/initializers/logging.rb +2 -2
  118. data/templates/frontend/assets/css/main.css +33 -1
  119. data/templates/frontend/views/headers.erb +20 -0
  120. data/templates/frontend/views/layout.erb +7 -1
  121. data/templates/frontend/views/payload.erb +1 -0
  122. data/templates/spec/spec_helper.rb +2 -0
  123. data/upgrades/0.5.0.tar.gz +0 -0
  124. data/upgrades/1.0.4.tar.gz +0 -0
  125. data/upgrades/1.1.0.tar.gz +0 -0
  126. data/upgrades/1.6.0.tar.gz +0 -0
  127. data/upgrades/1.6.1.tar.gz +0 -0
  128. data/upgrades/2.0.0.tar.gz +0 -0
  129. data/yard_templates/api_doc/doc_index/html/body.erb +3 -0
  130. data/yard_templates/api_doc/doc_index/setup.rb +8 -0
  131. data/yard_templates/api_doc/html/css/api-doc.css +222 -0
  132. data/yard_templates/api_doc/html/favicon.ico +0 -0
  133. data/yard_templates/api_doc/html/js/switch_tab.js +17 -0
  134. data/yard_templates/api_doc/html/setup.rb +59 -0
  135. data/yard_templates/api_doc/layout/html/footer.erb +3 -0
  136. data/yard_templates/api_doc/layout/html/header.erb +7 -0
  137. data/yard_templates/api_doc/layout/html/layout.erb +13 -0
  138. data/yard_templates/api_doc/layout/setup.rb +24 -0
  139. data/yard_templates/api_doc/profile/html/attributes.erb +10 -0
  140. data/yard_templates/api_doc/profile/html/profile.erb +6 -0
  141. data/yard_templates/api_doc/profile/html/relations.erb +10 -0
  142. data/yard_templates/api_doc/profile/setup.rb +38 -0
  143. data/yard_templates/api_doc/profile_attribute/html/attribute.erb +23 -0
  144. data/yard_templates/api_doc/profile_attribute/setup.rb +21 -0
  145. data/yard_templates/api_doc/profile_relation/html/relation.erb +37 -0
  146. data/yard_templates/api_doc/profile_relation/setup.rb +41 -0
  147. data/yard_templates/api_doc/resource/html/attributes.erb +10 -0
  148. data/yard_templates/api_doc/resource/html/profile.erb +14 -0
  149. data/yard_templates/api_doc/resource/html/relations.erb +10 -0
  150. data/yard_templates/api_doc/resource/html/resource.erb +5 -0
  151. data/yard_templates/api_doc/resource/setup.rb +56 -0
  152. data/yard_templates/api_doc/resource_attribute/html/attribute.erb +23 -0
  153. data/yard_templates/api_doc/resource_attribute/setup.rb +20 -0
  154. data/yard_templates/api_doc/resource_relation/html/relation.erb +47 -0
  155. data/yard_templates/api_doc/resource_relation/setup.rb +80 -0
  156. data/yard_templates/api_doc/setup.rb +31 -0
  157. data/yard_templates/api_doc/sidebar/html/profile_list.erb +8 -0
  158. data/yard_templates/api_doc/sidebar/html/search.erb +7 -0
  159. data/yard_templates/api_doc/sidebar/html/serializer_list.erb +8 -0
  160. data/yard_templates/api_doc/sidebar/html/sidebar.erb +13 -0
  161. data/yard_templates/api_doc/sidebar/setup.rb +56 -0
  162. metadata +140 -30
  163. metadata.gz.sig +0 -0
  164. data/lib/shaf/extensions/current_user.rb +0 -48
  165. data/lib/shaf/responder/hal_serializable.rb +0 -54
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shaf
4
+ module Profiles
5
+ class ShafBasic < Shaf::Profile
6
+ name 'shaf-basic'
7
+
8
+ rel :delete,
9
+ http_method: :delete,
10
+ doc: <<~DOC
11
+ When a resource contains a link with rel 'delete', this
12
+ means that the autenticated user (or any user if the
13
+ current user has not been authenticated), may send a
14
+ DELETE request to the href of the link.
15
+ If a DELETE request is sent to this href then the corresponding
16
+ resource will be deleted.
17
+ DOC
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shaf
4
+ module Profiles
5
+ class ShafError < Shaf::Profile
6
+ name 'shaf-error'
7
+ urn 'urn:shaf:error'
8
+
9
+ doc 'This profile describes a set of descriptors for generic error messages.'
10
+ example <<~EXAMPLE
11
+ {
12
+ "title": "Invalid entity",
13
+ "message": "The user could not be saved, due to unfulfilled requirements",
14
+ "code": "VALIDATION_ERROR",
15
+ "fields": {
16
+ "email": ["cannot be empty"]
17
+ }
18
+ }
19
+ EXAMPLE
20
+
21
+ example <<~EXAMPLE
22
+ {
23
+ "title": "Unpermitted action",
24
+ "message": "User is not allowed to edit this resource",
25
+ "code": "FORBIDDEN_ERROR",
26
+ }
27
+ EXAMPLE
28
+
29
+ attribute :code,
30
+ type: :string,
31
+ doc: 'An identifier that describes the type of error.'
32
+
33
+ attribute :title,
34
+ type: :string,
35
+ doc: 'A short string used for labeling the error.'
36
+
37
+ attribute :message,
38
+ type: :string,
39
+ doc: 'A description with details about the error.'
40
+
41
+ attribute :fields,
42
+ type: :string,
43
+ doc: 'An object describing validation errors. ' \
44
+ 'The keys correspond to attributes of a resource. ' \
45
+ 'The values are an array of strings describing validation errors ' \
46
+ 'for the corresponding attribute.'
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shaf
4
+ module Profiles
5
+ class ShafForm < Shaf::Profile
6
+ name 'shaf-form'
7
+ urn 'urn:shaf:form'
8
+
9
+ doc 'This profile describes how a set of descriptors should be interpreted to ' \
10
+ 'turn a generic media type into a form (similar to HTML forms). For ' \
11
+ 'example, the HAL media type (application/hal+json) does not specify any ' \
12
+ 'semantics about forms, however we can add semantics about forms to a HAL ' \
13
+ 'document using this profile.'
14
+
15
+ example <<~EXAMPLE
16
+ Given the following form:
17
+
18
+ ```json
19
+ "create-form": {
20
+ "method": "POST",
21
+ "name": "create-post",
22
+ "title": "Create Post",
23
+ "href": "/posts",
24
+ "type": "application/json",
25
+ "_links": {
26
+ "self": {
27
+ "href": "http://localhost:3000/posts/form"
28
+ }
29
+ },
30
+ "fields": [
31
+ {
32
+ "name": "title",
33
+ "type": "string",
34
+ },
35
+ {
36
+ "name": "message",
37
+ "type": "string",
38
+ }
39
+ ]
40
+ }
41
+ ```
42
+
43
+ A client should then present a user interface with the possiblity to fill in two fields accepting string values (if applicable the interface should be titled "Create Post"). The entered values should be mapped to the keys title resp. message. (The client may of course fill in the details itself whenever possible). When the form is to be submitted, the client constructs a json string with the keys and values and makes a POST request to http://localhost:3000/posts/form with the header Content-Type header set to application/json and the json string as body.
44
+
45
+ Using Curl, the form above could be submitted using:
46
+
47
+ ```sh
48
+ curl -H "Content-Type: application/json" \
49
+ -X POST \
50
+ -d '{"title": "hello", "message": "world"}' \
51
+ localhost:3000/posts
52
+ ```
53
+ EXAMPLE
54
+
55
+ attribute :method,
56
+ type: :string,
57
+ doc: 'The HTTP method used for submitting the form'
58
+
59
+ attribute :href,
60
+ type: :string,
61
+ doc: 'The target uri that the form should be submitted to'
62
+
63
+ attribute :name,
64
+ type: :string,
65
+ doc: 'A string used to indentify the form.'
66
+
67
+ attribute :title,
68
+ type: :string,
69
+ doc: 'A string used as title/label when showing the form in a user interface'
70
+
71
+ attribute :type,
72
+ type: :string,
73
+ doc: 'The media type that must be set in the CONTENT-TYPE header when submiting the form'
74
+
75
+ attribute :submit,
76
+ type: :string,
77
+ doc: 'A string used as label for the CallToAction (e.g. the submit button text)'
78
+
79
+ attribute :fields, doc: 'A list of field descriptors' do
80
+
81
+ attribute :name,
82
+ type: :string,
83
+ doc: 'A string that should be used as key for the field.'
84
+
85
+ attribute :type,
86
+ type: :string,
87
+ doc: 'A string specifying the possible values accepted by the field.'
88
+
89
+ attribute :title,
90
+ type: :string,
91
+ doc: 'A string used to present this field in a UI. If not present name may be used instead.'
92
+
93
+ attribute :value,
94
+ doc: 'A prefilled value. Should be shown in a user interface (unless hidden) and sent back on submission unless changed.'
95
+
96
+ attribute :required,
97
+ type: :boolean,
98
+ doc: 'A boolean specifying that the field must be given a value before the form is submitted.'
99
+
100
+ attribute :hidden,
101
+ type: :boolean,
102
+ doc: 'A boolean specifying that the field should not be shown in a user interface.'
103
+
104
+ attribute :accepted_values,
105
+ type: :array,
106
+ doc: 'A list of av acceptable values. Submitting a value not present in this list SHOULD result in a client error response.'
107
+ end
108
+ end
109
+ end
110
+ end
@@ -1,7 +1,10 @@
1
1
  require 'set'
2
+ require 'shaf/errors'
2
3
 
3
4
  module Shaf
4
5
  module Responder
6
+ MEDIA_TYPE_SUFFIX_PATTERN = %r{(.*)/([^+]+)\+(.*)}.freeze
7
+
5
8
  class << self
6
9
  def register(responder)
7
10
  uninitialized << responder
@@ -15,15 +18,23 @@ module Shaf
15
18
  end
16
19
 
17
20
  def for(request, resource)
21
+ return Responder::ProblemJson if resource.is_a?(Errors::NotAcceptableError)
22
+
18
23
  types = supported_responders_for(resource).map(&:mime_type)
19
- mime = request.preferred_type(types)
20
- responders[mime]
24
+ types = move_html_to_last(types)
25
+
26
+ mime = preferred_type(request, types)
27
+ responders[mime] or raise Errors::NotAcceptableError
21
28
  end
22
29
 
23
30
  def default=(responder)
24
31
  responders.default = responder
25
32
  end
26
33
 
34
+ def default
35
+ responders.default
36
+ end
37
+
27
38
  private
28
39
 
29
40
  def supported_responders_for(resource)
@@ -41,6 +52,23 @@ module Shaf
41
52
  @supported_responders ||= Hash.new { |hash, key| hash[key] = Set.new }
42
53
  end
43
54
 
55
+ def preferred_type(request, types)
56
+ mime = request.preferred_type(types)
57
+ return mime if mime
58
+
59
+ request.accept.find do |accept|
60
+ next if accept.match? MEDIA_TYPE_SUFFIX_PATTERN
61
+
62
+ types.find do |type|
63
+ m = type.match MEDIA_TYPE_SUFFIX_PATTERN
64
+ next unless m
65
+
66
+ format = "#{m[1]}/#{m[3]}"
67
+ return type if accept.to_str == format
68
+ end
69
+ end
70
+ end
71
+
44
72
  def uninitialized
45
73
  @uninitialized ||= []
46
74
  end
@@ -57,6 +85,16 @@ module Shaf
57
85
  init_responders!
58
86
  end
59
87
  end
88
+
89
+ # We want to always be able to respond with text/html, but only when
90
+ # asked for (Accept header) to be able to let other more specific mime
91
+ # types take precedence we need to move text/html to the end of the
92
+ # array.
93
+ def move_html_to_last(types)
94
+ return types unless types.include? Html.mime_type
95
+
96
+ (types - [Html.mime_type]) << Html.mime_type
97
+ end
60
98
  end
61
99
  end
62
100
  end
@@ -65,3 +103,4 @@ require 'shaf/responder/base'
65
103
  require 'shaf/responder/hal'
66
104
  require 'shaf/responder/html'
67
105
  require 'shaf/responder/problem_json'
106
+ require 'shaf/responder/alps_json'
@@ -0,0 +1,25 @@
1
+ require 'shaf/alps/json_serializer'
2
+
3
+ module Shaf
4
+ module Responder
5
+ class AlpsJson < Base
6
+ mime_type :alps_json, 'application/alps+json'
7
+
8
+ def self.can_handle?(resource)
9
+ return false unless resource.is_a? Class
10
+ resource <= Shaf::Profile
11
+ end
12
+
13
+ def body
14
+ JSON.generate hash
15
+ end
16
+
17
+ private
18
+
19
+ def hash
20
+ ALPS::JsonSerializer.call(resource)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
@@ -2,12 +2,14 @@ module Shaf
2
2
  module Responder
3
3
  class Response
4
4
  attr_reader :content_type, :body, :serialized_hash, :resource
5
+ attr_accessor :preload_links
5
6
 
6
7
  def initialize(content_type:, body:, serialized_hash: {}, resource: nil)
7
8
  @content_type = content_type
8
9
  @body = body
9
10
  @serialized_hash = serialized_hash
10
11
  @resource = resource
12
+ @preload_links = []
11
13
  end
12
14
 
13
15
  def log_entry
@@ -36,11 +38,10 @@ module Shaf
36
38
  end
37
39
 
38
40
  def call(controller, resource, preload: [], **kwargs)
39
- responder = new(controller, resource, **kwargs)
41
+ responder = new(controller, resource, preload_rels: preload, **kwargs)
40
42
  response = responder.build_response
41
43
  log_response(controller, response)
42
- preload_links = preload_links(preload, responder, response, controller)
43
- write_response(controller, response, preload_links)
44
+ write_response(controller, response)
44
45
  end
45
46
 
46
47
  def can_handle?(_obj)
@@ -58,23 +59,14 @@ module Shaf
58
59
  controller.log.send(type, msg)
59
60
  end
60
61
 
61
- def preload_links(rels, responder, response, controller = nil)
62
- Array(rels).map do |rel|
63
- links = responder.lookup_rel(rel, response)
64
- links = [links].compact unless links.is_a? Array
65
- log(controller, PRELOAD_FAILED_MSG % rel) if links.empty?
66
- links
67
- end
68
- end
69
-
70
- def write_response(controller, response, preload_links)
62
+ def write_response(controller, response)
71
63
  controller.content_type(response.content_type)
72
- add_preload_links(controller, response, preload_links)
64
+ add_preload_links(controller, response)
73
65
  controller.body(response.body)
74
66
  end
75
67
 
76
- def add_preload_links(controller, response, preload_links)
77
- preload_links.each do |links|
68
+ def add_preload_links(controller, response)
69
+ response.preload_links.each do |links|
78
70
  links.each do |link|
79
71
  next unless link[:href]
80
72
  # Nginx http2_push_preload only processes relative URIs with absolute path
@@ -111,9 +103,20 @@ module Shaf
111
103
  body: body,
112
104
  serialized_hash: serialized_hash,
113
105
  resource: resource
114
- )
106
+ ).tap do |response|
107
+ response.preload_links = preload_links(response)
108
+ end
115
109
  end
116
110
 
111
+ def preload_links(response)
112
+ Array(options[:preload_rels]).map do |rel|
113
+ links = lookup_rel(rel, response)
114
+ links = [links].compact unless links.is_a? Array
115
+ log(controller, PRELOAD_FAILED_MSG % rel) if links.empty?
116
+ links
117
+ end
118
+ end
119
+
117
120
  def lookup_rel(_rel, _response)
118
121
  []
119
122
  end
@@ -1,29 +1,84 @@
1
- require 'shaf/responder/hal_serializable'
2
-
3
1
  module Shaf
4
2
  module Responder
5
3
  class Hal < Base
6
- include HalSerializable
7
-
8
4
  use_as_default!
9
5
  mime_type :hal, 'application/hal+json'
10
6
 
7
+ def self.can_handle?(resource)
8
+ return false if resource.is_a? StandardError
9
+
10
+ if resource.is_a? Class
11
+ return false if resource <= Shaf::Profile
12
+ end
13
+
14
+ true
15
+ end
16
+
11
17
  def body
12
- @body ||= JSON.generate(serialized_hash)
18
+ @body ||= generate_json
13
19
  end
14
20
 
21
+ def lookup_rel(rel, response)
22
+ hal = response.serialized_hash
23
+ links = hal&.dig(:_links, rel.to_sym)
24
+ return [] unless links
25
+
26
+ links = [links] unless links.is_a? Array
27
+ links.map do |link|
28
+ {
29
+ href: link[:href],
30
+ as: 'fetch',
31
+ crossorigin: 'anonymous'
32
+ }
33
+ end
34
+ end
15
35
  private
16
36
 
17
37
  def mime_type
18
38
  type = super
19
- type = "#{type};profile=#{profile}" if profile
39
+ type = "#{type}; profile=\"#{profile}\"" if profile
20
40
  type
21
41
  end
22
42
 
43
+ def collection?
44
+ !!options.fetch(:collection, false)
45
+ end
46
+
47
+ def serializer
48
+ @serializer ||= options[:serializer] || HALPresenter.lookup_presenter(resource)
49
+ end
50
+
51
+ def serialized_hash
52
+ raise Errors::NotAcceptableError unless serializer
53
+
54
+ @serialized_hash ||=
55
+ if collection?
56
+ serializer.to_collection(resource, current_user: user, as_hash: true, **options)
57
+ else
58
+ serializer.to_hal(resource, current_user: user, as_hash: true, **options)
59
+ end
60
+
61
+ # hal_presenter versions before v1.5.0 does not understand the :as_hash
62
+ # keyword argument and will always return a String from
63
+ # to_hal/to_collection, thus we need to parse it if its a String.
64
+ if @serialized_hash.is_a? String
65
+ @body = @serialized_hash
66
+ @serialized_hash = JSON.parse(@serialized_hash, symbolize_names: true)
67
+ end
68
+
69
+ @serialized_hash
70
+ end
71
+
23
72
  def profile
24
- return unless serializer
73
+ @profile ||= options[:profile]
74
+ return unless @profile || serializer
75
+
76
+ @profile ||= serializer.semantic_profile
77
+ end
25
78
 
26
- @profile ||= options[:profile] || serializer.semantic_profile
79
+ def generate_json
80
+ # FIXME: change to Oj??
81
+ JSON.generate(serialized_hash)
27
82
  end
28
83
  end
29
84
  end