lib/esquema/builder.rb
# frozen_string_literal: true require_relative "property" require_relative "virtual_column" module Esquema # The Builder class is responsible for building a schema for an ActiveRecord model. class Builder attr_reader :model, :required_properties def initialize(model) raise ArgumentError, "Class is not an ActiveRecord model" unless model.ancestors.include? ActiveRecord::Base @model = model @properties = {} @required_properties = [] end # Builds the schema for the ActiveRecord model. # # @return [Hash] The built schema. def build_schema @build_schema ||= { title: build_title, description: build_description, type: build_type, properties: build_properties, required: required_properties }.compact end # @return [Hash] The schema for the ActiveRecord model. def schema build_schema end # Builds the properties for the schema. # # @return [Hash] The built properties. def build_properties add_properties_from_columns add_properties_from_associations add_virtual_properties @properties end # Builds the type for the schema. # # @return [String] The built type. def build_type model.respond_to?(:type) ? model.type : "object" end private # Adds virtual properties to the schema. def add_virtual_properties return unless schema_enhancements[:properties] virtual_properties = schema_enhancements[:properties].select { |_k, v| v[:virtual] } required_properties.concat(virtual_properties.keys) virtual_properties.each do |property_name, options| virtual_col = VirtualColumn.new(property_name, options) @properties[property_name] = Property.new(virtual_col, options) end end # Adds properties from columns to the schema. def add_properties_from_columns columns.each do |property| next if property.name.end_with?("_id") && config.exclude_foreign_keys? required_properties << property.name options = enhancement_for(property.name) @properties[property.name] ||= Property.new(property, options) end end # Adds properties from associations to the schema. def add_properties_from_associations associations.each do |association| next if config.exclude_associations? @properties[association.name] ||= Property.new(association) end end # Retrieves the columns of the model. # # @return [Array<ActiveRecord::ConnectionAdapters::Column>] The columns of the model. def columns model.columns.reject { |c| excluded_column?(c.name) } end # Retrieves the enhancement options for a property. # # @param property_name [Symbol] The name of the property. # @return [Hash] The enhancement options for the property. def enhancement_for(property_name) schema_enhancements.dig(:properties, property_name.to_sym) || {} end # Retrieves the associations of the model. # # @return [Array<ActiveRecord::Reflection::AssociationReflection>] The associations of the model. def associations return [] unless model.respond_to?(:reflect_on_all_associations) model.reflect_on_all_associations end # Checks if a column is excluded. # # @param column_name [String] The name of the column. # @return [Boolean] True if the column is excluded, false otherwise. def excluded_column?(column_name) raise ArgumentError, "Column name must be a string" unless column_name.is_a? String config.excluded_columns.include?(column_name.to_sym) end # Builds the title for the schema. # # @return [String] The built title. def build_title schema_enhancements[:model_title].presence || model.name.demodulize.humanize end # Builds the description for the schema. # # @return [String] The built description. def build_description schema_enhancements[:model_description].presence end # Retrieves the schema enhancements for the model. # # @return [Hash] The schema enhancements. def schema_enhancements if model.respond_to?(:schema_enhancements) model.schema_enhancements else {} end end # Retrieves the Esquema configuration. # # @return [Esquema::Configuration] The Esquema configuration. def config Esquema.configuration end end end