New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

wirer

Package Overview
Dependencies
Maintainers
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

wirer

  • 0.5.2
  • Rubygems
  • Socket score

Version published
Maintainers
2
Created
Source

Wirer

A lightweight dependency injection framework to help wire up objects in Ruby.

Some usage examples for now:

container = Wirer do |c|

SHOWING THE CONTAINER HOW TO CONSTRUCT AN EXISTING CLASS:

This is registered as providing class Logger.

It will be constructed via Logger.new('/option_for_logger.txt')

c.add Logger, '/option_for_logger.txt'

This is registered as providing class Logger, and also providing feature :special_logger

which can then be used to request it in particular situations

c.add :special_logger, Logger, '/special_log.txt'

You can supply a custom block for constructing the dependency if you want;

specifying the class upfront means it still knows what class is provided by the block

c.add(:other_special_logger, Logger) do Logger.new(foo, bar, baz) end

You don't actually have to specify the class that's provided; it will just

provide_class Object by default. In this case you really need to specify

a feature name for it, or else you'll have no way to refer to it:

c.add(:mystery_meat) do rand(2) == 0 ? Mystery.new : Meat.new end

add_new_factory is the more explicit but verbose way to do this.

note in this case you need to specify a :method_name separately if you want a method defined on

the container for it.

c.add_new_factory(:class => Foo, :features => [:foo, :bar], :method_name => :foo) {Foo.new(...)} c.add_new_factory(:class => Logger, :features => [:logger], :method_name => :logger, :args => ['/arg_for_logger.txt'])

SPECIFYING DEPENDENCIES (which will then automatically get constructed and passed into your constructor)

This will be constructed via LogSpewer.new(:logger => logger)

c.add LogSpewer, :logger => Logger

however since two Loggers are available, we might want to specify

a particular one, by making it depend on the feature :special_logger

provided by only one of them.

c.add :special_log_spewer, LogSpewer, :logger => :special_logger

You can specify a combination of class/module and feature name requirements for a

dependency:

c.add :fussy_log_spewer, LogSpewer, :logger => [SpecialLogger, :providing, :these, :features]

USING DEFAULTS and PREFERRED FEATURES TO CHOOSE FROM MANY AVAILABLE DEPENDENCIES

If there are many Loggers available, and you have a dependency on a Logger, how does it

decide which one to give you?

Answer: It will never make an arbitrary choice for you. If there are multiple matching

factories and it has no way to differentiate between them, it will raise an error complaining

about it and let you fix the issue.

You can either refine the dependency to a particular logger, as in the example above

where we asked for a :special_logger.

But it would also be nice if you could nominate one logger as the default to use in the

case of multiple loggers, without having to specifically request it in each case:

c.add Logger, :default => true

which is shorthand for:

c.add Logger, :features => [:default]

this will then be chosen over any other options when resolving a dependency.

(if more than one 'default' is available, it will still complain).

Defaults are actually implemented under the hood using 'preferred features'.

These are extra feature names in addition to the required features for a dependency,

which you'd kinda like if possible but if not then no worries.

eg:

c.add ColourfulLogSpewer, :logger => {:class => Logger, :prefer => :colour_capable_logger}

If there are multiple matches for the logger dependency here, it will prefer one which

provides the :colour_capable_logger feature.

(if there are multiple :colour_capable_loggers, it will still complain).

By default, dependencies come with a preferred feature of :default, as though they

were constructed via:

c.add LogSpewer, :logger => {:class => Logger, :prefer => :default}

You can even have multiple preferred_features, in which case it'll try to pick the

dependency providing the greatest number of them. However if you need more advanced

logic to choose the particular dependency you want

MULTIPLE AND OPTIONAL DEPENDENCIES

intended to be useful for extension points in plugin systems - you can have for example a

multiple dependency on 'anything interested in listening to me' or 'anything interested

in plugging in to this extension point'.

You can specify cardinality options on dependencies via a longer argument form:

by default one and only one dependency is required, but you can make it

:multiple to get an array of all matching dependencies.

This will be constructed as NoisyLogSpewer.new(:loggers => [logger1, logger2, ...])

c.add :i_spew_to_all_logs, NoisyLogSpewer, :loggers => {:class => Logger, :multiple => true}

if you don't mind getting a nil if there dependency isn't available, you can make it :optional

c.add :i_spew_to_a_log_if_present, HesitantLogSpewer, :logger => {:class => Logger, :optional => true}

or maybe you want as many are as available but don't mind if that number is zero:

if you don't mind getting a nil if there dependency isn't available, you can make it :optional

c.add :i_spew_to_what_i_can_get, HesitantLogSpewer, :loggers => {:class => Logger, :multiple => true, :optional => true}

