lib/scorpion/object.rb
require "scorpion/attribute_set"
module Scorpion
# Identifies objects that are injected by {Scorpion scorpions} that inject
# {Scorpion#hunt hunted} dependencies.
module Object
# ============================================================================
# @!group Attributes
#
include Scorpion::Method
# @!attribute
# @return [Scorpion::AttributeSet] the set of injected attributes and their
# settings.
def injected_attributes
self.class.injected_attributes
end
#
# @!endgroup Attributes
# Injects one of the {#injected_attributes} into the object.
# @param [Scorpion::Attribute] attribute to be fed.
# @param [Object] dependency the value of the attribute
# @visibility private
#
# This method is used by the {#scorpion} to feed the object. Do not call it
# directly.
def inject_dependency( attribute, dependency )
send "#{ attribute.name }=", dependency
end
# Infest the object with a scoprion and prepare it to be fed.
def self.infest( base )
base.extend Scorpion::Object::ClassMethods
if base.is_a? Class
base.class_exec do
# Create a new instance of this class with all non-lazy dependencies
# satisfied.
# @param [Hunt] hunt that this instance will be used to satisfy.
def self.spawn( hunt, *args, &block )
object = new( *args, &block )
object.send :scorpion=, hunt.scorpion
# Go hunt for dependencies that are not lazy and initialize the
# references.
hunt.inject object
object
end
end
# base.subclasses.each do |sub|
# infest( sub ) unless sub < Scorpion::Object
# end
end
end
def self.included( base )
infest( base )
super
end
def self.prepended( base )
infest( base )
super
end
private
# Called after the object has been initialized and fed all its required
# dependencies. It should be used in place of #initialize when the
# constructor needs access to injected attributes.
def on_injected
end
# Feed dependencies from a hash into their associated attributes.
# @param [Hash] dependencies hash describing attributes to inject.
# @param [Boolean] overwrite existing attributes with values in in the hash.
def inject_from( dependencies, overwrite = false )
injected_attributes.each do |attr|
next unless dependencies.key? attr.name
if overwrite || !send( "#{ attr.name }?" )
send( "#{ attr.name }=", dependencies[ attr.name ] )
end
end
dependencies
end
# Injects dependenices from the hash and removes them from the hash.
# @see #inject_from
def inject_from!( dependencies, overwrite = false )
injected_attributes.each do |attr|
next unless dependencies.key? attr.name
val = dependencies.delete( attr.name )
if overwrite || !send( "#{ attr.name }?" )
send( "#{ attr.name }=", val )
end
end
dependencies
end
module ClassMethods
# Tells a {Scorpion} what to inject into the class when it is constructed
# @return [nil]
# @see AttributeSet#define
def depend_on( &block )
injected_attributes.define &block
build_injected_attributes
validate_initializer_injections
end
# Define a single dependency and accessor.
# @param [Symbol] name of the dependency.
# @param [Class,Module,Symbol] contract describing the desired behavior of the dependency.
def attr_dependency( name, contract, **options )
attr = injected_attributes.define_attribute name, contract, **options
build_injected_attribute attr
adjust_injected_attribute_visibility attr
validate_initializer_injections
attr
end
# @!attribute
# @return [Scorpion::AttributeSet] the set of injected attributes.
def injected_attributes
@injected_attributes ||= begin
attrs = AttributeSet.new
attrs.inherit! superclass.injected_attributes if superclass.respond_to? :injected_attributes
attrs
end
end
# @!attribute
# @return [Scorpion::AttributeSet] the set of injected attributes.
def initializer_injections
@initializer_injections ||= begin
if superclass.respond_to?( :initializer_injections )
superclass.initializer_injections
else
AttributeSet.new
end
end
end
private
def validate_initializer_injections
initializer_injections.each do |attr|
injected = injected_attributes[ attr.name ]
if injected.contract != attr.contract
fail Scorpion::ContractMismatchError.new( self, attr, injected )
end
end
end
def build_injected_attributes
injected_attributes.each do |attr|
build_injected_attribute attr
adjust_injected_attribute_visibility attr
end
end
def build_injected_attribute( attr )
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{ attr.name }
@#{ attr.name } ||= begin
attr = injected_attributes[ :#{ attr.name } ]
( scorpion_hunt || scorpion ).fetch( attr.contract )
end
end
def #{ attr.name }=( value )
@#{ attr.name } = value
end
def #{ attr.name }?
!!@#{ attr.name }
end
RUBY
end
def adjust_injected_attribute_visibility( attr )
unless attr.public?
class_eval <<-RUBY, __FILE__, __LINE__ + 1
private :#{ attr.name }=
private :#{ attr.name }?
RUBY
end
if attr.private?
class_eval <<-RUBY, __FILE__, __LINE__ + 1
private :#{ attr.name }
RUBY
end
end
end
end
end