schemable 0.1.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/schemable.rb CHANGED
@@ -1,946 +1,69 @@
1
- # frozen_string_literal: true
2
-
3
1
  require_relative 'schemable/version'
4
- require 'active_support/concern'
5
-
2
+ require_relative 'schemable/definition'
3
+ require_relative 'schemable/configuration'
4
+ require_relative 'schemable/schema_modifier'
5
+ require_relative 'schemable/attribute_schema_generator'
6
+ require_relative 'schemable/relationship_schema_generator'
7
+ require_relative 'schemable/included_schema_generator'
8
+ require_relative 'schemable/response_schema_generator'
9
+ require_relative 'schemable/request_schema_generator'
10
+
11
+ # The Schemable module provides a set of classes and methods for generating and modifying schemas in JSONAPI format.
12
+ # It includes classes for generating attribute, relationship, included, response, and request schemas.
13
+ # It also provides a configuration class for setting up the module's behavior.
14
+ #
15
+ # @example
16
+ # The following example shows how to use the Schemable module to generate a schema for a Comment model.
17
+ #
18
+ # # config/initializers/schemable.rb
19
+ # Schemable.configure do |config|
20
+ # #... chosen configuration options ...
21
+ # end
22
+ #
23
+ # # lib/swagger/definitions/comment.rb
24
+ # class Swagger::Definitions::Comment < Schemable::Definition; end
25
+ #
26
+ # # whenever you need to generate the schema for a Comment model.
27
+ # # i.e. in RSwag's swagger_helper.rb
28
+ #
29
+ # # spec/swagger_helper.rb
30
+ # # ...
31
+ # RSpec.configure do |config|
32
+ #
33
+ # config.swagger_docs = {
34
+ # # ...
35
+ # components: {
36
+ # # ...
37
+ # schemas: Swagger::Definitions::Comment.generate.flatten.reduce({}, :merge)
38
+ # # ...
39
+ # }
40
+ # # ...
41
+ # }
42
+ # # ...
43
+ # end
44
+ #
45
+ # @see Schemable::Definition
46
+ # @see Schemable::Configuration
47
+ # @see Schemable::SchemaModifier
48
+ # @see Schemable::AttributeSchemaGenerator
49
+ # @see Schemable::RelationshipSchemaGenerator
50
+ # @see Schemable::IncludedSchemaGenerator
51
+ # @see Schemable::ResponseSchemaGenerator
52
+ # @see Schemable::RequestSchemaGenerator
6
53
  module Schemable
54
+ # Error class for handling exceptions specific to the Schemable module.
7
55
  class Error < StandardError; end
8
56
 
9
- extend ActiveSupport::Concern
10
-
11
- included do
12
- # Maps a given type name to a corresponding JSON schema object that represents that type.
13
- #
14
- # @param type_name [String, Symbol] A String or Symbol representing the type of the property to be mapped.
15
- #
16
- # @return [Hash, nil] A Hash that represents a JSON schema object for the given type, or nil if the type is not recognized.
17
- def type_mapper(type_name)
18
- {
19
- text: { type: :string },
20
- string: { type: :string },
21
- integer: { type: :integer },
22
- float: { type: (configs[:float_as_string] ? :string : :number).to_s.to_sym, format: :float },
23
- decimal: { type: (configs[:decimal_as_string] ? :string : :number).to_s.to_sym, format: :double },
24
- datetime: { type: :string, format: :'date-time' },
25
- date: { type: :string, format: :date },
26
- time: { type: :string, format: :time },
27
- boolean: { type: :boolean },
28
- trueclass: { type: :boolean, default: true },
29
- falseclass: { type: :boolean, default: false },
30
- binary: { type: :string, format: :binary },
31
- json: { type: :object, properties: {} },
32
- jsonb: { type: :object, properties: {} },
33
- array: { type: :array, items: { anyOf: [
34
- { type: :string }, { type: :integer }, { type: :number, format: :float }, { type: :number, format: :double }, { type: :boolean }, { type: :object, properties: {} }
35
- ] } },
36
- hash: { type: :object, properties: {} },
37
- object: { type: :object, properties: {} }
38
- }[type_name.try(:to_sym)]
39
- end
40
-
41
- # Modify a JSON schema object by merging new properties into it or deleting a specified path.
42
- #
43
- # @param original_schema [Hash] The original schema object to modify.
44
- #
45
- # @param new_props [Hash] The new properties to merge into the schema.
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
- #
49
- # @param delete [Boolean] Whether to delete the property at the given path, if it exists.
50
- #
51
- # @raise [ArgumentError] If `delete` is true but `given_path` is nil, or if `given_path` does not exist in the original schema.
52
- #
53
- # @return [Hash] A new schema object with the specified modifications.
54
- #
55
- # @example
56
- # `Merge new properties into the schema`
57
- #
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: {}}
69
- def modify_schema(original_schema, new_props, given_path = nil, delete: false)
70
- return new_props if original_schema.nil?
71
-
72
- raise ArgumentError, 'Cannot delete without a given path' if given_path.nil? && delete
73
-
74
- if given_path.present?
75
- path_segments = given_path.split('.').map(&:to_sym)
76
-
77
- if path_segments.size == 1
78
- raise ArgumentError, 'Given path does not exist in the original schema' unless original_schema.key?(path_segments.first)
79
- else
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
- end
82
-
83
- path_hash = path_segments.reverse.reduce(new_props) { |a, n| { n => a } }
84
-
85
- if delete
86
- new_schema = original_schema.deep_dup
87
- parent_hash = path_segments.size > 1 ? new_schema.dig(*path_segments[0..-2]) : new_schema
88
- parent_hash.delete(path_segments.last)
89
- new_schema
90
- else
91
- original_schema.deep_merge(path_hash)
92
- end
93
- else
94
- original_schema.deep_merge(new_props)
95
- end
96
- end
97
-
98
- # Returns a JSON Schema attribute definition for a given attribute on the model.
99
- #
100
- # @param attribute [Symbol] The name of the attribute.
101
- #
102
- # @raise [NoMethodError] If the `model` object does not respond to `columns_hash`.
103
- #
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
- #
106
- # @example
107
- # attribute_schema(:title) => { "type": "string" }
108
- def attribute_schema(attribute)
109
- # Get the column hash for the attribute
110
- column_hash = model.columns_hash[attribute.to_s]
111
-
112
- # Check if this attribute has a custom JSON Schema definition
113
- return array_types[attribute] if array_types.keys.include?(attribute)
114
-
115
- return additional_response_attributes[attribute] if additional_response_attributes.keys.include?(attribute)
116
-
117
- # Check if this is an array attribute
118
- return type_mapper(:array) if column_hash.as_json.try(:[], 'sql_type_metadata').try(:[], 'sql_type').include?('[]')
119
-
120
- # Map the column type to a JSON Schema type if none of the above conditions are met
121
- response = type_mapper(column_hash.try(:type))
122
-
123
- # If the attribute is nullable, modify the schema accordingly
124
- return modify_schema(response, { nullable: true }) if response && nullable_attributes.include?(attribute)
125
-
126
- # If attribute is an enum, modify the schema accordingly
127
- return modify_schema(response, { type: :string, enum: model.defined_enums[attribute.to_s].keys }) if response && model.defined_enums.key?(attribute.to_s)
128
-
129
- return response unless response.nil?
130
-
131
- # If we haven't found a schema type yet, try to infer it from the type of the attribute's value in the instance data
132
- type_from_factory = @instance.as_json['data']['attributes'][attribute.to_s.camelize(:lower)].class.name.downcase
133
- response = type_mapper(type_from_factory) if type_from_factory.present?
134
-
135
- return response unless response.nil?
136
-
137
- # If we still haven't found a schema type, default to object
138
- type_mapper(:object)
139
- rescue NoMethodError
140
- # Log a warning if the attribute does not exist on the model
141
- Rails.logger.warn("\e[33mWARNING: #{model} does not have an attribute named \e[31m#{attribute}\e[0m")
142
- {}
143
- end
144
-
145
- # Returns a JSON Schema for the model's attributes.
146
- # This method is used to generate the schema for the `attributes` that are automatically generated by using the `attribute_schema` method on each attribute.
147
- #
148
- # @note The `additional_response_attributes` and `excluded_response_attributes` are applied to the schema returned by this method.
149
- #
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
- # }
160
- def attributes_schema
161
- schema = {
162
- type: :object,
163
- properties: attributes.index_with do |attr|
164
- attribute_schema(attr)
165
- end
166
- }
167
-
168
- # modify the schema to include additional response relations
169
- schema = modify_schema(schema, additional_response_attributes, 'properties')
170
-
171
- # modify the schema to exclude response relations
172
- excluded_response_attributes.each do |key|
173
- schema = modify_schema(schema, {}, "properties.#{key}", delete: true)
174
- end
175
-
176
- schema
177
- end
178
-
179
- # Generates the schema for the relationships of a resource.
180
- #
181
- # @note The `additional_response_relations` and `excluded_response_relations` are applied to the schema returned by this method.
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
- #
185
- # @param expand [Boolean] A boolean indicating whether to expand the relationships in the schema.
186
- #
187
- # @param exclude_from_expansion [Array] An array of relationship names to exclude from expansion.
188
- #
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
- # }
210
- def relationships_schema(relations = try(:relationships), expand: false, exclude_from_expansion: [])
211
- return {} if relations.blank?
212
- return {} if relations == { belongs_to: {}, has_many: {} }
213
-
214
- schema = {
215
- type: :object,
216
- properties: relations.reduce({}) do |props, (relation_type, relation_definitions)|
217
- non_expanded_data_properties = {
218
- type: :object,
219
- properties: {
220
- meta: {
221
- type: :object,
222
- properties: {
223
- included: { type: :boolean, default: false }
224
- }
225
- }
226
- }
227
- }
228
-
229
- if relation_type == :has_many
230
- props.merge!(
231
- relation_definitions.keys.index_with do |relationship|
232
- result = {
233
- type: :object,
234
- properties: {
235
- data: {
236
- type: :array,
237
- items: {
238
- type: :object,
239
- properties: {
240
- id: { type: :string },
241
- type: { type: :string, default: relation_definitions[relationship].model_name }
242
- }
243
- }
244
- }
245
- }
246
- }
247
-
248
- result = non_expanded_data_properties if !expand || exclude_from_expansion.include?(relationship)
249
-
250
- result
251
- end
252
- )
253
- else
254
- props.merge!(
255
- relation_definitions.keys.index_with do |relationship|
256
- result = {
257
- type: :object,
258
- properties: {
259
- data: {
260
- type: :object,
261
- properties: {
262
- id: { type: :string },
263
- type: { type: :string, default: relation_definitions[relationship].model_name }
264
-
265
- }
266
- }
267
- }
268
- }
269
-
270
- result = non_expanded_data_properties if !expand || exclude_from_expansion.include?(relationship)
271
-
272
- result
273
- end
274
- )
275
- end
276
- end
277
- }
278
-
279
- # modify the schema to include additional response relations
280
- schema = modify_schema(schema, additional_response_relations, 'properties')
281
-
282
- # modify the schema to exclude response relations
283
- excluded_response_relations.each do |key|
284
- schema = modify_schema(schema, {}, "properties.#{key}", delete: true)
285
- end
286
-
287
- schema
288
- end
289
-
290
- # Generates the schema for the included resources in a response.
291
- #
292
- # @note The `additional_response_includes` and `excluded_response_includes` (yet to be implemented) are applied to the schema returned by this method.
293
- #
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
- #
296
- # @param expand [Boolean] A boolean indicating whether to expand the relationships of the relationships in the schema.
297
- #
298
- # @param exclude_from_expansion [Array] An array of relationship names to exclude from expansion.
299
- #
300
- # @param metadata [Hash] Additional metadata to include in the schema, usually received from the nested_relationships method sent by the response_schema method.
301
- #
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
- # }
329
- def included_schema(relations = try(:relationships), expand: false, exclude_from_expansion: [], metadata: {})
330
- return {} if relations.blank?
331
- return {} if relations == { belongs_to: {}, has_many: {} }
332
-
333
- schema = {
334
- included: {
335
- type: :array,
336
- items: {
337
- anyOf:
338
- relations.reduce([]) do |props, (_relation_type, relation_definitions)|
339
- props + relation_definitions.keys.reduce([]) do |props, relationship|
340
- props + [
341
- unless exclude_from_expansion.include?(relationship)
342
- {
343
- type: :object,
344
- properties: {
345
- type: { type: :string, default: relation_definitions[relationship].model_name },
346
- id: { type: :string },
347
- attributes: begin
348
- relation_definitions[relationship].new.attributes_schema || {}
349
- rescue NoMethodError
350
- {}
351
- end
352
- }.merge(
353
- if relation_definitions[relationship].new.relationships != { belongs_to: {}, has_many: {} } || relation_definitions[relationship].new.relationships.blank?
354
- if !expand || metadata.blank?
355
- { relationships: relation_definitions[relationship].new.relationships_schema(expand: false) }
356
- else
357
- { relationships: relation_definitions[relationship].new.relationships_schema(relations = metadata[:nested_relationships][relationship], expand: true, exclude_from_expansion:) }
358
- end
359
- else
360
- {}
361
- end
362
- )
363
- }
364
- end
365
- ].concat(
366
- [
367
- if expand && metadata.present? && exclude_from_expansion.exclude?(relationship)
368
- extra_relations = []
369
- metadata[:nested_relationships].keys.reduce({}) do |props, nested_relationship|
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)
395
- end
396
-
397
- extra_relations
398
- end
399
- ].flatten
400
- ).compact_blank
401
- end
402
- end
403
- }
404
- }
405
- }
406
-
407
- modify_schema(schema, additional_response_included, 'included.items')
408
- end
409
-
410
- # Generates the schema for the response of a resource or collection of resources in JSON API format.
411
- #
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
- #
414
- # @param expand [Boolean] A boolean indicating whether to expand the relationships of the relationships in the schema.
415
- #
416
- # @param exclude_from_expansion [Array] An array of relationship names to exclude from expansion.
417
- #
418
- # @param multi [Boolean] A boolean indicating whether the response contains multiple resources.
419
- #
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)
421
- #
422
- # @param metadata [Hash] Additional metadata to include in the schema, usually received from the nested_relationships method sent by the response_schema method.
423
- #
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."
428
- def response_schema(relations = try(:relationships), expand: false, exclude_from_expansion: [], multi: false, nested: false, metadata: { nested_relationships: try(:nested_relationships) })
429
- data = {
430
- type: :object,
431
- properties: {
432
- type: { type: :string, default: itself.class.model_name },
433
- id: { type: :string },
434
- attributes: attributes_schema
435
- }.merge(
436
- if relations.blank? || relations == { belongs_to: {}, has_many: {} }
437
- {}
438
- else
439
- { relationships: relationships_schema(relations, expand:, exclude_from_expansion:) }
440
- end
441
- )
442
- }
443
-
444
- schema = if multi
445
- {
446
- data: {
447
- type: :array,
448
- items: data
449
- }
450
- }
451
- else
452
- {
453
- data:
454
- }
455
- end
456
-
457
- schema.merge!(
458
- if nested && expand
459
- included_schema(relations, expand: nested, exclude_from_expansion:, metadata:)
460
- elsif !nested && expand
461
- included_schema(relations, expand: nested, exclude_from_expansion:)
462
- else
463
- {}
464
- end
465
- ).merge!(
466
- if expand
467
- {}
468
- else
469
- { meta: }
470
- end
471
- ).merge!(
472
- jsonapi:
473
- )
474
-
475
- {
476
- type: :object,
477
- properties: schema
478
- }
479
- end
480
-
481
- # Generates the schema for the creation request payload of a resource.
482
- #
483
- # @note The `additional_create_request_attributes` and `excluded_create_request_attributes` applied to the returned schema by this method.
484
- # @note The `required_attributes` are applied to the returned schema by this method.
485
- # @note The `nullable_attributes` are applied to the returned schema by this method.
486
- #
487
- # @return [Hash] A hash representing the schema for the request payload.
488
- #
489
- # @example
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.
529
- #
530
- # @return [Hash] A hash representing the schema for the request payload.
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
547
- schema = {
548
- type: :object,
549
- properties: {
550
- data: attributes_schema
551
- }
552
- }
553
-
554
- schema = modify_schema(schema, additional_update_request_attributes, 'properties.data.properties')
555
-
556
- excluded_update_request_attributes.each do |key|
557
- schema = modify_schema(schema, {}, "properties.data.properties.#{key}", delete: true)
558
- end
559
-
560
- required_attributes = {
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 }
562
- }
57
+ class << self
58
+ # Accessor for the module's configuration.
59
+ attr_accessor :configuration
563
60
 
