darthjee/sinclair

View on GitHub
lib/sinclair/method_definition.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
# frozen_string_literal: true

class Sinclair
  # @api private
  # @author darthjee
  #
  # Definition of the code or block to be aded as method
  class MethodDefinition
    include Sinclair::OptionsParser

    autoload :BlockHelper,      'sinclair/method_definition/block_helper'
    autoload :BlockDefinition,  'sinclair/method_definition/block_definition'
    autoload :CallDefinition,   'sinclair/method_definition/call_definition'
    autoload :StringDefinition, 'sinclair/method_definition/string_definition'
    autoload :ParameterBuilder, 'sinclair/method_definition/parameter_builder'
    autoload :ParameterHelper,  'sinclair/method_definition/parameter_helper'
    autoload :Stringifier,      'sinclair/method_definition/stringifier'

    # @attr_reader name
    #
    # name of the method
    #
    # @return [String,Symbol]
    attr_reader :name

    # Default options of initialization
    DEFAULT_OPTIONS = {
      cached: false
    }.freeze

    class << self
      # Builds a method that will return the same value always
      #
      # @return [Symbol]
      def default_value(method_name, value)
        define_method(method_name) { value }
      end

      # @param name    [String,Symbol] name of the method
      # @param code    [String] code to be evaluated as method
      # @param block   [Proc] block with code to be added as method
      # @param options [Hash] Options of construction
      # @option options cached [Boolean] Flag telling to create a block
      #   with cache
      #
      # builds a method definition based on arguments
      #
      # when block is given, returns a {BlockDefinition} and
      # returns a {StringDefinition} otherwise
      #
      # @return [Base]
      def from(name, code = nil, **options, &block)
        if block
          BlockDefinition.new(name, **options, &block)
        else
          StringDefinition.new(name, code, **options)
        end
      end

      # creates a definition
      #
      # The creation is based on type which will be used to infer
      # which subclass of {Sinclair::MethodDefinition} to be used
      #
      # If type is +nil+ then call is delegated to {.from} which will infer the type
      # from the arguments
      #
      # @param type [Symbol] the method definition type
      #
      # @return [Sinclair::MethodDefinition] an instance of a subclass
      def for(type, *args, **options, &block)
        return from(*args, **options, &block) unless type

        klass = const_get("#{type}_definition".camelize)
        klass.new(*args, **options, &block)
      end

      # Defines builder for a definition class
      #
      # @param builder_class [Class<MethodBuilder>]
      #
      # @return [Symbol] constant +:build+
      #
      # @!macro build_with
      #   @api private
      #
      #   @!method build(klass, type)
      #
      #   Builds the method defined
      #
      #   The method is built using {$1}
      #
      #   @param klass [Class] The class where the method will be built
      #   @param type [Symbol] type of method to be built
      #     - {MethodBuilder::CLASS_METHOD} : A class method will be built
      #     - {MethodBuilder::INSTANCE_METHOD} : An instance method will be built
      #
      #   @see $1#build
      #
      #   @return [Symbol] the name of the method built
      def build_with(builder_class)
        define_method(:build) do |klass, type|
          builder_class.build(klass, self, type: type)
        end
      end
    end

    # @param name    [String,Symbol] name of the method
    # @param options [Hash] Options of construction
    # @option options cached [Boolean] Flag telling to create
    #   a method with cache
    def initialize(name, **options)
      @name =    name
      @options = DEFAULT_OPTIONS.merge(options)
    end

    # @abstract
    #
    # Adds the method to given klass
    #
    # This should be implemented on child classes
    #
    # @param _klass [Class] class which will receive the new method
    #
    # @example (see MethodDefinition::StringDefinition#build)
    # @example (see MethodDefinition::BlockDefinition#build)
    # @example (see MethodDefinition::CallDefinition#build)
    #
    # @return [Symbol] name of the created method
    #
    # @raise NotImplementedError
    def build(_klass, _type)
      raise NotImplementedError, 'Build is implemented in subclasses. ' \
        "Use #{self.class}.from to initialize a proper object"
    end

    delegate :cached, to: :options_object

    # @private
    #
    # Flag telling to use cached method
    #
    # @return [Boolean]
    alias cached? cached
  end
end