module Avo::Concerns::HasFieldDiscovery
def associations
def associations @associations ||= reflections.reject do |key| attachment_associations.key?(key) || tags.key?(key) || rich_texts.key?(key) end end
def attachment_associations
def attachment_associations @attachment_associations ||= reflections.select { |_, r| r.options[:class_name] == "ActiveStorage::Attachment" } end
def base_association_options(reflection)
def base_association_options(reflection) { as: reflection.macro, searchable: true, sortable: true } end
def column_in_scope?(column_name)
Determines if a column is included in the discovery scope.
def column_in_scope?(column_name) (!@only || @only.include?(column_name)) && (!@except || !@except.include?(column_name)) end
def column_names_mapping
def column_names_mapping @column_names_mapping ||= Avo::Mappings::NAMES_MAPPING.dup .merge(Avo.configuration.column_names_mapping || {}) end
def column_types_mapping
def column_types_mapping @column_types_mapping ||= Avo::Mappings::FIELDS_MAPPING.dup .merge(Avo.configuration.column_types_mapping || {}) end
def create_association_field(association_name, reflection)
def create_association_field(association_name, reflection) options = base_association_options(reflection) options.merge!(polymorphic_options(reflection)) if reflection.options[:polymorphic] field(association_name, **options, **@field_options) end
def create_attachment_field(association_name, reflection)
def create_attachment_field(association_name, reflection) field_name = association_name&.to_s&.delete_suffix("_attachment")&.to_sym || association_name field_type = determine_attachment_field_type(reflection) field(field_name, as: field_type, **@field_options) end
def create_field(column_name, field_config)
def create_field(column_name, field_config) field_options = {as: field_config.dup.delete(:field).to_sym}.merge(field_config) field(column_name, **field_options.symbolize_keys, **@field_options.symbolize_keys) end
def detect_polymorphic_types(reflection)
def detect_polymorphic_types(reflection) ApplicationRecord.descendants.select { |klass| klass.reflections[reflection.plural_name] } end
def determine_attachment_field_type(reflection)
def determine_attachment_field_type(reflection) ( reflection.is_a?(ActiveRecord::Reflection::HasOneReflection) || reflection.is_a?(ActiveStorage::Reflection::HasOneAttachedReflection) ) ? :file : :files end
def determine_field_config(attribute, column)
def determine_field_config(attribute, column) model_enums[attribute.to_s] || self.class.column_names_mapping[attribute] || self.class.column_types_mapping[column.type] end
def discover_associations(only: nil, except: nil, **field_options)
def discover_associations(only: nil, except: nil, **field_options) setup_discovery_options(only, except, field_options) return unless safe_model_class.respond_to?(:reflections) discover_attachments discover_basic_associations end
def discover_attachments
def discover_attachments attachment_associations.each do |association_name, reflection| next unless column_in_scope?(association_name) create_attachment_field(association_name, reflection) end end
def discover_basic_associations
def discover_basic_associations associations.each do |association_name, reflection| next unless column_in_scope?(association_name) create_association_field(association_name, reflection) end end
def discover_by_type(associations, as_type)
def discover_by_type(associations, as_type) associations.each_key do |association_name| next unless column_in_scope?(association_name) field association_name, as: as_type, **@field_options.merge(name: yield(association_name)) end end
def discover_columns(only: nil, except: nil, **field_options)
def discover_columns(only: nil, except: nil, **field_options) setup_discovery_options(only, except, field_options) return unless safe_model_class.respond_to?(:columns_hash) discoverable_columns.each do |column_name, column| process_column(column_name, column) end discover_tags discover_rich_texts end
def discover_rich_texts
def discover_rich_texts rich_texts.each_key do |association_name| next unless column_in_scope?(association_name) field_name = association_name&.to_s&.delete_prefix("rich_text_")&.to_sym || association_name field field_name, as: :trix, **@field_options end end
def discover_tags
def discover_tags tags.each_key do |association_name| next unless column_in_scope?(association_name) field( tag_field_name(association_name), as: :tags, acts_as_taggable_on: tag_field_name(association_name), **@field_options ) end end
def discoverable_columns
def discoverable_columns model_db_columns.reject do |column_name, _| skip_column?(column_name) end end
def ignore_reflection?(name)
def ignore_reflection?(name) %w[blob blobs tags].include?(name.split("_").pop) || name.to_sym == :taggings end
def model_db_columns
def model_db_columns @model_db_columns ||= safe_model_class.columns_hash.symbolize_keys.except(*COLUMN_NAMES_TO_IGNORE) end
def model_enums
def model_enums @model_enums ||= if safe_model_class.respond_to?(:defined_enums) safe_model_class.defined_enums.transform_values do |enum| { field: :select, enum: } end else {} end.with_indifferent_access end
def polymorphic_options(reflection)
def polymorphic_options(reflection) {polymorphic_as: reflection.name, types: detect_polymorphic_types(reflection)} end
def process_column(column_name, column)
def process_column(column_name, column) field_config = determine_field_config(column_name, column) return unless field_config create_field(column_name, field_config) end
def reflections
def reflections @reflections ||= safe_model_class.reflections.symbolize_keys.reject do |name, _| ignore_reflection?(name.to_s) end end
def rich_text_column?(column_name)
def rich_text_column?(column_name) rich_texts.key?(:"rich_text_#{column_name}") end
def rich_texts
def rich_texts @rich_texts ||= reflections.select { |_, r| r.options[:class_name] == "ActionText::RichText" } end
def safe_model_class
Fetches the model class, falling back to the items_holder parent record in certain instances
def safe_model_class respond_to?(:model_class) ? model_class : @items_holder.parent.model_class rescue ActiveRecord::NoDatabaseError, ActiveRecord::ConnectionNotEstablished nil end
def setup_discovery_options(only, except, field_options)
def setup_discovery_options(only, except, field_options) @only = only @except = except @field_options = field_options end
def skip_column?(column_name)
def skip_column?(column_name) !column_in_scope?(column_name) || reflections.key?(column_name) || rich_text_column?(column_name) end
def tag_field_name(association_name)
def tag_field_name(association_name) association_name&.to_s&.delete_suffix("_taggings")&.pluralize&.to_sym || association_name end
def tags
def tags @tags ||= reflections.select { |_, r| r.options[:as] == :taggable } end