schemable 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/schemable.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "schemable/version"
3
+ require_relative 'schemable/version'
4
4
  require 'active_support/concern'
5
5
 
6
6
  module Schemable
@@ -9,7 +9,6 @@ module Schemable
9
9
  extend ActiveSupport::Concern
10
10
 
11
11
  included do
12
-
13
12
  # Maps a given type name to a corresponding JSON schema object that represents that type.
14
13
  #
15
14
  # @param type_name [String, Symbol] A String or Symbol representing the type of the property to be mapped.
@@ -22,7 +21,7 @@ module Schemable
22
21
  integer: { type: :integer },
23
22
  float: { type: :number, format: :float },
24
23
  decimal: { type: :number, format: :double },
25
- datetime: { type: :string, format: :"date-time" },
24
+ datetime: { type: :string, format: :'date-time' },
26
25
  date: { type: :string, format: :date },
27
26
  time: { type: :string, format: :time },
28
27
  boolean: { type: :boolean },
@@ -42,42 +41,43 @@ module Schemable
42
41
  # Modify a JSON schema object by merging new properties into it or deleting a specified path.
43
42
  #
44
43
  # @param original_schema [Hash] The original schema object to modify.
44
+ #
45
45
  # @param new_props [Hash] The new properties to merge into the schema.
46
- # @param given_path [String, nil] The path to the property to modify or delete, if any.
47
- # Use dot notation to specify nested properties (e.g. "person.address.city").
46
+ #
47
+ # @param given_path [String, nil] The path to the property to modify or delete, if any. Use dot notation to specify nested properties (e.g. "person.address.city").
48
+ #
48
49
  # @param delete [Boolean] Whether to delete the property at the given path, if it exists.
50
+ #
49
51
  # @raise [ArgumentError] If `delete` is true but `given_path` is nil, or if `given_path` does not exist in the original schema.
50
52
  #
51
53
  # @return [Hash] A new schema object with the specified modifications.
52
54
  #
53
- # @example Merge new properties into the schema
54
- # original_schema = { type: 'object', properties: { name: { type: 'string' } } }
55
- # new_props = { properties: { age: { type: 'integer' } } }
56
- # modify_schema(original_schema, new_props)
57
- # # => { type: 'object', properties: { name: { type: 'string' }, age: { type: 'integer' } } }
55
+ # @example
56
+ # `Merge new properties into the schema`
58
57
  #
59
- # @example Delete a property from the schema
60
- # original_schema = { type: 'object', properties: { name: { type: 'string' } } }
61
- # modify_schema(original_schema, {}, 'properties.name', delete: true)
62
- # # => { type: 'object', properties: {} }
58
+ # original_schema = { type: 'object', properties: { name: { type: 'string' } } }
59
+ # new_props = { properties: { age: { type: 'integer' } } }
60
+ # modify_schema(original_schema, new_props)
61
+ # => {type: 'object', properties: {name: {type: 'string'}, age: {type: 'integer'}}}
62
+ #
63
+ # @example
64
+ # `Delete a property from the schema`
65
+ #
66
+ # original_schema = { type: 'object', properties: { name: { type: 'string' } } }
67
+ # modify_schema(original_schema, {}, 'properties.name', delete: true)
68
+ # => {type: 'object', properties: {}}
63
69
  def modify_schema(original_schema, new_props, given_path = nil, delete: false)
64
70
  return new_props if original_schema.nil?
65
71
 
66
- if given_path.nil? && delete
67
- raise ArgumentError, "Cannot delete without a given path"
68
- end
72
+ raise ArgumentError, 'Cannot delete without a given path' if given_path.nil? && delete
69
73
 
70
74
  if given_path.present?
71
75
  path_segments = given_path.split('.').map(&:to_sym)
72
76
 
73
77
  if path_segments.size == 1
74
- unless original_schema.key?(path_segments.first)
75
- raise ArgumentError, "Given path does not exist in the original schema"
76
- end
78
+ raise ArgumentError, 'Given path does not exist in the original schema' unless original_schema.key?(path_segments.first)
77
79
  else
78
- unless original_schema.dig(*path_segments[0..-2]).is_a?(Hash) && original_schema.dig(*path_segments)
79
- raise ArgumentError, "Given path does not exist in the original schema"
80
- end
80
+ raise ArgumentError, 'Given path does not exist in the original schema' unless original_schema.dig(*path_segments[0..-2]).is_a?(Hash) && original_schema.dig(*path_segments)
81
81
  end
82
82
 
83
83
  path_hash = path_segments.reverse.reduce(new_props) { |a, n| { n => a } }
@@ -104,38 +104,27 @@ module Schemable
104
104
  # @return [Hash] The JSON Schema attribute definition as a Hash or an empty Hash if the attribute does not exist on the model.
105
105
  #
106
106
  # @example
107
- # attribute_schema(:title)
108
- # # => { "type": "string" }
107
+ # attribute_schema(:title) => { "type": "string" }
109
108
  def attribute_schema(attribute)
110
109
  # Get the column hash for the attribute
111
110
  column_hash = model.columns_hash[attribute.to_s]
112
111
 
113
112
  # Check if this attribute has a custom JSON Schema definition
114
- if array_types.keys.include?(attribute)
115
- return array_types[attribute]
116
- end
113
+ return array_types[attribute] if array_types.keys.include?(attribute)
117
114
 
118
- if additional_response_attributes.keys.include?(attribute)
119
- return additional_response_attributes[attribute]
120
- end
115
+ return additional_response_attributes[attribute] if additional_response_attributes.keys.include?(attribute)
121
116
 
122
117
  # Check if this is an array attribute
123
- if column_hash.as_json.try(:[], 'sql_type_metadata').try(:[], 'sql_type').include?('[]')
124
- return type_mapper(:array)
125
- end
118
+ return type_mapper(:array) if column_hash.as_json.try(:[], 'sql_type_metadata').try(:[], 'sql_type').include?('[]')
126
119
 
127
120
  # Map the column type to a JSON Schema type if none of the above conditions are met
128
121
  response = type_mapper(column_hash.try(:type))
129
122
 
130
123
  # If the attribute is nullable, modify the schema accordingly
131
- if response && nullable_attributes.include?(attribute)
132
- return modify_schema(response, { nullable: true })
133
- end
124
+ return modify_schema(response, { nullable: true }) if response && nullable_attributes.include?(attribute)
134
125
 
135
126
  # If attribute is an enum, modify the schema accordingly
136
- if response && model.defined_enums.key?(attribute.to_s)
137
- return modify_schema(response, { type: :string, enum: model.defined_enums[attribute.to_s].keys })
138
- end
127
+ return modify_schema(response, { type: :string, enum: model.defined_enums[attribute.to_s].keys }) if response && model.defined_enums.key?(attribute.to_s)
139
128
 
140
129
  return response unless response.nil?
141
130
 
@@ -147,7 +136,6 @@ module Schemable
147
136
 
148
137
  # If we still haven't found a schema type, default to object
149
138
  type_mapper(:object)
150
-
151
139
  rescue NoMethodError
152
140
  # Log a warning if the attribute does not exist on the model
153
141
  Rails.logger.warn("\e[33mWARNING: #{model} does not have an attribute named \e[31m#{attribute}\e[0m")
@@ -159,27 +147,26 @@ module Schemable
159
147
  #
160
148
  # @note The `additional_response_attributes` and `excluded_response_attributes` are applied to the schema returned by this method.
161
149
  #
162
- # @example
163
- # {
164
- # type: :object,
165
- # properties: {
166
- # id: { type: :string },
167
- # title: { type: :string }
168
- # }
169
- # }
170
- #
171
150
  # @return [Hash] The JSON Schema for the model's attributes.
151
+ #
152
+ # @example
153
+ # {
154
+ # type: :object,
155
+ # properties: {
156
+ # id: { type: :string },
157
+ # title: { type: :string }
158
+ # }
159
+ # }
172
160
  def attributes_schema
173
161
  schema = {
174
162
  type: :object,
175
- properties: attributes.reduce({}) do |props, attr|
176
- props[attr] = attribute_schema(attr)
177
- props
163
+ properties: attributes.index_with do |attr|
164
+ attribute_schema(attr)
178
165
  end
179
166
  }
180
167
 
181
168
  # modify the schema to include additional response relations
182
- schema = modify_schema(schema, additional_response_attributes, given_path = "properties")
169
+ schema = modify_schema(schema, additional_response_attributes, 'properties')
183
170
 
184
171
  # modify the schema to exclude response relations
185
172
  excluded_response_attributes.each do |key|
@@ -191,35 +178,35 @@ module Schemable
191
178
 
192
179
  # Generates the schema for the relationships of a resource.
193
180
  #
194
- # @param relations [Hash] A hash representing the relationships of the resource in the form of { belongs_to: {}, has_many: {} }.
195
- # If not provided, the relationships will be inferred from the model's associations.
196
- #
197
181
  # @note The `additional_response_relations` and `excluded_response_relations` are applied to the schema returned by this method.
198
182
  #
183
+ # @param relations [Hash] A hash representing the relationships of the resource in the form of { belongs_to: {}, has_many: {} }. If not provided, the relationships will be inferred from the model's associations.
184
+ #
199
185
  # @param expand [Boolean] A boolean indicating whether to expand the relationships in the schema.
200
- # @param exclude_from_expansion [Array] An array of relationship names to exclude from expansion.
201
186
  #
202
- # @example
203
- # {
204
- # type: :object,
205
- # properties: {
206
- # province: {
207
- # type: :object,
208
- # properties: {
209
- # meta: {
210
- # type: :object,
211
- # properties: {
212
- # included: {
213
- # type: :boolean, default: false
214
- # }
215
- # }
216
- # }
217
- # }
218
- # }
219
- # }
220
- # }
187
+ # @param exclude_from_expansion [Array] An array of relationship names to exclude from expansion.
221
188
  #
222
189
  # @return [Hash] A hash representing the schema for the relationships.
190
+ #
191
+ # @example
192
+ # {
193
+ # type: :object,
194
+ # properties: {
195
+ # province: {
196
+ # type: :object,
197
+ # properties: {
198
+ # meta: {
199
+ # type: :object,
200
+ # properties: {
201
+ # included: {
202
+ # type: :boolean, default: false
203
+ # }
204
+ # }
205
+ # }
206
+ # }
207
+ # }
208
+ # }
209
+ # }
223
210
  def relationships_schema(relations = try(:relationships), expand: false, exclude_from_expansion: [])
224
211
  return {} if relations.blank?
225
212
  return {} if relations == { belongs_to: {}, has_many: {} }
@@ -242,7 +229,6 @@ module Schemable
242
229
  if relation_type == :has_many