564
- modify_schema(schema, required_attributes, 'properties.data')
565
- end
566
-
567
- # Returns the schema for the meta data of the response body.
568
- # This is used to provide pagination information usually (in the case of a collection).
569
- #
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.
571
- #
572
- # @return [Hash] The schema for the meta data of the response body.
573
- def meta
574
- {
575
- type: :object,
576
- properties: {
577
- page: {
578
- type: :object,
579
- properties: {
580
- totalPages: {
581
- type: :integer,
582
- default: 1
583
- },
584
- count: {
585
- type: :integer,
586
- default: 1
587
- },
588
- limitValue: {
589
- type: :integer,
590
- default: 1
591
- },
592
- currentPage: {
593
- type: :integer,
594
- default: 1
595
- }
596
- }
597
- }
598
- }
599
- }
600
- end
601
-
602
- # Returns the schema for the JSONAPI version.
603
- #
604
- # @return [Hash] The schema for the JSONAPI version.
605
- def jsonapi
606
- {
607
- type: :object,
608
- properties: {
609
- version: {
610
- type: :string,
611
- default: '1.0'
612
- }
613
- }
614
- }
615
- end
616
-
617
- # Returns the resource serializer to be used for serialization. This method must be implemented in the definition class.
618
- #
619
- # @abstract This method must be implemented in the definition class.
620
- #
621
- # @raise [NotImplementedError] If the method is not implemented in the definition class.
622
- #
623
- # @return [Class] The resource serializer class.
624
- #
625
- # @example
626
- # V1::UserSerializer
627
- #
628
- def serializer
629
- raise NotImplementedError, 'serializer method must be implemented in the definition class'
630
- end
631
-
632
- # Returns the attributes defined in the serializer (Auto generated from the serializer).
633
- #
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]
638
- def attributes
639
- serializer.attribute_blocks.transform_keys { |key| key.to_s.underscore.to_sym }.keys || nil
640
- end
641
-
642
- # Returns the relationships defined in the serializer.
643
- #
644
- # @return [Hash] The relationships defined in the serializer.
645
- #
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 }
648
- #
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
- # }
659
- def relationships
660
- { belongs_to: {}, has_many: {} }
661
- end
662
-
663
- # Returns a hash of all the arrays defined for the model. The schema for each array is defined in the definition class manually.
664
- # This method must be implemented in the definition class if there are any arrays.
665
- #
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
- # }
678
- def array_types
679
- {}
680
- end
681
-
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.
683
- #
684
- # @return [Array<Symbol>] The attributes that are optional in the create request body.
685
- #
686
- # @example
687
- # [:name, :email]
688
- def optional_create_request_attributes
689
- %i[]
690
- end
691
-
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.
693
- #
694
- # @return [Array<Symbol>] The attributes that are optional in the update request body.
695
- #
696
- # @example
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.
704
- #
705
- # @return [Array<Symbol>] The attributes that are nullable in the request/response body.
706
- #
707
- # @example
708
- # [:name, :email]
709
- def nullable_attributes
710
- %i[]
711
- end
712
-
713
- # Returns the additional create request attributes that are not automatically generated. These attributes are appended to the create request schema.
714
- #
715
- # @return [Hash] The additional create request attributes that are not automatically generated (if any).
716
- #
717
- # @example
718
- # {
719
- # name: { type: :string }
720
- # }
721
- def additional_create_request_attributes
722
- {}
723
- end
724
-
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).
728
- #
729
- # @example
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.
738
- #
739
- # @return [Hash] The additional response attributes that are not automatically generated (if any).
740
- #
741
- # @example
742
- # {
743
- # name: { type: :string }
744
- # }
745
- def additional_response_attributes
746
- {}
747
- end
748
-
749
- # Returns the additional response relations that are not automatically generated. These relations are appended to the response schema's relationships.
750
- #
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
- # }
771
- def additional_response_relations
772
- {}
773
- end
774
-
775
- # Returns the additional response included that are not automatically generated. These included are appended to the response schema's included.
776
- #
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
- # }
793
- def additional_response_included
794
- {}
795
- end
796
-
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.
801
- #
802
- # @example
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.
810
- #
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
816
- %i[]
817
- end
818
-
819
- # Returns the attributes that are excluded from the response schema.
820
- # These attributes are not needed to be present in the response body.
821
- #
822
- # @return [Array<Symbol>] The attributes that are excluded from the response schema.
823
- #
824
- # @example
825
- # [:id, :updated_at, :created_at]
826
- def excluded_response_attributes
827
- %i[]
828
- end
829
-
830
- # Returns the relationships that are excluded from the response schema.
831
- # These relationships are not needed to be present in the response body.
832
- #
833
- # @return [Array<Symbol>] The relationships that are excluded from the response schema.
834
- #
835
- # @example
836
- # [:users, :applicants]
837
- def excluded_response_relations
838
- %i[]
839
- end
840
-
841
- # Returns the included that are excluded from the response schema.
842
- # These included are not needed to be present in the response body.
843
- #
844
- # @return [Array<Symbol>] The included that are excluded from the response schema.
845
- #
846
- # @example
847
- # [:users, :applicants]
848
- #
849
- # @todo
850
- # This method is not used anywhere yet.
851
- def excluded_response_included
852
- %i[]
853
- end
854
-
855
- # Returns the relationships to be further expanded in the response schema.
856
- #
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
- # }
871
- def nested_relationships
872
- {}
873
- end
874
-
875
- # Returns the model class (Constantized from the definition class name)
876
- #
877
- # @return [Class] The model class (Constantized from the definition class name)
878
- #
879
- # @example
880
- # User
881
- def model
882
- self.class.name.gsub('Swagger::Definitions::', '').constantize
883
- end
884
-
885
- # Returns the model name. Used for schema type naming.
886
- #
887
- # @return [String] The model name.
888
- #
889
- # @example
890
- # 'users' for the User model
891
- # 'citizen_applications' for the CitizenApplication model
892
- def self.model_name
893
- name.gsub('Swagger::Definitions::', '').pluralize.underscore.downcase
894
- end
895
-
896
- # Returns the generated schemas in JSONAPI format that are used in the swagger documentation.
897
- #
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.
909
- #
910
- # @note The returned schemas are in JSONAPI format are usually appended to the rswag component's 'schemas' in swagger_helper.
911
- #
912
- # @note The method can be overridden in the definition class if there are any additional customizations needed.
913
- #
914
- def self.definitions
915
- schema_instance = new
916
- [
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),
919
- "#{schema_instance.model}Response": schema_instance.camelize_keys(schema_instance.response_schema(multi: true)),
920
- "#{schema_instance.model}ResponseExpanded": schema_instance.camelize_keys(schema_instance.response_schema(expand: true))
921
- ]
922
- end
923
-
924
- # Given a hash, it returns a new hash with all the keys camelized.
925
- #
926
- # @param hash [Array | Hash] The hash with all the keys camelized.
927
- #
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' }
932
- def camelize_keys(hash)
933
- hash.deep_transform_keys { |key| key.to_s.camelize(:lower).to_sym }
934
- end
935
-
936
- # Returns a json of config options for the definition class.
937
- #
938
- # @return [Hash] The config options for the definition class.
61
+ # Configures the module. If a block is given, it yields the current configuration.
939
62
  #
940
- # @example
941
- # { decimal_as_string: true }
942
- def configs
943
- {}
63
+ # @yield [Configuration] The current configuration.
64
+ def configure
65
+ @configuration ||= Configuration.new
66
+ yield(@configuration) if block_given?
944
67
  end
945
68
  end
946
69
  end