jsonapi-resources 0.4.2 → 0.4.3

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