243
230
  props.merge!(
244
231
  relation_definitions.keys.index_with do |relationship|
245
-
246
232
  result = {
247
233
  type: :object,
248
234
  properties: {
@@ -267,7 +253,6 @@ module Schemable
267
253
  else
268
254
  props.merge!(
269
255
  relation_definitions.keys.index_with do |relationship|
270
-
271
256
  result = {
272
257
  type: :object,
273
258
  properties: {
@@ -292,7 +277,7 @@ module Schemable
292
277
  }
293
278
 
294
279
  # modify the schema to include additional response relations
295
- schema = modify_schema(schema, additional_response_relations, "properties")
280
+ schema = modify_schema(schema, additional_response_relations, 'properties')
296
281
 
297
282
  # modify the schema to exclude response relations
298
283
  excluded_response_relations.each do |key|
@@ -306,39 +291,41 @@ module Schemable
306
291
  #
307
292
  # @note The `additional_response_includes` and `excluded_response_includes` (yet to be implemented) are applied to the schema returned by this method.
308
293
  #
309
- # @param relations [Hash] A hash representing the relationships of the resource in the form of { belongs_to: {}, has_many: {} }.
310
- # If not provided, the relationships will be inferred from the model's associations.
294
+ # @param relations [Hash] A hash representing the relationships of the resource in the form of { belongs_to: {}, has_many: {} }. If not provided, the relationships will be inferred from the model's associations.
295
+ #
311
296
  # @param expand [Boolean] A boolean indicating whether to expand the relationships of the relationships in the schema.
297
+ #
312
298
  # @param exclude_from_expansion [Array] An array of relationship names to exclude from expansion.
313
- # @param metadata [Hash] Additional metadata to include in the schema, usually received from the nested_relationships method sent by the response_schema method.
314
299
  #
315
- # @example
316
- # {
317
- # included: {
318
- # type: :array,
319
- # items: {
320
- # anyOf:
321
- # [
322
- # {
323
- # type: :object,
324
- # properties: {
325
- # type: { type: :string, default: "provinces" },
326
- # id: { type: :string },
327
- # attributes: {
328
- # type: :object,
329
- # properties: {
330
- # id: { type: :string },
331
- # name: { type: :string }
332
- # }
333
- # }
334
- # }
335
- # }
336
- # ]
337
- # }
338
- # }
339
- # }
300
+ # @param metadata [Hash] Additional metadata to include in the schema, usually received from the nested_relationships method sent by the response_schema method.
340
301
  #
341
302
  # @return [Hash] A hash representing the schema for the included resources.
303
+ #
304
+ # @example
305
+ # {
306
+ # included: {
307
+ # type: :array,
308
+ # items: {
309
+ # anyOf:
310
+ # [
311
+ # {
312
+ # type: :object,
313
+ # properties: {
314
+ # type: { type: :string, default: "provinces" },
315
+ # id: { type: :string },
316
+ # attributes: {
317
+ # type: :object,
318
+ # properties: {
319
+ # id: { type: :string },
320
+ # name: { type: :string }
321
+ # }
322
+ # }
323
+ # }
324
+ # }
325
+ # ]
326
+ # }
327
+ # }
328
+ # }
342
329
  def included_schema(relations = try(:relationships), expand: false, exclude_from_expansion: [], metadata: {})
343
330
  return {} if relations.blank?
344
331
  return {} if relations == { belongs_to: {}, has_many: {} }
@@ -348,7 +335,7 @@ module Schemable
348
335
  type: :array,
349
336
  items: {
350
337
  anyOf:
351
- relations.reduce([]) do |props, (relation_type, relation_definitions)|
338
+ relations.reduce([]) do |props, (_relation_type, relation_definitions)|
352
339
  props + relation_definitions.keys.reduce([]) do |props, relationship|
353
340
  props + [
354
341
  unless exclude_from_expansion.include?(relationship)
@@ -358,16 +345,16 @@ module Schemable
358
345
  type: { type: :string, default: relation_definitions[relationship].model_name },
359
346
  id: { type: :string },
360
347
  attributes: begin
361
- relation_definitions[relationship].new.attributes_schema || {}
362
- rescue NoMethodError
363
- {}
364
- end
348
+ relation_definitions[relationship].new.attributes_schema || {}
349
+ rescue NoMethodError
350
+ {}
351
+ end
365
352
  }.merge(
366
353
  if relation_definitions[relationship].new.relationships != { belongs_to: {}, has_many: {} } || relation_definitions[relationship].new.relationships.blank?
367
354
  if !expand || metadata.blank?
368
355
  { relationships: relation_definitions[relationship].new.relationships_schema(expand: false) }
369
356
  else
370
- { relationships: relation_definitions[relationship].new.relationships_schema(relations = metadata[:nested_relationships][relationship], expand: true, exclude_from_expansion: exclude_from_expansion) }
357
+ { relationships: relation_definitions[relationship].new.relationships_schema(relations = metadata[:nested_relationships][relationship], expand: true, exclude_from_expansion:) }
371
358
  end
372
359
  else
373
360
  {}
@@ -377,36 +364,34 @@ module Schemable
377
364
  end
378
365
  ].concat(
379
366
  [
380
- if expand && metadata.present? && !exclude_from_expansion.include?(relationship)
367
+ if expand && metadata.present? && exclude_from_expansion.exclude?(relationship)
381
368
  extra_relations = []
382
369
  metadata[:nested_relationships].keys.reduce({}) do |props, nested_relationship|
383
- if metadata[:nested_relationships][relationship].present?
384
- props.merge!(metadata[:nested_relationships][nested_relationship].keys.each_with_object({}) do |relationship_type, inner_props|
385
- props.merge!(metadata[:nested_relationships][nested_relationship][relationship_type].keys.each_with_object({}) do |relationship, inner_inner_props|
386
-
387
- extra_relation_schema = {
388
- type: :object,
389
- properties: {
390
- type: { type: :string, default: metadata[:nested_relationships][nested_relationship][relationship_type][relationship].model_name },
391
- id: { type: :string },
392
- attributes: metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.attributes_schema
393
- }.merge(
394
- if metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.relationships == { belongs_to: {}, has_many: {} } || metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.relationships.blank?
395
- {}
396
- else
397
- result = { relationships: metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.relationships_schema(expand: false) }
398
- return {} if result == { relationships: {} }
399
- result
400
- end
401
- )
402
- }
403
-
404
- extra_relations << extra_relation_schema
405
- end
406
- )
407
- end
408
- )
409
- end
370
+ next if metadata[:nested_relationships][relationship].blank?
371
+
372
+ props.merge!(metadata[:nested_relationships][nested_relationship].keys.each_with_object({}) do |relationship_type, _inner_props|
373
+ props.merge!(metadata[:nested_relationships][nested_relationship][relationship_type].keys.each_with_object({}) do |relationship, _inner_inner_props|
374
+ extra_relation_schema = {
375
+ type: :object,
376
+ properties: {
377
+ type: { type: :string, default: metadata[:nested_relationships][nested_relationship][relationship_type][relationship].model_name },
378
+ id: { type: :string },
379
+ attributes: metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.attributes_schema
380
+ }.merge(
381
+ if metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.relationships == { belongs_to: {}, has_many: {} } || metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.relationships.blank?
382
+ {}
383
+ else
384
+ result = { relationships: metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.relationships_schema(expand: false) }
385
+ return {} if result == { relationships: {} }
386
+
387
+ result
388
+ end
389
+ )
390
+ }
391
+
392
+ extra_relations << extra_relation_schema
393
+ end)
394
+ end)
410
395
  end
411
396
 
412
397
  extra_relations
@@ -419,38 +404,39 @@ module Schemable
419
404
  }
420
405
  }
421
406
 
422
- schema = modify_schema(schema, additional_response_included, "included.items")
423
-
424
- schema
407
+ modify_schema(schema, additional_response_included, 'included.items')
425
408
  end
426
409
 
427
410
  # Generates the schema for the response of a resource or collection of resources in JSON API format.
428
411
  #
429
- # @param relations [Hash] A hash representing the relationships of the resource in the form of { belongs_to: {}, has_many: {} }.
430
- # If not provided, the relationships will be inferred from the model's associations.
412
+ # @param relations [Hash] A hash representing the relationships of the resource in the form of { belongs_to: {}, has_many: {} }. If not provided, the relationships will be inferred from the model's associations.
413
+ #
431
414
  # @param expand [Boolean] A boolean indicating whether to expand the relationships of the relationships in the schema.
415
+ #
432
416
  # @param exclude_from_expansion [Array] An array of relationship names to exclude from expansion.
417
+ #
433
418
  # @param multi [Boolean] A boolean indicating whether the response contains multiple resources.
419
+ #
434
420
  # @param nested [Boolean] A boolean indicating whether the response is to be expanded further than the first level of relationships. (expand relationships of relationships)
435
- # @param metadata [Hash] Additional metadata to include in the schema, usually received from the nested_relationships method sent by the response_schema method.
436
421
  #
437
- # @example
438
- # The returned schema will have a JSON API format, including the data (included attributes and relationships), included and meta keys.
422
+ # @param metadata [Hash] Additional metadata to include in the schema, usually received from the nested_relationships method sent by the response_schema method.
439
423
  #
440
424
  # @return [Hash] A hash representing the schema for the response.
425
+ #
426
+ # @example
427
+ # "The returned schema will have a JSON API format, including the data (included attributes and relationships), included and meta keys."
441
428
  def response_schema(relations = try(:relationships), expand: false, exclude_from_expansion: [], multi: false, nested: false, metadata: { nested_relationships: try(:nested_relationships) })
442
-
443
429
  data = {
444
430
  type: :object,
445
431
  properties: {
446
432
  type: { type: :string, default: itself.class.model_name },
447
433
  id: { type: :string },
448
- attributes: attributes_schema,
434
+ attributes: attributes_schema
449
435
  }.merge(
450
436
  if relations.blank? || relations == { belongs_to: {}, has_many: {} }
451
437
  {}
452
438
  else
453
- { relationships: relationships_schema(relations, expand: expand, exclude_from_expansion: exclude_from_expansion) }
439
+ { relationships: relationships_schema(relations, expand:, exclude_from_expansion:) }
454
440
  end
455
441
  )
456
442
  }
@@ -464,26 +450,26 @@ module Schemable
464
450
  }
465
451
  else
466
452
  {
467
- data: data
453
+ data:
468
454
  }
469
455
  end
470
456
 
471
457
  schema.merge!(
472
458
  if nested && expand
473
- included_schema(relations, expand: nested, exclude_from_expansion: exclude_from_expansion, metadata: metadata)
459
+ included_schema(relations, expand: nested, exclude_from_expansion:, metadata:)
474
460
  elsif !nested && expand
475
- included_schema(relations, expand: nested, exclude_from_expansion: exclude_from_expansion)
461
+ included_schema(relations, expand: nested, exclude_from_expansion:)
476
462
  else
477
463
  {}
478
464
  end
479
465
  ).merge!(
480
- if !expand
481
- { meta: meta }
482
- else
466
+ if expand
483
467
  {}
468
+ else
469
+ { meta: }
484
470
  end
485
471
  ).merge!(
486
- jsonapi: jsonapi
472
+ jsonapi:
487
473
  )
488
474
 
489
475
  {
@@ -492,29 +478,72 @@ module Schemable
492
478
  }
493
479
  end
494
480
 
495
- # Generates the schema for the request payload of a resource.
481
+ # Generates the schema for the creation request payload of a resource.
496
482
  #
497
- # @note The `additional_request_attributes` and `excluded_request_attributes` applied to the returned schema by this method.
483
+ # @note The `additional_create_request_attributes` and `excluded_create_request_attributes` applied to the returned schema by this method.
498
484
  # @note The `required_attributes` are applied to the returned schema by this method.
499
485
  # @note The `nullable_attributes` are applied to the returned schema by this method.
500
486
  #
487
+ # @return [Hash] A hash representing the schema for the request payload.
488
+ #
501
489
  # @example
502
- # {
503
- # type: :object,
504
- # properties: {
505
- # data: {
506
- # type: :object,
507
- # properties: {
508
- # firstName: { type: :string },
509
- # lastName: { type: :string }
510
- # },
511
- # required: [:firstName, :lastName]
512
- # }
513
- # }
514
- # }
490
+ # {
491
+ # type: :object,
492
+ # properties: {
493
+ # data: {
494
+ # type: :object,
495
+ # properties: {
496
+ # firstName: { type: :string },
497
+ # lastName: { type: :string }
498
+ # },
499
+ # required: [:firstName, :lastName]
500
+ # }
501
+ # }
502
+ # }
503
+ def create_request_schema
504
+ schema = {
505
+ type: :object,
506
+ properties: {
507
+ data: attributes_schema
508
+ }
509
+ }
510
+
511
+ schema = modify_schema(schema, additional_create_request_attributes, 'properties.data.properties')
512
+
513
+ excluded_create_request_attributes.each do |key|
514
+ schema = modify_schema(schema, {}, "properties.data.properties.#{key}", delete: true)
515
+ end
516
+
517
+ required_attributes = {
518
+ required: (schema.as_json['properties']['data']['properties'].keys - optional_create_request_attributes.map(&:to_s) - nullable_attributes.map(&:to_s)).map { |key| key.to_s.camelize(:lower).to_sym }
519
+ }
520
+
521
+ modify_schema(schema, required_attributes, 'properties.data')
522
+ end
523
+
524
+ # Generates the schema for the update request payload of a resource.
525
+ #
526
+ # @note The `additional_update_request_attributes` and `excluded_update_request_attributes` applied to the returned schema by this method.
527
+ # @note The `required_attributes` are applied to the returned schema by this method.
528
+ # @note The `nullable_attributes` are applied to the returned schema by this method.
515
529
  #
516
530
  # @return [Hash] A hash representing the schema for the request payload.
517
- def request_schema
531
+ #
532
+ # @example
533
+ # {
534
+ # type: :object,
535
+ # properties: {
536
+ # data: {
537
+ # type: :object,
538
+ # properties: {
539
+ # firstName: { type: :string },
540
+ # lastName: { type: :string }
541
+ # },
542
+ # required: [:firstName, :lastName]
543
+ # }
544
+ # }
545
+ # }
546
+ def update_request_schema
518
547
  schema = {
519
548
  type: :object,
520
549
  properties: {
@@ -522,27 +551,23 @@ module Schemable
522
551
  }
523
552
  }
524
553
 
525
- schema = modify_schema(schema, additional_request_attributes, "properties.data.properties")
554
+ schema = modify_schema(schema, additional_update_request_attributes, 'properties.data.properties')
526
555
 
527
- excluded_request_attributes.each do |key|
556
+ excluded_update_request_attributes.each do |key|
528
557
  schema = modify_schema(schema, {}, "properties.data.properties.#{key}", delete: true)
529
558
  end
530
559
 
531
560
  required_attributes = {
532
- required: schema.as_json['properties']['data']['properties'].keys - optional_request_attributes.map(&:to_s) - nullable_attributes.map(&:to_s)
561
+ required: (schema.as_json['properties']['data']['properties'].keys - optional_update_request_attributes.map(&:to_s) - nullable_attributes.map(&:to_s)).map { |key| key.to_s.camelize(:lower).to_sym }
533
562
  }
534
563
 
535
- schema = modify_schema(schema, required_attributes, "properties.data")
536
-
537
- schema
564
+ modify_schema(schema, required_attributes, 'properties.data')
538
565
  end
539
566
 
540
567
  # Returns the schema for the meta data of the response body.
541
- #
542
568
  # This is used to provide pagination information usually (in the case of a collection).
543
569
  #
544
- # Note that this is an opinionated schema and may not be suitable for all use cases.
545
- # If you need to override this schema, you can do so by overriding the `meta` method in your definition.
570
+ # @note Note that this is an opinionated schema and may not be suitable for all use cases. If you need to override this schema, you can do so by overriding the `meta` method in your definition.
546
571
  #
547
572
  # @return [Hash] The schema for the meta data of the response body.
548
573
  def meta
@@ -583,7 +608,7 @@ module Schemable
583
608
  properties: {
584
609
  version: {
585
610
  type: :string,
586
- default: "1.0"
611
+ default: '1.0'
587
612
  }
588
613
  }
589
614
  }
@@ -591,179 +616,213 @@ module Schemable
591
616
 
592
617
  # Returns the resource serializer to be used for serialization. This method must be implemented in the definition class.
593
618
  #
619
+ # @abstract This method must be implemented in the definition class.
620
+ #
594
621
  # @raise [NotImplementedError] If the method is not implemented in the definition class.
595
622
  #
596
- # @example V1::UserSerializer
623
+ # @return [Class] The resource serializer class.
597
624
  #
598
- # @abstract This method must be implemented in the definition class.
625
+ # @example
626
+ # V1::UserSerializer
599
627
  #
600
- # @return [Class] The resource serializer class.
601
628
  def serializer
602
629
  raise NotImplementedError, 'serializer method must be implemented in the definition class'
603
630
  end
604
631
 
605
632
  # Returns the attributes defined in the serializer (Auto generated from the serializer).
606
633
  #
607
- # @example
608
- # [:id, :name, :email, :created_at, :updated_at]
609
- #
610
634
  # @return [Array<Symbol>, nil] The attributes defined in the serializer or nil if there are none.
635
+ #
636
+ # @example
637
+ # [:id, :name, :email, :created_at, :updated_at]
611
638
  def attributes
612
639
  serializer.attribute_blocks.transform_keys { |key| key.to_s.underscore.to_sym }.keys || nil
613
640
  end
614
641
 
615
642
  # Returns the relationships defined in the serializer.
616
643
  #
617
- # Note that the format of the relationships is as follows: { belongs_to: { relationship_name: relationship_definition }, has_many: { relationship_name: relationship_definition }
644
+ # @return [Hash] The relationships defined in the serializer.
618
645
  #
619
- # @example
620
- # {
621
- # belongs_to: {
622
- # district: Swagger::Definitions::District,
623
- # user: Swagger::Definitions::User
624
- # },
625
- # has_many: {
626
- # applicants: Swagger::Definitions::Applicant,
627
- # }
628
- # }
646
+ # @note Note that the format of the relationships is as follows:
647
+ # { belongs_to: { relationship_name: relationship_definition }, has_many: { relationship_name: relationship_definition }
629
648
  #
630
- # @return [Hash] The relationships defined in the serializer.
649
+ # @example
650
+ # {
651
+ # belongs_to: {
652
+ # district: Swagger::Definitions::District,
653
+ # user: Swagger::Definitions::User
654
+ # },
655
+ # has_many: {
656
+ # applicants: Swagger::Definitions::Applicant,
657
+ # }
658
+ # }
631
659
  def relationships
632
660
  { belongs_to: {}, has_many: {} }
633
661
  end
634
662
 
635
663
  # Returns a hash of all the arrays defined for the model. The schema for each array is defined in the definition class manually.
636
- #
637
664
  # This method must be implemented in the definition class if there are any arrays.
638
665
  #
639
- # @example
640
- # {
641
- # metadata: {
642
- # type: :array,
643
- # items: {
644
- # type: :object, nullable: true,
645
- # properties: { name: { type: :string, nullable: true } }
646
- # }
647
- # }
648
- # }
649
- #
650
666
  # @return [Hash] The arrays of the model and their schemas.
667
+ #
668
+ # @example
669
+ # {
670
+ # metadata: {
671
+ # type: :array,
672
+ # items: {
673
+ # type: :object, nullable: true,
674
+ # properties: { name: { type: :string, nullable: true } }
675
+ # }
676
+ # }
677
+ # }
651
678
  def array_types
652
679
  {}
653
680
  end
654
681
 
655
- # Returns the attributes that are optional in the request body. This means that they are not required to be present in the request body thus they are taken out of the required array.
682
+ # Returns the attributes that are optional in the create request body. This means that they are not required to be present in the create request body thus they are taken out of the required array.
656
683
  #
657
- # @example
658
- # [:name, :email]
684
+ # @return [Array<Symbol>] The attributes that are optional in the create request body.
659
685
  #
660
- # @return [Array<Symbol>] The attributes that are optional in the request body.
661
- def optional_request_attributes
686
+ # @example
687
+ # [:name, :email]
688
+ def optional_create_request_attributes
662
689
  %i[]
663
690
  end
664
691
 
665
- # Returns the attributes that are nullable in the request/response body. This means that they can be present in the request/response body but they can be null.
692
+ # Returns the attributes that are optional in the update request body. This means that they are not required to be present in the update request body thus they are taken out of the required array.
666
693
  #
667
- # They are not required to be present in the request body.
694
+ # @return [Array<Symbol>] The attributes that are optional in the update request body.
668
695
  #
669
696
  # @example
670
- # [:name, :email]
697
+ # [:name, :email]
698
+ def optional_update_request_attributes
699
+ %i[]
700
+ end
701
+
702
+ # Returns the attributes that are nullable in the request/response body. This means that they can be present in the request/response body but they can be null.
703
+ # They are not required to be present in the request body.
671
704
  #
672
705
  # @return [Array<Symbol>] The attributes that are nullable in the request/response body.
706
+ #
707
+ # @example
708
+ # [:name, :email]
673
709
  def nullable_attributes
674
710
  %i[]
675
711
  end
676
712
 
677
- # Returns the additional request attributes that are not automatically generated. These attributes are appended to the request schema.
713
+ # Returns the additional create request attributes that are not automatically generated. These attributes are appended to the create request schema.
678
714
  #
679
- # @example
680
- # {
681
- # name: { type: :string }
682
- # }
715
+ # @return [Hash] The additional create request attributes that are not automatically generated (if any).
683
716
  #
684
- # @return [Hash] The additional request attributes that are not automatically generated (if any).
685
- def additional_request_attributes
717
+ # @example
718
+ # {
719
+ # name: { type: :string }
720
+ # }
721
+ def additional_create_request_attributes
686
722
  {}
687
723
  end
688
724
 
689
- # Returns the additional response attributes that are not automatically generated. These attributes are appended to the response schema.
725
+ # Returns the additional update request attributes that are not automatically generated. These attributes are appended to the update request schema.
726
+ #
727
+ # @return [Hash] The additional update request attributes that are not automatically generated (if any).
690
728
  #
691
729
  # @example
692
- # {
693
- # name: { type: :string }
694
- # }
730
+ # {
731
+ # name: { type: :string }
732
+ # }
733
+ def additional_update_request_attributes
734
+ {}
735
+ end
736
+
737
+ # Returns the additional response attributes that are not automatically generated. These attributes are appended to the response schema.
695
738
  #
696
739
  # @return [Hash] The additional response attributes that are not automatically generated (if any).
740
+ #
741
+ # @example
742
+ # {
743
+ # name: { type: :string }
744
+ # }
697
745
  def additional_response_attributes
698
746
  {}
699
747
  end
700
748
 
701
749
  # Returns the additional response relations that are not automatically generated. These relations are appended to the response schema's relationships.
702
750
  #
703
- # @example
704
- # {
705
- # users: {
706
- # type: :object,
707
- # properties: {
708
- # data: {
709
- # type: :array,
710
- # items: {
711
- # type: :object,
712
- # properties: {
713
- # id: { type: :string },
714
- # type: { type: :string }
715
- # }
716
- # }
717
- # }
718
- # }
719
- # }
720
- # }
721
- #
722
751
  # @return [Hash] The additional response relations that are not automatically generated (if any).
752
+ #
753
+ # @example
754
+ # {
755
+ # users: {
756
+ # type: :object,
757
+ # properties: {
758
+ # data: {
759
+ # type: :array,
760
+ # items: {
761
+ # type: :object,
762
+ # properties: {
763
+ # id: { type: :string },
764
+ # type: { type: :string }
765
+ # }
766
+ # }
767
+ # }
768
+ # }
769
+ # }
770
+ # }
723
771
  def additional_response_relations
724
772
  {}
725
773
  end
726
774
 
727
775
  # Returns the additional response included that are not automatically generated. These included are appended to the response schema's included.
728
776
  #
729
- # @example
730
- # {
731
- # type: :object,
732
- # properties: {
733
- # id: { type: :string },
734
- # type: { type: :string },
735
- # attributes: {
736
- # type: :object,
737
- # properties: {
738
- # name: { type: :string }
739
- # }
740
- # }
741
- # }
742
- # }
743
- #
744
777
  # @return [Hash] The additional response included that are not automatically generated (if any).
778
+ #
779
+ # @example
780
+ # {
781
+ # type: :object,
782
+ # properties: {
783
+ # id: { type: :string },
784
+ # type: { type: :string },
785
+ # attributes: {
786
+ # type: :object,
787
+ # properties: {
788
+ # name: { type: :string }
789
+ # }
790
+ # }
791
+ # }
792
+ # }
745
793
  def additional_response_included
746
794
  {}
747
795
  end
748
796
 
749
- # Returns the attributes that are excluded from the request schema.
750
- # These attributes are not required or not needed to be present in the request body.
797
+ # Returns the attributes that are excluded from the create request schema.
798
+ # These attributes are not required or not needed to be present in the create request body.
799
+ #
800
+ # @return [Array<Symbol>] The attributes that are excluded from the create request schema.
751
801
  #
752
802
  # @example
753
- # [:id, :updated_at, :created_at]
803
+ # [:id, :updated_at, :created_at]
804
+ def excluded_create_request_attributes
805
+ %i[]
806
+ end
807
+
808
+ # Returns the attributes that are excluded from the update request schema.
809
+ # These attributes are not required or not needed to be present in the update request body.
754
810
  #
755
- # @return [Array<Symbol>] The attributes that are excluded from the request schema.
756
- def excluded_request_attributes
811
+ # @return [Array<Symbol>] The attributes that are excluded from the update request schema.
812
+ #
813
+ # @example
814
+ # [:id, :updated_at, :created_at]
815
+ def excluded_update_request_attributes
757
816
  %i[]
758
817
  end
759
818
 
760
819
  # Returns the attributes that are excluded from the response schema.
761
820
  # These attributes are not needed to be present in the response body.
762
821
  #
763
- # @example
764
- # [:id, :updated_at, :created_at]
765
- #
766
822
  # @return [Array<Symbol>] The attributes that are excluded from the response schema.
823
+ #
824
+ # @example
825
+ # [:id, :updated_at, :created_at]
767
826
  def excluded_response_attributes
768
827
  %i[]
769
828
  end
@@ -771,10 +830,10 @@ module Schemable
771
830
  # Returns the relationships that are excluded from the response schema.
772
831
  # These relationships are not needed to be present in the response body.
773
832
  #
774
- # @example
775
- # [:users, :applicants]
776
- #
777
833
  # @return [Array<Symbol>] The relationships that are excluded from the response schema.
834
+ #
835
+ # @example
836
+ # [:users, :applicants]
778
837
  def excluded_response_relations
779
838
  %i[]
780
839
  end
@@ -782,73 +841,81 @@ module Schemable
782
841
  # Returns the included that are excluded from the response schema.
783
842
  # These included are not needed to be present in the response body.
784
843
  #
785
- # @todo This method is not used anywhere yet.
844
+ # @return [Array<Symbol>] The included that are excluded from the response schema.
786
845
  #
787
846
  # @example
788
- # [:users, :applicants]
847
+ # [:users, :applicants]
789
848
  #
790
- # @return [Array<Symbol>] The included that are excluded from the response schema.
849
+ # @todo
850
+ # This method is not used anywhere yet.
791
851
  def excluded_response_included
792
852
  %i[]
793
853
  end
794
854
 
795
855
  # Returns the relationships to be further expanded in the response schema.
796
856
  #
797
- # @example
798
- # {
799
- # applicants: {
800
- # belongs_to: {
801
- # district: Swagger::Definitions::District,
802
- # province: Swagger::Definitions::Province,
803
- # },
804
- # has_many: {
805
- # attachments: Swagger::Definitions::Upload,
806
- # }
807
- # }
808
- # }
809
- #
810
857
  # @return [Hash] The relationships to be further expanded in the response schema.
858
+ #
859
+ # @example
860
+ # {
861
+ # applicants: {
862
+ # belongs_to: {
863
+ # district: Swagger::Definitions::District,
864
+ # province: Swagger::Definitions::Province,
865
+ # },
866
+ # has_many: {
867
+ # attachments: Swagger::Definitions::Upload,
868
+ # }
869
+ # }
870
+ # }
811
871
  def nested_relationships
812
872
  {}
813
873
  end
814
874
 
815
875
  # Returns the model class (Constantized from the definition class name)
816
876
  #
817
- # @example
818
- # User
819
- #
820
877
  # @return [Class] The model class (Constantized from the definition class name)
878
+ #
879
+ # @example
880
+ # User
821
881
  def model
822
- self.class.name.gsub("Swagger::Definitions::", '').constantize
882
+ self.class.name.gsub('Swagger::Definitions::', '').constantize
823
883
  end
824
884
 
825
885
  # Returns the model name. Used for schema type naming.
826
886
  #
827
- # @example
828
- # 'users' for the User model
829
- # 'citizen_applications' for the CitizenApplication model
830
- #
831
887
  # @return [String] The model name.
888
+ #
889
+ # @example
890
+ # 'users' for the User model
891
+ # 'citizen_applications' for the CitizenApplication model
832
892
  def self.model_name
833
- name.gsub("Swagger::Definitions::", '').pluralize.underscore.downcase
893
+ name.gsub('Swagger::Definitions::', '').pluralize.underscore.downcase
834
894
  end
835
895
 
836
896
  # Returns the generated schemas in JSONAPI format that are used in the swagger documentation.
837
897
  #
838
- # @note This method is used for generating schema in 3 different formats: request, response and response expanded.
839
- # request: The schema for the request body.
840
- # response: The schema for the response body (without any relationships expanded), used for collection responses.
841
- # response expanded: The schema for the response body with all the relationships expanded, used for single resource responses.
898
+ # @return [Array<Hash>] The generated schemas in JSONAPI format that are used in the swagger documentation.
899
+ #
900
+ # @note This method is used for generating schema in 4 different formats: request (both create and update), response and response expanded.
901
+ #
902
+ # @option CreateRequest
903
+ # is the schema for the creation request body.
904
+ # @option UpdateRequest
905
+ # is the schema for the updating request body.
906
+ # @option Response
907
+ # is the schema for the response body (without any relationships expanded), used for collection responses.
908
+ # @option ResponseExpanded: The schema for the response body with all the relationships expanded, used for single resource responses.
842
909
  #
843
910
  # @note The returned schemas are in JSONAPI format are usually appended to the rswag component's 'schemas' in swagger_helper.
844
911
  #
845
912
  # @note The method can be overridden in the definition class if there are any additional customizations needed.
846
913
  #
847
- # @return [Array<Hash>] The generated schemas in JSONAPI format that are used in the swagger documentation.
848
914
  def self.definitions
849
- schema_instance = self.new
915
+ schema_instance = new
850
916
  [
851
- "#{schema_instance.model}Request": schema_instance.camelize_keys(schema_instance.request_schema),
917
+ "#{schema_instance.model}CreateRequest": schema_instance.camelize_keys(schema_instance.create_request_schema),
918
+ "#{schema_instance.model}UpdateRequest": schema_instance.camelize_keys(schema_instance.update_request_schema),
852
919
  "#{schema_instance.model}Response": schema_instance.camelize_keys(schema_instance.response_schema(multi: true)),
853
920
  "#{schema_instance.model}ResponseExpanded": schema_instance.camelize_keys(schema_instance.response_schema(expand: true))
854
921
  ]
@@ -858,10 +925,10 @@ module Schemable
858
925
  #
859
926
  # @param hash [Array | Hash] The hash with all the keys camelized.
860
927
  #
861
- # @example
862
- # { first_name: 'John', last_name: 'Doe' } => { firstName: 'John', lastName: 'Doe' }
863
- #
864
928
  # @return [Array | Hash] The hash with all the keys camelized.
929
+ #
930
+ # @example
931
+ # { first_name: 'John', last_name: 'Doe' } => { firstName: 'John', lastName: 'Doe' }
865
932
  def camelize_keys(hash)
866
933
  hash.deep_transform_keys { |key| key.to_s.camelize(:lower).to_sym }
867
934
  end