Skip to content

antoineLeclercq/dependency

 
 

Repository files navigation

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: https://github.com/eventide-project/mimic#recording-invocations-of-a-mimic-object

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 Predicates

The principle use of the inner Substitute module mixin is the declaration of specialized predicates using the Mimic library's Mimic::Recorder::Predicate module and its predicate macro.

class SomeClass
  def some_method
    # ...
  end

  module Substitute
    include Mimic::Recorder::Predicate

    predicate :some_method_invoked?, method_name: :some_method
  end
end

class Example
  dependency :some_dependency, SomeClass
end

e = Example.new
e.some_dependency.some_method

e.some_method_invoked? #=> true

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

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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Ruby 82.6%
  • Shell 17.4%