Skip to content

Repository files navigation

Gem version GitHub repo Codeberg repo

Plugs

Plugs are dependencies that are loosely coupled internally but externally appear as one entity such as a feature, config object or plugin. A plug is reusable, shareable and overridable.

Note

Plugs is for local dependency management. For global dependency management see Providers

Advantages:

  • Keep unit tests isolated from upstream setup/configuration
  • Add plugins and config to your interfaces via a simple :key
  • Plugs can be nested in a tree and "sliced" by key. The parent key includes its dependencies and their keys

Example

Plugs is used by Antlers to configure which elements the Abstract Syntax Tree supports.

require 'plugs'

# Define the dependencies.
class MyPlugs
  include Plugs

  plug(:html) do
    plug(:node) do
      require_relative '../nodes/html_node'
      HTMLNode
    end
  end

  plug(:form) do
    plug(:lexeme) do
      require_relative '../lexemes/form_lexeme'
      FormLexeme
    end

    plug(:node) do
      require_relative '../nodes/form_node'
      FormNode
    end
  end
end

# Get a top level dependency and its children.
def new(plugs: MyPlugs[:html, :form])
  plugs.to_a # => [HTMLNode, FormLexeme, FormNode].
end

# Get all "node" plugs regardless of their parent.
def new(plugs: MyPlugs[:node])
  plugs.to_a # => [HTMLNode, FormNode]
end

Use Cases

Loosely Coupled Dependencies

Imagine you have two loosely coupled components:

FormLexeme.new
FormNode.new

They are unit tested individually and created at completely separate stages.

But they are both enabled or disabled at the same time depending on configuration:

Parser.new(node_types: Elements[:form])

Plugs lets you pull them together:

plug(:form) do
  plug(:lexeme) do
    require_relative '../lexemes/form_lexeme'
    FormLexeme
  end

  plug(:node) do
    require_relative '../nodes/form_node'
    FormNode
  end
end

API

Note

Plugs is designed for defining dependencies on class-load. Please don't instantiate dependencies again and again at runtime.

.plug(:key)

.plug() lets you define plugs in a nested tree-like structure.

Plugs can be sliced out from the nested tree. For example you could get all plugs that are :lexeme or just the single that is :form... doesn't matter where they sit in the hierarchy. On the flip side if you slice a single plug that has children then only that plug and it's children will be included. It goes both ways.

.lazy { ... }

Plugs defined via .plug() are eager loaded by default so that they can define child plugs. Wrap the "return value" section of your plug in a lazy block to defer evaluation until that plug is called, either directly or indirectly as a dependency:

plug(:eager_a) do
  plug(:lazy_b) do
    lazy do
      require_relative '../lazy_b'
      LazyB
    end

    plug(:eager_c) do
      require_relative '../eager_c'
      EagerC
    end
  end
end

The :lazy_b and :eager_c plugs will still be called eagerly and their keys added to the dependency tree, but the lazy block will not be evaluated until called via something like MyPlugs[:eager_a].to_a.

A[] + B[]

A[:form, :html, :var] + B[:toc]

The :toc plug will be added to the A instance.

Installation

Add gem 'plugs' to your Gemfile then:

bundle install

About

Keep code loosely coupled. Keep features tightly coupled

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages