lib/cattri/deferred_attributes.rb



# frozen_string_literal: true

require "set"

module Cattri
  # Provides support for defining attributes within a module that should be
  # applied later to any class or module that includes or extends it.
  #
  # This allows DSL modules to define Cattri attributes without prematurely
  # applying them to themselves, deferring application to the including/extending context.
  module DeferredAttributes
    # Hook into the module extension lifecycle to ensure deferred attributes are
    # applied when the module is included or extended.
    #
    # @param base [Module] the module that extended this module
    # @return [void]
    def self.extended(base)
      return if base.singleton_class.ancestors.include?(Hook)

      base.singleton_class.prepend(Hook)
    end

    # Hook methods for inclusion/extension that trigger deferred application.
    module Hook
      # Called when a module including `DeferredAttributes` is included into another module/class.
      #
      # @param target [Module] the including class or module
      # @return [void]
      def included(target)
        apply_deferred_attributes(target) if respond_to?(:apply_deferred_attributes) # steep:ignore
      end

      # Called when a module including `DeferredAttributes` is extended into another module/class.
      #
      # @param target [Module] the extending class or module
      # @return [void]
      def extended(target)
        apply_deferred_attributes(target) if respond_to?(:apply_deferred_attributes) # steep:ignore
      end
    end

    # Registers an attribute to be applied later when this module is included or extended.
    #
    # @param attribute [Cattri::Attribute] the attribute to defer
    # @return [void]
    def defer_attribute(attribute)
      deferred_attributes[attribute.name] = attribute
    end

    # Applies all deferred attributes to the target class or module.
    #
    # This is triggered automatically by the {Hook} on `included` or `extended`.
    #
    # @param target [Module] the class or module to apply the attributes to
    # @return [void]
    def apply_deferred_attributes(target)
      context = Cattri::Context.new(target)

      deferred_attributes.each_value do |attribute|
        Cattri::AttributeCompiler.define_accessor(attribute, context)
      end
    end

    private

    # Internal storage of deferred attributes for this module.
    #
    # @return [Hash{Symbol => Cattri::Attribute}]
    def deferred_attributes
      @deferred_attributes ||= {}
    end
  end
end