# frozen_string_literal: true module ConvenientService module Core module ClassMethods ## # @see ConvenientService::Core::Entities::Config#concerns # # @internal # NOTE: The instance variable is named `@__convenient_service_config__` intentionally in order to decrease the possibility of accidental redefinition by the end-user. # NOTE: An `attr_reader` for `@__convenient_service_config__` is NOT created intentionally in order to NOT pollute the end-user class interface. # def concerns(...) (@__convenient_service_config__ ||= Entities::Config.new(klass: self)).concerns(...) end ## # @see ConvenientService::Core::Entities::Config#middlewares # # @internal # NOTE: The instance variable is named `@__convenient_service_config__` intentionally in order to decrease the possibility of accidental redefinition by the end-user. # NOTE: An `attr_reader` for `@__convenient_service_config__` is NOT created intentionally in order to NOT pollute the end-user class interface. # def middlewares(...) (@__convenient_service_config__ ||= Entities::Config.new(klass: self)).middlewares(...) end ## # Commits config when called for the first time. # Does nothing for the subsequent calls. # # @param trigger [ConvenientService::Support::UniqueValue] # @return [Boolean] true if called for the first time, false otherwise (similarly as Kernel#require). # # @see https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-require # # @internal # NOTE: The instance variable is named `@__convenient_service_config__` intentionally in order to decrease the possibility of accidental redefinition by the end-user. # NOTE: An `attr_reader` for `@__convenient_service_config__` is NOT created intentionally in order to NOT pollute the end-user class interface. # def commit_config!(trigger: ConvenientService::Core::Constants::Triggers::USER) (@__convenient_service_config__ ||= Entities::Config.new(klass: self)).commit!(trigger: trigger) .tap { ConvenientService.logger.debug { "[Core] Committed config for `#{self}` | Triggered by `.commit_config!(trigger: #{trigger.inspect})` " } } end ## # @see https://thoughtbot.com/blog/always-define-respond-to-missing-when-overriding # @see https://stackoverflow.com/a/3304683/12201472 # # @param method_name [Symbol, String] # @param include_private [Boolean] # @return [Boolean] # def respond_to_missing?(method_name, include_private = false) return true if singleton_class.method_defined?(method_name) return true if concerns.class_method_defined?(method_name) if include_private return true if singleton_class.private_method_defined?(method_name) return true if concerns.private_class_method_defined?(method_name) end false end private ## # Includes `concerns` into the mixing class. # If `method` is still NOT defined, raises `NoMethodError`, otherwise - retries to call the `method`. # # @param method [Symbol] # @param args [Array<Object>] # @param kwargs [Hash{Symbol => Object}] # @param block [Proc, nil] # @return [void] # # @internal # IMPORTANT: `method_missing` MUST be thread-safe. # # NOTE: `__send__` is used instead of `Support::SafeMethod` intentionally, since checking whether a method is defined is performed earlier by `Utils::Module.class_method_defined?`. # def method_missing(method, *args, **kwargs, &block) commit_config!(trigger: Constants::Triggers::CLASS_METHOD_MISSING) return super unless Utils::Module.class_method_defined?(self, method, private: true) return super if middlewares(method, scope: :class).defined_without_super_method? ConvenientService.logger.debug { "[Core] Committed config for `#{self}` | Triggered by `method_missing` | Method: `.#{method}`" } __send__(method, *args, **kwargs, &block) end end end end