phallguy/scorpion

View on GitHub
lib/scorpion/dependency_map.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
require "forwardable"

module Scorpion
  # {#chart} available {Dependency} and {#find} them based on desired
  # {Scorpion::Attribute attributes}.
  class DependencyMap
    include Enumerable
    extend Forwardable

    # ============================================================================
    # @!group Attributes
    #

    # @return [Scorpion] the scorpion that created the map.
      attr_reader :scorpion

    # @return [Set] the set of dependency charted on this map.
      attr_reader :dependency_set
      private :dependency_set

    # @return [Set] the set of dependencies charted on this map that is shared
    #   with all child dependencies.
      attr_reader :shared_dependency_set
      private :shared_dependency_set

    # @return [Set] the active dependency set either {#dependency_set} or {#shared_dependency_set}
      attr_reader :active_dependency_set
      private :active_dependency_set

    #
    # @!endgroup Attributes

    def initialize( scorpion )
      @scorpion              = scorpion
      reset
    end

    # Find {Dependency} that matches the requested `contract`.
    # @param [Class,Module,Symbol] contract describing the desired behavior of the dependency.
    # @return [Dependency] the dependency matching the attribute.
    def find( contract )
      dependency_set.find { |p| p.satisfies?( contract ) } ||
        shared_dependency_set.find { |p| p.satisfies?( contract ) }
    end

    # Chart the {Dependency} that this hunting map can {#find}.
    #
    # The block is executed in the context of DependencyMap if the block does not
    # accept any arguments so that {#hunt_for}, {#capture} and {#share} can be
    # called as methods.
    #
    # @example
    #
    #   cache = {}
    #   chart do
    #     self #=> DependencyMap
    #     hunt_for Repository
    #     capture  Cache, return: cache # => NoMethodError
    #   end
    #
    #   chart do |map|
    #     map.hunt_for Repository
    #     map.capture  Cache, return: cache # => No problem
    #   end
    #
    # @return [self]
    def chart( &block )
      return unless block_given?

      if block.arity == 1
        yield self
      else
        instance_eval &block
      end

      self
    end

    # Define {Dependency} that can be found on this map by `contract`.
    #
    # If a block is given, it will be used build the actual instances of the
    # dependency for the {Scorpion}.
    #
    # @param [Class,Module,Symbol] contract describing the desired behavior of the dependency.
    # @return [Dependency] the dependency to be hunted for.
    def hunt_for( contract, **options, &builder )
      active_dependency_set.unshift define_dependency( contract, options, &builder )
    end

    # Captures a single dependency and returns the same instance fore each request
    # for the resource.
    # @see #hunt_for
    # @return [Dependency] the dependency to be hunted for.
    def capture( contract, **options, &builder )
      active_dependency_set.unshift Dependency::CapturedDependency.new( define_dependency( contract, options, &builder ) ) # rubocop:disable Metrics/LineLength
    end
    alias_method :singleton, :capture

    # Share dependencies defined within the block with all child scorpions.
    # @return [Dependency] the dependency to be hunted for.
    def share( &block )
      old_set = active_dependency_set
      @active_dependency_set = shared_dependency_set
      yield
    ensure
      @active_dependency_set = old_set
    end

    # @visibility private
    def each( &block )
      dependency_set.each &block
    end
    delegate [ :empty?, :blank?, :present? ] => :dependency_set

    # Replicates the dependency in `other_map` into this map.
    # @param [Scorpion::DependencyMap] other_map to replicate from.
    # @return [self]
    def replicate_from( other_map )
      other_map.each do |dependency|
        if replica = dependency.replicate
          dependency_set << replica
        end
      end

      self
    end

    # Remove all dependency mappings.
    def reset
      @dependency_set&.each &:release
      @shared_dependency_set&.each &:release

      @dependency_set        = @active_dependency_set = []
      @shared_dependency_set = []
    end

    private

      def define_dependency( contract, options, &builder )
        Dependency.define contract, options, &builder
      end
  end
end