Class: Schemable::AttributeSchemaGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/schemable/attribute_schema_generator.rb

Overview

The AttributeSchemaGenerator class is responsible for generating JSON schemas for model attributes. It includes methods for generating the overall schema and individual attribute schemas.

See Also:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model_definition) ⇒ AttributeSchemaGenerator

Initializes a new AttributeSchemaGenerator instance.

Examples:

generator = AttributeSchemaGenerator.new(model_definition)

Parameters:

  • model_definition (ModelDefinition)

    The model definition to generate the schema for.



14
15
16
17
18
19
20
# File 'lib/schemable/attribute_schema_generator.rb', line 14

def initialize(model_definition)
  @model_definition = model_definition
  @model = model_definition.model
  @configuration = Schemable.configuration
  @schema_modifier = SchemaModifier.new
  @response = nil
end

Instance Attribute Details

#configurationObject (readonly)

Returns the value of attribute configuration.



7
8
9
# File 'lib/schemable/attribute_schema_generator.rb', line 7

def configuration
  @configuration
end

#modelObject (readonly)

Returns the value of attribute model.



7
8
9
# File 'lib/schemable/attribute_schema_generator.rb', line 7

def model
  @model
end

#model_definitionObject (readonly)

Returns the value of attribute model_definition.



7
8
9
# File 'lib/schemable/attribute_schema_generator.rb', line 7

def model_definition
  @model_definition
end

#responseObject (readonly)

Returns the value of attribute response.



7
8
9
# File 'lib/schemable/attribute_schema_generator.rb', line 7

def response
  @response
end

#schema_modifierObject (readonly)

Returns the value of attribute schema_modifier.



7
8
9
# File 'lib/schemable/attribute_schema_generator.rb', line 7

def schema_modifier
  @schema_modifier
end

Instance Method Details

#generateHash

Generates the JSON schema for the model attributes.

Examples:

schema = generator.generate

Returns:

  • (Hash)

    The generated schema.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/schemable/attribute_schema_generator.rb', line 27

def generate
  schema = {
    type: :object,
    properties: @model_definition.attributes.index_with do |attr|
      generate_attribute_schema(attr)
    end
  }

  # Rename enum attributes to remove the suffix or prefix if mongoid is used
  if @configuration.orm == :mongoid
    schema[:properties].transform_keys! do |key|
      key.to_s.gsub(@configuration.enum_prefix_for_simple_enum || @configuration.enum_suffix_for_simple_enum, '')
    end
  end

  # modify the schema to include additional response relations
  schema = @schema_modifier.add_properties(schema, @model_definition.additional_response_attributes, 'properties')

  # modify the schema to exclude response relations
  @model_definition.excluded_response_attributes.each do |key|
    schema = @schema_modifier.delete_properties(schema, "properties.#{key}")
  end

  schema
end

#generate_attribute_schema(attribute) ⇒ Hash

Generates the JSON schema for a specific attribute.

Examples:

attribute_schema = generator.generate_attribute_schema(:attribute_name)

Parameters:

  • attribute (Symbol, String)

    The attribute to generate the schema for.

Returns:

  • (Hash)

    The generated schema for the attribute.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/schemable/attribute_schema_generator.rb', line 59

def generate_attribute_schema(attribute)
  if @configuration.orm == :mongoid
    # Get the column hash for the attribute
    attribute_hash = @model.fields[attribute.to_s]

    # Check if this attribute has a custom JSON Schema definition
    return @model_definition.array_types[attribute] if @model_definition.array_types.keys.include?(attribute.to_sym)
    return @model_definition.additional_response_attributes[attribute] if @model_definition.additional_response_attributes.keys.include?(attribute)

    # Check if this is an array attribute
    return @configuration.type_mapper(:array) if attribute_hash.try(:[], 'options').try(:[], 'type') == 'Array'

    # Check if this is an enum attribute
    @response = if attribute_hash.name.end_with?('_cd')
                  @configuration.type_mapper(:string)
                else
                  # Map the column type to a JSON Schema type if none of the above conditions are met
                  @configuration.type_mapper(attribute_hash.try(:type).to_s.downcase.to_sym)
                end

  elsif @configuration.orm == :active_record
    # Get the column hash for the attribute
    attribute_hash = @model.columns_hash[attribute.to_s]

    # Check if this attribute has a custom JSON Schema definition
    return @model_definition.array_types[attribute] if @model_definition.array_types.keys.include?(attribute.to_sym)
    return @model_definition.additional_response_attributes[attribute] if @model_definition.additional_response_attributes.keys.include?(attribute)

    # Check if this is an array attribute
    return @configuration.type_mapper(:array) if attribute_hash.as_json.try(:[], 'sql_type_metadata').try(:[], 'sql_type').include?('[]')

    # Map the column type to a JSON Schema type if none of the above conditions are met
    @response = @configuration.type_mapper(attribute_hash.try(:type))

  else
    raise 'ORM not supported'
  end

  # If the attribute is nullable, modify the schema accordingly
  return @schema_modifier.add_properties(@response, { nullable: true }, '.') if @response && @model_definition.nullable_attributes.include?(attribute)

  # If attribute is an enum, modify the schema accordingly
  if @configuration.custom_defined_enum_method && @model.respond_to?(@configuration.custom_defined_enum_method)
    defined_enums = @model.send(@configuration.custom_defined_enum_method)
    enum_attribute = attribute.to_s.gsub(@configuration.enum_prefix_for_simple_enum || @configuration.enum_suffix_for_simple_enum, '').to_s
    if @response && defined_enums[enum_attribute].present?
      return @schema_modifier.add_properties(
        @response,
        {
          enum: defined_enums[enum_attribute].keys,
          default: @model_definition.default_value_for_enum_attributes[attribute.to_sym] || defined_enums[enum_attribute].keys.first
        },
        '.'
      )
    end
  elsif @model.respond_to?(:defined_enums) && @response && @model.defined_enums.key?(attribute.to_s)
    return @schema_modifier.add_properties(
      @response,
      {
        enum: @model.defined_enums[attribute.to_s].keys,
        default: @model_definition.default_value_for_enum_attributes[attribute.to_sym] || @model.defined_enums[attribute.to_s].keys.first
      },
      '.'
    )
  end

  return @response unless @response.nil?

  # 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
  if @configuration.use_serialized_instance
    serialized_instance = @model_definition.serialized_instance

    type_from_instance = serialized_instance.as_json['data']['attributes'][attribute.to_s.camelize(:lower)]&.class&.name&.downcase

    @response = @configuration.type_mapper(type_from_instance) if type_from_instance.present?

    return @response unless @response.nil?
  end

  # If we still haven't found a schema type, default to object
  @configuration.type_mapper(:object)
rescue NoMethodError
  # Log a warning if the attribute does not exist on the @model
  Rails.logger.warn("\e[33mWARNING: #{@model} does not have an attribute named \e[31m#{attribute}\e[0m")
  {}
end