a particularly complicated dependency example:

c.add :complicated, LogSpewer, :loggers => {:class => Logger, :features => [:foo, :bar], :multiple => true, :optional => true}

CUSTOM ARGS OR CONSTRUCTOR BLOCK

By default, dependencies are passed to the class's new method as a hash argument.

you can customize this with a block though:

c.add(:foo, Foo, :logger => Logger) do |dependencies| Foo.new(dependencies[:logger]) end

And you can specify initial :args which will be passed before the dependencies hash.

in this case it'll be constructed as

Foo.new('initial arg', :logger => logger)

c.add(:foo, Foo, 'initial arg', :logger => Logger)

If you need to specify any other keyword arguments for the factory, :dependencies need to be supplied separately, eg:

c.add(:foo, Foo, :dependencies => {:logger => Logger}, :args => ['initial arg'], :features => [:extra, :feature, :names])

SETTER DEPENDENCIES AND TWO-PHASE INITIALIZATION

Sometimes you need depdendencies to be supplied after the object has been constructed.

Eg if you need to break a cyclic dependency.

These kinds of dependencies can be specified as :setter_dependencies.

An example:

c.add(Foo, :setter_dependencies => {:bar => Bar}) c.add(Bar, :setter_dependencies => {:bar => Foo})

this situation will be wired up like so:

foo = Foo.new

bar = Bar.new

foo.send(:bar=, bar)

bar.send(:foo=, foo)

foo.send(:post_initialize) if foo.respond_to?(:post_initialize)

bar.send(:post_initialize) if bar.respond_to?(:post_initialize)

Note you can get a post_initialize callback once your entire dependency graph

is wired up and ready for action.

Note that the setters and post_initialize hook used for this purpose

can be private, if you want to limit them only to use by the container

during two-phase initialization.

If you need precise control over two-phase initialization, you can add your own

Factory provided it implements Wirer::Factory::Interface.

The factory implementation can, if it wants, override the default mechanism for

injecting dependencies into instances created from it, and the default mechanism

for post_initializing them.

It can also make the setter_dependencies requested conditional on the particular

instance constructed, which may be useful if they vary depending on arguments to

the constructor.

add_factory_instance(my_custom_factory, :method_name => :my_custom_factory)

ADDING AN EXISTING GLOBAL OBJECT

Useful if you're using some (doubtless third-party ;-) library which has

hardcoded global state or singletons in a global scope, but you want to add them

to your container anyway so they at least appear as modular components for use by

other stuff.

this will work provided the global thing is not itself a class or module:

c.add :naughty_global_state, SomeLibraryWithA::GLOBAL_THINGUMY

or this is more explicit:

c.add_instance SomeLibraryWithA::GLOBAL_THINGUMY, :method_name => :naughty_global_state

the object will be added as providing the class of which it is an instance,

together with any extra feature name or names that you specify.

here multiple feature names are specified

c.add :instance => SomeLibraryWithA::GLOBAL_THINGUMY, :features => [:foo, :bar]

NON-SINGLETON FACTORIES

So far every factory we added to our container has been a singleton in the scope of the container.

This is the default and means that the container will only ever construct one instance of it, and

will cache that instance.

You can turn this off it you want though, via eg:

c.add :foo, Foo, :singleton => false

The container will then construct a new instance whenever a Foo is required.

Factories which are added as singletons can also support arguments, eg:

container.foo(args, for, factory)

These will then be passed on as additional arguments to the constructor

block where you supply one, eg:

c.add(:foo, Foo, :singleton => false, :dependencies => {:logger => Logger}) do |dependencies, *other_args| Foo.new(other_args, dependencies[:logger]) end

Where you only supply a class, by default they'll be passed as additional

arguments to the new method before the dependencies hash.

If the last argument is a hash, dependencies will be merged into it. eg:

c.add(:foo, Foo, :singleton => false, :dependencies => {:logger => Logger})

here,

c.foo(:other => arg)

will lead to

Foo.new(:other => arg, :logger => logger)

and

c.foo(arg1, arg2, :arg3 => 'foo')

to

Foo.new(arg1, arg2, :arg3 => 'foo', :logger => logger)

If you don't like this, just make sure to supply a constructor block.

Note that the singleton-ness or otherwise, is not a property of the factory itself, rather

it's specific to the context of that factory within a particular container.

Note that when using non-singleton factories, all bets are off when it comes to wiring up

object graphs which have cycles in them - since it can't keep constructing new instances

all the way down.

Similarly, if the same dependency occurs twice in your dependency graph,

where a non-singleton factory is used for it, you'll obviously get multiple distinct instances

rather than references to one shared instance.

