jsonapi-resources 0.4.2 → 0.4.3

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/README.md +103 -71
  4. data/Rakefile +2 -2
  5. data/jsonapi-resources.gemspec +2 -2
  6. data/lib/jsonapi-resources.rb +0 -1
  7. data/lib/jsonapi/active_record_operations_processor.rb +10 -2
  8. data/lib/jsonapi/acts_as_resource_controller.rb +26 -24
  9. data/lib/jsonapi/association.rb +50 -15
  10. data/lib/jsonapi/callbacks.rb +1 -2
  11. data/lib/jsonapi/configuration.rb +8 -24
  12. data/lib/jsonapi/error.rb +1 -2
  13. data/lib/jsonapi/error_codes.rb +3 -1
  14. data/lib/jsonapi/exceptions.rb +59 -47
  15. data/lib/jsonapi/include_directives.rb +11 -11
  16. data/lib/jsonapi/mime_types.rb +2 -2
  17. data/lib/jsonapi/operation.rb +28 -11
  18. data/lib/jsonapi/operations_processor.rb +16 -5
  19. data/lib/jsonapi/paginator.rb +19 -19
  20. data/lib/jsonapi/request.rb +175 -196
  21. data/lib/jsonapi/resource.rb +158 -105
  22. data/lib/jsonapi/resource_serializer.rb +37 -26
  23. data/lib/jsonapi/resources/version.rb +2 -2
  24. data/lib/jsonapi/response_document.rb +5 -4
  25. data/lib/jsonapi/routing_ext.rb +24 -19
  26. data/test/controllers/controller_test.rb +261 -31
  27. data/test/fixtures/active_record.rb +206 -8
  28. data/test/fixtures/book_comments.yml +2 -1
  29. data/test/fixtures/books.yml +1 -0
  30. data/test/fixtures/documents.yml +3 -0
  31. data/test/fixtures/people.yml +8 -1
  32. data/test/fixtures/pictures.yml +15 -0
  33. data/test/fixtures/products.yml +3 -0
  34. data/test/fixtures/vehicles.yml +8 -0
  35. data/test/helpers/{hash_helpers.rb → assertions.rb} +6 -1
  36. data/test/integration/requests/request_test.rb +14 -3
  37. data/test/integration/routes/routes_test.rb +47 -0
  38. data/test/test_helper.rb +27 -4
  39. data/test/unit/serializer/include_directives_test.rb +5 -0
  40. data/test/unit/serializer/polymorphic_serializer_test.rb +384 -0
  41. data/test/unit/serializer/serializer_test.rb +19 -1
  42. metadata +14 -4
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env rake
2
- require "bundler/gem_tasks"
3
- require "rake/testtask"
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
4
  require './test/test_helper.rb'
5
5
 
6
6
  TestApp.load_tasks
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = JSONAPI::Resources::VERSION
9
9
  spec.authors = ['Dan Gebhardt', 'Larry Gebhardt']
10
10
  spec.email = ['dan@cerebris.com', 'larry@cerebris.com']
11
- spec.summary = %q{Easily support JSON API in Rails.}
12
- spec.description = %q{A resource-centric approach to implementing the controllers, routes, and serializers needed to support the JSON API spec.}
11
+ spec.summary = 'Easily support JSON API in Rails.'
12
+ spec.description = 'A resource-centric approach to implementing the controllers, routes, and serializers needed to support the JSON API spec.'
13
13
  spec.homepage = 'https://github.com/cerebris/jsonapi-resources'
14
14
  spec.license = 'MIT'
15
15
 
@@ -20,4 +20,3 @@ require 'jsonapi/include_directives'
20
20
  require 'jsonapi/operation_result'
21
21
  require 'jsonapi/operation_results'
22
22
  require 'jsonapi/callbacks'
23
-
@@ -1,6 +1,6 @@
1
1
  class ActiveRecordOperationsProcessor < JSONAPI::OperationsProcessor
2
-
3
2
  private
3
+
4
4
  def transaction
5
5
  if @transactional
6
6
  ActiveRecord::Base.transaction do
@@ -12,7 +12,7 @@ class ActiveRecordOperationsProcessor < JSONAPI::OperationsProcessor
12
12
  end
13
13
 
14
14
  def rollback
15
- raise ActiveRecord::Rollback if @transactional
15
+ fail ActiveRecord::Rollback if @transactional
16
16
  end
17
17
 
18
18
  def process_operation(operation)
@@ -24,5 +24,13 @@ class ActiveRecordOperationsProcessor < JSONAPI::OperationsProcessor
24
24
  rescue ActiveRecord::RecordNotFound
25
25
  record_not_found = JSONAPI::Exceptions::RecordNotFound.new(operation.associated_key)
26
26
  return JSONAPI::ErrorsOperationResult.new(record_not_found.errors[0].code, record_not_found.errors)
27
+
28
+ rescue JSONAPI::Exceptions::Error => e
29
+ raise e
30
+
31
+ rescue => e
32
+ internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
33
+ Rails.logger.error { "Internal Server Error: #{e.message} #{e.backtrace.join("\n")}" }
34
+ return JSONAPI::ErrorsOperationResult.new(internal_server_error.errors[0].code, internal_server_error.errors)
27
35
  end
28
36
  end
@@ -6,7 +6,7 @@ module JSONAPI
6
6
 
7
7
  included do
8
8
  before_filter :ensure_correct_media_type, only: [:create, :update, :create_association, :update_association]
9
- before_filter :setup_request
9
+ append_before_filter :setup_request
10
10
  after_filter :setup_response
11
11
  end
12
12
 
@@ -60,6 +60,7 @@ module JSONAPI
60
60
  end
61
61
 
62
62
  private
63
+
63
64
  def resource_klass
64
65
  @resource_klass ||= resource_klass_name.safe_constantize
65
66
  end
@@ -78,17 +79,14 @@ module JSONAPI
78
79
 
79
80
  def ensure_correct_media_type
80
81
  unless request.content_type == JSONAPI::MEDIA_TYPE
81
- raise JSONAPI::Exceptions::UnsupportedMediaTypeError.new(request.content_type)
82
+ fail JSONAPI::Exceptions::UnsupportedMediaTypeError.new(request.content_type)
82
83
  end
83
84
  rescue => e
84
85
  handle_exceptions(e)
85
86
  end
86
87
 
87
88
  def setup_request
88
- @request = JSONAPI::Request.new(params, {
89
- context: context,
90
- key_formatter: key_formatter
91
- })
89
+ @request = JSONAPI::Request.new(params, context: context, key_formatter: key_formatter)
92
90
  render_errors(@request.errors) unless @request.errors.empty?
93
91
  rescue => e
94
92
  handle_exceptions(e)
@@ -105,6 +103,11 @@ module JSONAPI
105
103
  {}
106
104
  end
107
105
 
106
+ # override to set scope_id
107
+ def scope_id
108
+ nil
109
+ end
110
+
108
111
  # Control by setting in an initializer:
109
112
  # JSONAPI.configuration.json_key_format = :camelized_key
110
113
  # JSONAPI.configuration.route = :camelized_route
@@ -128,7 +131,7 @@ module JSONAPI
128
131
  end
129
132
 
130
133
  def render_errors(errors)
131
- operation_results = JSONAPI::OperationResults.new()
134
+ operation_results = JSONAPI::OperationResults.new
132
135
  result = JSONAPI::ErrorsOperationResult.new(errors[0].status, errors)
133
136
  operation_results.add_result(result)
134
137
 
@@ -143,18 +146,17 @@ module JSONAPI
143
146
  def create_response_document(operation_results)
144
147
  JSONAPI::ResponseDocument.new(
145
148
  operation_results,
146
- {
147
- primary_resource_klass: resource_klass,
148
- include_directives: @request ? @request.include_directives : nil,
149
- fields: @request ? @request.fields : nil,
150
- base_url: base_url,
151
- key_formatter: key_formatter,
152
- route_formatter: route_formatter,
153
- base_meta: base_response_meta,
154
- base_links: base_response_links,
155
- resource_serializer_klass: resource_serializer_klass,
156
- request: @request
157
- }
149
+ primary_resource_klass: resource_klass,
150
+ include_directives: @request ? @request.include_directives : nil,
151
+ fields: @request ? @request.fields : nil,
152
+ base_url: base_url,
153
+ key_formatter: key_formatter,
154
+ route_formatter: route_formatter,
155
+ base_meta: base_response_meta,
156
+ base_links: base_response_links,
157
+ resource_serializer_klass: resource_serializer_klass,
158
+ request: @request,
159
+ scope_id: scope_id
158
160
  )
159
161
  end
160
162
 
@@ -169,11 +171,11 @@ module JSONAPI
169
171
  # Note: Be sure to either call super(e) or handle JSONAPI::Exceptions::Error and raise unhandled exceptions
170
172
  def handle_exceptions(e)
171
173
  case e
172
- when JSONAPI::Exceptions::Error
173
- render_errors(e.errors)
174
- else # raise all other exceptions
175
- # :nocov:
176
- raise e
174
+ when JSONAPI::Exceptions::Error
175
+ render_errors(e.errors)
176
+ else # raise all other exceptions
177
+ # :nocov:
178
+ fail e
177
179
  # :nocov:
178
180
  end
179
181
  end
@@ -1,34 +1,69 @@
1
1
  module JSONAPI
2
2
  class Association
3
- attr_reader :acts_as_set, :foreign_key, :type, :options, :name, :class_name
4
-
5
- def initialize(name, options={})
6
- @name = name.to_s
7
- @options = options
8
- @acts_as_set = options.fetch(:acts_as_set, false) == true
9
- @foreign_key = options[:foreign_key ] ? options[:foreign_key ].to_sym : nil
10
- @module_path = options[:module_path] || ''
3
+ attr_reader :acts_as_set, :foreign_key, :type, :options, :name,
4
+ :class_name, :polymorphic
5
+
6
+ def initialize(name, options = {})
7
+ @name = name.to_s
8
+ @options = options
9
+ @acts_as_set = options.fetch(:acts_as_set, false) == true
10
+ @foreign_key = options[:foreign_key] ? options[:foreign_key].to_sym : nil
11
+ @module_path = options[:module_path] || ''
12
+ @relation_name = options.fetch(:relation_name, @name)
13
+ @polymorphic = options.fetch(:polymorphic, false) == true
11
14
  end
12
15
 
16
+ alias_method :polymorphic?, :polymorphic
17
+
13
18
  def primary_key
14
- @primary_key ||= Resource.resource_for(@module_path + @name)._primary_key
19
+ @primary_key ||= resource_klass._primary_key
20
+ end
21
+
22
+ def resource_klass
23
+ @resource_klass ||= Resource.resource_for(@module_path + @class_name)
24
+ end
25
+
26
+ def relation_name(options = {})
27
+ case @relation_name
28
+ when Symbol
29
+ # :nocov:
30
+ @relation_name
31
+ # :nocov:
32
+ when String
33
+ @relation_name.to_sym
34
+ when Proc
35
+ @relation_name.call(options)
36
+ end
37
+ end
38
+
39
+ def type_for_source(source)
40
+ if polymorphic?
41
+ resource = source.public_send(name)
42
+ resource.class._type if resource
43
+ else
44
+ type
45
+ end
15
46
  end
16
47
 
17
48
  class HasOne < Association
18
- def initialize(name, options={})
49
+ def initialize(name, options = {})
19
50
  super
20
- @class_name = options.fetch(:class_name, name.to_s.capitalize)
51
+ @class_name = options.fetch(:class_name, name.to_s.camelize)
21
52
  @type = class_name.underscore.pluralize.to_sym
22
- @foreign_key ||= @key.nil? ? "#{name}_id".to_sym : @key
53
+ @foreign_key ||= "#{name}_id".to_sym
54
+ end
55
+
56
+ def polymorphic_type
57
+ "#{type.to_s.singularize}_type" if polymorphic?
23
58
  end
24
59
  end
25
60
 
26
61
  class HasMany < Association
27
- def initialize(name, options={})
62
+ def initialize(name, options = {})
28
63
  super
29
- @class_name = options.fetch(:class_name, name.to_s.capitalize.singularize)
64
+ @class_name = options.fetch(:class_name, name.to_s.camelize.singularize)
30
65
  @type = class_name.underscore.pluralize.to_sym
31
- @foreign_key ||= @key.nil? ? "#{name.to_s.singularize}_ids".to_sym : @key
66
+ @foreign_key ||= "#{name.to_s.singularize}_ids".to_sym
32
67
  end
33
68
  end
34
69
  end
@@ -2,7 +2,6 @@ require 'active_support/callbacks'
2
2
 
3
3
  module JSONAPI
4
4
  module Callbacks
5
-
6
5
  def self.included(base)
7
6
  base.class_eval do
8
7
  include ActiveSupport::Callbacks
@@ -49,4 +48,4 @@ module JSONAPI
49
48
  end
50
49
  end
51
50
  end
52
- end
51
+ end
@@ -62,37 +62,21 @@ module JSONAPI
62
62
  @operations_processor = JSONAPI::OperationsProcessor.operations_processor_for(@operations_processor_name)
63
63
  end
64
64
 
65
- def allowed_request_params=(allowed_request_params)
66
- @allowed_request_params = allowed_request_params
67
- end
65
+ attr_writer :allowed_request_params
68
66
 
69
- def default_paginator=(default_paginator)
70
- @default_paginator = default_paginator
71
- end
67
+ attr_writer :default_paginator
72
68
 
73
- def default_page_size=(default_page_size)
74
- @default_page_size = default_page_size
75
- end
69
+ attr_writer :default_page_size
76
70
 
77
- def maximum_page_size=(maximum_page_size)
78
- @maximum_page_size = maximum_page_size
79
- end
71
+ attr_writer :maximum_page_size
80
72
 
81
- def use_text_errors=(use_text_errors)
82
- @use_text_errors = use_text_errors
83
- end
73
+ attr_writer :use_text_errors
84
74
 
85
- def top_level_links_include_pagination=(top_level_links_include_pagination)
86
- @top_level_links_include_pagination = top_level_links_include_pagination
87
- end
75
+ attr_writer :top_level_links_include_pagination
88
76
 
89
- def top_level_meta_include_record_count=(top_level_meta_include_record_count)
90
- @top_level_meta_include_record_count = top_level_meta_include_record_count
91
- end
77
+ attr_writer :top_level_meta_include_record_count
92
78
 
93
- def top_level_meta_record_count_key=(top_level_meta_record_count_key)
94
- @top_level_meta_record_count_key = top_level_meta_record_count_key
95
- end
79
+ attr_writer :top_level_meta_record_count_key
96
80
  end
97
81
 
98
82
  class << self
data/lib/jsonapi/error.rb CHANGED
@@ -1,9 +1,8 @@
1
1
  module JSONAPI
2
2
  class Error
3
-
4
3
  attr_accessor :title, :detail, :id, :href, :code, :path, :links, :status
5
4
 
6
- def initialize(options={})
5
+ def initialize(options = {})
7
6
  @title = options[:title]
8
7
  @detail = options[:detail]
9
8
  @id = options[:id]
@@ -24,6 +24,7 @@ module JSONAPI
24
24
  RECORD_NOT_FOUND = 404
25
25
  UNSUPPORTED_MEDIA_TYPE = 415
26
26
  LOCKED = 423
27
+ INTERNAL_SERVER_ERROR = 500
27
28
 
28
29
  TEXT_ERRORS =
29
30
  { VALIDATION_ERROR => 'VALIDATION_ERROR',
@@ -50,6 +51,7 @@ module JSONAPI
50
51
  FORBIDDEN => 'FORBIDDEN',
51
52
  RECORD_NOT_FOUND => 'RECORD_NOT_FOUND',
52
53
  UNSUPPORTED_MEDIA_TYPE => 'UNSUPPORTED_MEDIA_TYPE',
53
- LOCKED => 'LOCKED'
54
+ LOCKED => 'LOCKED',
55
+ INTERNAL_SERVER_ERROR => 'INTERNAL_SERVER_ERROR'
54
56
  }
55
57
  end
@@ -2,6 +2,21 @@ module JSONAPI
2
2
  module Exceptions
3
3
  class Error < RuntimeError; end
4
4
 
5
+ class InternalServerError < Error
6
+ attr_accessor :exception
7
+
8
+ def initialize(exception)
9
+ @exception = exception
10
+ end
11
+
12
+ def errors
13
+ [JSONAPI::Error.new(code: JSONAPI::INTERNAL_SERVER_ERROR,
14
+ status: 500,
15
+ title: 'Internal Server Error',
16
+ detail: 'Internal Server Error')]
17
+ end
18
+ end
19
+
5
20
  class InvalidResource < Error
6
21
  attr_accessor :resource
7
22
  def initialize(resource)
@@ -10,9 +25,9 @@ module JSONAPI
10
25
 
11
26
  def errors
12
27
  [JSONAPI::Error.new(code: JSONAPI::INVALID_RESOURCE,
13
- status: :bad_request,
14
- title: 'Invalid resource',
15
- detail: "#{resource} is not a valid resource.")]
28
+ status: :bad_request,
29
+ title: 'Invalid resource',
30
+ detail: "#{resource} is not a valid resource.")]
16
31
  end
17
32
  end
18
33
 
@@ -24,9 +39,9 @@ module JSONAPI
24
39
 
25
40
  def errors
26
41
  [JSONAPI::Error.new(code: JSONAPI::RECORD_NOT_FOUND,
27
- status: :not_found,
28
- title: 'Record not found',
29
- detail: "The record identified by #{id} could not be found.")]
42
+ status: :not_found,
43
+ title: 'Record not found',
44
+ detail: "The record identified by #{id} could not be found.")]
30
45
  end
31
46
  end
32
47
 
@@ -38,9 +53,9 @@ module JSONAPI
38
53
 
39
54
  def errors
40
55
  [JSONAPI::Error.new(code: JSONAPI::UNSUPPORTED_MEDIA_TYPE,
41
- status: :unsupported_media_type,
42
- title: 'Unsupported media type',
43
- detail: "All requests that create or update resources must use the '#{JSONAPI::MEDIA_TYPE}' Content-Type. This request specified '#{media_type}.'")]
56
+ status: :unsupported_media_type,
57
+ title: 'Unsupported media type',
58
+ detail: "All requests that create or update resources must use the '#{JSONAPI::MEDIA_TYPE}' Content-Type. This request specified '#{media_type}.'")]
44
59
  end
45
60
  end
46
61
 
@@ -75,9 +90,9 @@ module JSONAPI
75
90
 
76
91
  def errors
77
92
  [JSONAPI::Error.new(code: JSONAPI::INVALID_FILTERS_SYNTAX,
78
- status: :bad_request,
79
- title: 'Invalid filters syntax',
80
- detail: "#{filters} is not a valid syntax for filtering.")]
93
+ status: :bad_request,
94
+ title: 'Invalid filters syntax',
95
+ detail: "#{filters} is not a valid syntax for filtering.")]
81
96
  end
82
97
  end
83
98
 
@@ -89,9 +104,9 @@ module JSONAPI
89
104
 
90
105
  def errors
91
106
  [JSONAPI::Error.new(code: JSONAPI::FILTER_NOT_ALLOWED,
92
- status: :bad_request,
93
- title: 'Filter not allowed',
94
- detail: "#{filter} is not allowed.")]
107
+ status: :bad_request,
108
+ title: 'Filter not allowed',
109
+ detail: "#{filter} is not allowed.")]
95
110
  end
96
111
  end
97
112
 
@@ -104,9 +119,9 @@ module JSONAPI
104
119
 
105
120
  def errors
106
121
  [JSONAPI::Error.new(code: JSONAPI::INVALID_FILTER_VALUE,
107
- status: :bad_request,
108
- title: 'Invalid filter value',
109
- detail: "#{value} is not a valid value for #{filter}.")]
122
+ status: :bad_request,
123
+ title: 'Invalid filter value',
124
+ detail: "#{value} is not a valid value for #{filter}.")]
110
125
  end
111
126
  end
112
127
 
@@ -119,9 +134,9 @@ module JSONAPI
119
134
 
120
135
  def errors
121
136
  [JSONAPI::Error.new(code: JSONAPI::INVALID_FIELD_VALUE,
122
- status: :bad_request,
123
- title: 'Invalid field value',
124
- detail: "#{value} is not a valid value for #{field}.")]
137
+ status: :bad_request,
138
+ title: 'Invalid field value',
139
+ detail: "#{value} is not a valid value for #{field}.")]
125
140
  end
126
141
  end
127
142
 
@@ -166,9 +181,9 @@ module JSONAPI
166
181
 
167
182
  def errors
