0.01
Low commit activity in last 3 years
A long-lived project that still receives updates
Declare dependency attributes that have default implementations that are diagnostic substitutes or null objects
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

 Project Readme

Dependency

Declare dependency attributes that have default implementations that are diagnostic substitutes or null objects.

Overview

An object's dependencies are references to other objects that typically represent external I/O or system-level resources. Dependencies should be declared explicitly to differentiate them from other instance attributes.

A dependency attribute's value should never be nil. A dependency attribute is always assigned a default value that is an inert object that will not raise a NoMethodError when method's are invoked on the dependency, rather than a nil.

A dependency's default value may be a null object, an object with a static interface that mimics the a dependency's declared interface, or an entirely arbitrary object that can be constructed by implementing an explicit override of the dependency's construction.

A static mimic can be configured to record the invocations of it's methods.

Constructing an Object that Has Dependencies

When an object that has declared dependencies is initialized, the values of its dependencies will never be nil. A dependency attribute will be assigned either a null object or an implementation of a substitute that conforms to the interface that a dependency may optionally be configured to support.

class Example
  dependency :some_dependency
end

e = Example.new
e.dependency.nil? #=> false

To construct an object and provide its dependencies with real, operational values, implement a constructor method that will be used by operational code.

class Example
  dependency :some_dependency

  def self.build
    instance = new
    instance.some_dependency = SomeDependency.new
    instance
  end
end

# Non-operational, substitute dependencies when the instance has only been initialized
e = Example.new
e.dependency.class #=> NullObject

# Operational dependencies when the instance has been fully constructed
e = Example.build
e.dependency.class #=> SomeDependency

Substitutes

Null Object

A null object responds to any method invoked on it. If the dependency declaration doesn't include an interface to mimic, the dependency attribute's default value will be a null object.

class Example
  dependency :some_dependency
end

e = Example.new
e.some_dependency.anything # => No error raised

Static Mimic

A static mimic responds only to the methods of the dependency's declared interface.

class SomeClass
  def some_method
    # ...
  end
end

class Example
  dependency :some_dependency, SomeClass
end

e = Example.new
e.some_dependency.some_method # => No error raised
e.some_dependency.anything # => NoMethodError raised

Invocation Recording

Static mimic dependencies record the methods invoked on them.

class Example
  dependency :some_dependency, SomeClass
end

e = Example.new
e.some_dependency.some_method

e.invoked?(:some_method) #=> true

Invocation recording can be disabled using the dependency declaration's record parameter.

class Example
  dependency :some_dependency, SomeClass, record: false
end

For more information about invocation recording, see the Mimic library's documentation.

Specialized Mimic

A static mimic can be specialized by implementing an inner module named Substitute in the dependency constant. If that inner Substitute module exists in the dependency constant's namespace, it will be mixed in to the mimic.

class SomeClass
  def some_method
    # ...
  end

  module Substitute
    def some_other_method
      # ...
    end
  end
end

class Example
  dependency :some_dependency, SomeClass
end

e = Example.new
e.some_dependency.some_other_method

Specialized Mimic with Invocation Recording and Predicates

The principle use of the inner Substitute module mixin is the definition of override methods that record their invocations, as well as the definition of domain-specific predicate methods that provide an applicative API for determining whether a substitute was actuated.

Including the RecordInvocation module allows a substitute's methods to be defined as recordable.

class SomeClass
  def some_method
    # ...
  end

  module Substitute
    include Mimic::Recorder::Predicate

    record def some_method
      # Substitute implementation
    end

    def some_predicate?
      invoked?(:some_method)
    end
  end
end

class Example
  dependency :some_dependency, SomeClass

  def call
    some_dependency.some_method
  end
end

e = Example.new
e.()

e.dependency.some_predicate?
#=> true

For more information about invocation recording, see the RecordInvocation library's documentation and the Mimic library's documentation.

Constructed Substitute

A dependency's default value can be an entirely arbitrary object that can be constructed by implementing an explicit override of the dependency's construction.

If the dependency's constant has an inner namespace named Substitute, and if that Substitute module has a self.build class method, then the result of invoking the build method will be used as the dependency's default value.

class SomeClass
  def some_method
    # ...
  end

  module Substitute
    def self.build
      SomeOtherSubstitute.new
    end
  end
end

class Example
  dependency :some_dependency, SomeClass
end

e = Example.new
e.some_dependency #=> SomeOtherSubstitute

Constructing and Testing a Substitute

The Dependency library offers a single utility to construct any substitute of a dependency, whether it be a null object, a static mimic, or a constructed dependency.

some_class_substitute_instance = Dependency::Substitute.build(SomeClass)

This constructor will return an instance of whichever kind of substitute that is defined by the class passed to it.

The most common use for this utility is the construction of diagnostic substitute directly within a test script to test any specialized behavior that the substitute may have.

Activation

The Dependency module must be included in a class that will use the dependency macro.

class Example
  include Dependency

  dependency :some_dependency
end

However, the Dependency module can be included anywhere within the object hierarchy by using the Dependency.activate method.

class Example
end

Dependency.activate Example

class Example
  dependency :some_attr
end

As in the example above, for a class to be "activated" for Dependency, it must have already been defined. The dependency class method must be visible to the class using it, either my including it in the class directly, or by including it in any super class of the using class.

If no class is specified, Dependency is included in Object, making the dependency macro available in all Ruby classes. Note that this isn't recommended practice.

Dependency.activate

class Example
  dependency :some_attr
end

License

The dependency library is released under the MIT License.