module Eco module API module Common # Helpers for dynamic object loading based on class declaration # @note # - this helpers aim to boost the usage of the ruby language in complex api configurations. module ClassAutoLoader include Eco::API::Common::ClassHelpers # To enable the class autoloader, you should use this method def autoloads_children_of(klass) class_resolver :autoloader_class, klass @autoloaded_class = klass end # Resolves the class `autoloader_class` if it has been defined via `autoloads_children_of` def autoloaded_class return nil unless @autoloaded_class autoloader_class end # To which restricted namespaces this class autoloads from def autoloaded_namespaces(type = :include) @autoloaded_namespaces ||= {} @autoloaded_namespaces[type] ||= [] end # To restrict which namespaces it is allowed to load from def autoload_namespace(*namespaces) _autoload_namespace(:include, *namespaces) end # To ignore certain namespaces this class should not autoload from def autoload_namespace_ignore(*namespaces) _autoload_namespace(:ignore, *namespaces) end def _autoload_namespace(type, *namespaces) autoloaded_namespaces(type).concat(namespaces) unless namespaces.empty? end # @param constant [Class, String] a class or namespace we want to check auto-load entitlement thereof. # @return [Boolean] determines if a given namespace is entitled for autoloading def autoload_class?(constant) constants = "#{constant}".split("::").compact autoload = true unless autoloaded_namespaces(:include).empty? autoload = autoloaded_namespaces(:include).any? do |ns| "#{ns}".split("::").compact.zip(constants).all? {|(r, c)| r == c} end end unless autoloaded_namespaces(:ignore).empty? autoload &&= autoloaded_namespaces(:ignore).none? do |ns| "#{ns}".split("::").compact.zip(constants).all? {|(r, c)| r == c} end end autoload end # As children are loaded as they are declared, we should not load twice same children. def autoloaded_children @auto_loaded_children ||= [] end # Children classes of `autoloader_class` that have not been created an instance of. def unloaded_children return [] unless autoloaded_class new_detected = new_classes known_class!(*new_detected) descendants(parent_class: autoloaded_class, scope: new_detected).select do |child_class| !autoloaded_children.include?(child_class) && autoload_class?(child_class) end.sort end # It loads/creates a new instance of children classes pending to be loaded. # @return [Boolean] `true` if there were children loaded, `false` otherwise. def autoload_children(object) return false if !autoloaded_class || @loading_children pending_children = unloaded_children return false if pending_children.empty? @loading_children = true pending_children.each do |klass| @child = klass.new(object) autoloaded_children.push(klass) end @loading_children = false true end # Known namespaces serves the purpose to discover recently added namespaces # provided that the namespace discovery is optimized def known_classes @known_classes ||= [] end # Add to known namespaces def known_class!(*classes) known_classes.concat(classes) end # List all new namespaces def new_classes ObjectSpace.each_object(::Class).to_a - known_classes end end end end end