lib/scorpion/dependency_map.rb
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