168
183
  [JSONAPI::Error.new(code: JSONAPI::INVALID_FIELD,
169
- status: :bad_request,
170
- title: 'Invalid field',
171
- detail: "#{field} is not a valid field for #{type}.")]
184
+ status: :bad_request,
185
+ title: 'Invalid field',
186
+ detail: "#{field} is not a valid field for #{type}.")]
172
187
  end
173
188
  end
174
189
 
@@ -209,13 +224,12 @@ module JSONAPI
209
224
  end
210
225
 
211
226
  def errors
212
- params.collect { |param|
213
- JSONAPI::Error.new(code: JSONAPI::PARAM_NOT_ALLOWED,
227
+ params.collect do |param|
228
+ JSONAPI::Error.new(code: JSONAPI::PARAM_NOT_ALLOWED,
214
229
  status: :bad_request,
215
230
  title: 'Param not allowed',
216
231
  detail: "#{param} is not allowed.")
217
- }
218
-
232
+ end
219
233
  end
220
234
  end
221
235
 
@@ -227,18 +241,18 @@ module JSONAPI
227
241
 
228
242
  def errors
229
243
  [JSONAPI::Error.new(code: JSONAPI::PARAM_MISSING,
230
- status: :bad_request,
231
- title: 'Missing Parameter',
232
- detail: "The required parameter, #{param}, is missing.")]
244
+ status: :bad_request,
245
+ title: 'Missing Parameter',
246
+ detail: "The required parameter, #{param}, is missing.")]
233
247
  end
234
248
  end
235
249
 
236
250
  class CountMismatch < Error
237
251
  def errors
238
252
  [JSONAPI::Error.new(code: JSONAPI::COUNT_MISMATCH,
239
- status: :bad_request,
240
- title: 'Count to key mismatch',
241
- detail: 'The resource collection does not contain the same number of objects as the number of keys.')]
253
+ status: :bad_request,
254
+ title: 'Count to key mismatch',
255
+ detail: 'The resource collection does not contain the same number of objects as the number of keys.')]
242
256
  end
243
257
  end
244
258
 
@@ -250,18 +264,18 @@ module JSONAPI
250
264
 
251
265
  def errors
252
266
  [JSONAPI::Error.new(code: JSONAPI::KEY_NOT_INCLUDED_IN_URL,
253
- status: :bad_request,
254
- title: 'Key is not included in URL',
255
- detail: "The URL does not support the key #{key}")]
267
+ status: :bad_request,
268
+ title: 'Key is not included in URL',
269
+ detail: "The URL does not support the key #{key}")]
256
270
  end
257
271
  end
258
272
 
259
273
  class MissingKey < Error
260
274
  def errors
261
275
  [JSONAPI::Error.new(code: JSONAPI::KEY_ORDER_MISMATCH,
262
- status: :bad_request,
263
- title: 'A key is required',
264
- detail: 'The resource object does not contain a key.')]
276
+ status: :bad_request,
277
+ title: 'A key is required',
278
+ detail: 'The resource object does not contain a key.')]
265
279
  end
266
280
  end
267
281
 
@@ -273,9 +287,9 @@ module JSONAPI
273
287
 
274
288
  def errors
275
289
  [JSONAPI::Error.new(code: JSONAPI::LOCKED,
276
- status: :locked,
277
- title: 'Locked resource',
278
- detail: "#{message}")]
290
+ status: :locked,
291
+ title: 'Locked resource',
292
+ detail: "#{message}")]
279
293
  end
280
294
  end
281
295
 
@@ -323,7 +337,6 @@ module JSONAPI
323
337
  end
324
338
  end
325
339
 
326
-
327
340
  class PageParametersNotAllowed < Error
328
341
  attr_accessor :params
329
342
  def initialize(params)
@@ -331,13 +344,12 @@ module JSONAPI
331
344
  end
332
345
 
333
346
  def errors
334
- params.collect { |param|
347
+ params.collect do |param|
335
348
  JSONAPI::Error.new(code: JSONAPI::PARAM_NOT_ALLOWED,
336
349
  status: :bad_request,
337
350
  title: 'Page parameter not allowed',
338
351
  detail: "#{param} is not an allowed page parameter.")
339
- }
340
-
352
+ end
341
353
  end
342
354
  end
343
355