I considered providing more fine-grained control over this (eg making things a singleton in the

scope of one particular 'construction session', but able to construct new instance for each

such construction session) but this is out of scope for now.

end

GETTING STUFF OUT OF A CONTAINER

Pretty crucial eh!

Things added via 'add' with a symbol method name as the first argument, are made available via

corresponding methods on the container:

container.special_logger container.mystery_meat

You can also specify this via an explicit :method_name parameter (and in fact you need to

specify it this if you use the slightly-lower-level add_factory / add_new_factory / add_instance

calls)

You can also ask the container to find any kind of dependency, via

passing dependency specification arguments to []:

container[Logger] container[:special_logger] container[Logger, :multiple => true] container[SomeClass, :and, :some, :features] container[SomeModule] container[SomeModule, :optional => true]

unless you specify :optional => true, it'll whinge if the dependency can't be fulfilled.

DSL FOR EXPRESSING DEPENDENCIES FOR A PARTICULAR CLASS

This is really handy if you're writing classes which are designed to be

components that are ready to be wired up by a Wirer::Container.

Using the DSL makes your class instance itself expose Wirer::Factory::Interface,

meaning it can be added to a container without having to manually state

its dependencies or provided features. The container will 'just know'.

(although as we shall see, you can refine the dependencies when adding it to a

container, to override the defaults within that particular context if you need to;

you can also specify extra provided features within the container context)

class Foo wireable # extends with the DSL methods (Wirer::Factory::ClassDSL)

Declare some dependencies.

dependency :logger, Logger dependency :arg_name, DesiredClass, :other, :desired, :feature, :names, :optional => true dependency :arg_name, :class => DesiredClass, :features => [:desired, :feature, :names], :optional => true

to avoid cyclic load-order dependencies between classes using this DSL, you can specify a class or module

name as a string to be resolved later. In this case you need to use the explicit :class => foo args style.

dependency :something, :class => "Some::Thing"

you can declare extra features which the class factory provides:

provides_feature :foo, :bar, :baz

and setter dependencies

setter_dependency :foo, Foo

(by default this will also define a private attr_writer :foo for you, which is

what it will use by default to inject the dependency)

you can also override the factory methods which the class has been extended with.

the most common case would be where you want to customize how instances are

constructed from the named dependencies (and any other args), via eg:

def self.new_from_dependencies(dependencies, *other_args) new(dependencies[:foo], dependencies[:bar], *other_args) end

you could also add extra instance-specific setter dependencies, eg via:

def self.setter_dependencies(instance) result = super result[:extra] = Wirer::Dependency.new(...) if instance.something? result end

Or to customize the way that setter dependencies are injected:

def self.inject_dependency(instance, arg_name, dependency) instance.instance_variable_set(:"@#{arg_name}", dependency) end end

class Bar < Foo

when using Wirer::Factory::ClassDSL, subclasses inherit their superclass's dependencies

and features, but you can add new ones:

dependency :another_thing, Wotsit provides_feature :extra

or override existing dependencies

dependency :logger, :special_logger

or if you don't want this inheritance between the class factory instances of subclasses,

you can just extend with Wirer::Factory::ClassMixin instead of using the DSL, or you

can override constructor_dependencies / setter_dependencies / provides_features class

methods, or both.

end

Adding these classes into a container is then quite simple:

Wirer do |c|

It will see that Foo.is_a?(Wirer::Factory::Interface) and add it directly as a factory

taking into account its dependencies etc

c.add Foo

You can refine the dependencies of an existing factory when adding it, eg:

c.add Foo, :logger => :special_logger

its original dependency was just on a Logger, but now it's on a Logger which also

provides_feature :special_logger.

This allows you to customize which particular instance of a given dependency this

class gets constructed with. It will be added using a Wirer::Factory::Wrapped around

the original factory.

You can also specify extra features when adding a factory, which then give you a handle

by which to refer to it when you want it passed to some other thing. Eg to provide the

special logger above:

c.add :special_logger, Logger

or both at once: adding an existing factory with some extra features and some refined

dependencies within this container's context.

c.add Foo, :features => [:special_foo], :dependencies => {:logger => :special_logger}

(if you want to specify other arguments for Factory::Wrapped, the dependency refining

arguments need to go in their own :dependencies arg)

then could then eg

c.add Bar, :foo => :special_foo

If you have an existing factory which takes arguments, you can wrap it with specific

(initial) arguments, allowing it to be added as a singleton, eg:

c.add Foo, 'args', 'for', 'foo', :logger => Logger

or to be more explicit:

c.add Foo, :args => ['args', 'for', 'foo'], :dependencies => {:logger => Logger} end

FAQs

Package last updated on 05 Sep 2014

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc