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
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]
endImagine you have two loosely coupled components:
FormLexeme.new
FormNode.newThey 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
endNote
Plugs is designed for defining dependencies on class-load. Please don't instantiate dependencies again and again at runtime.
.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.
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
endThe :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[:form, :html, :var] + B[:toc]The :toc plug will be added to the A instance.
Add gem 'plugs' to your Gemfile then:
bundle install