Class: Puppet::Pops::Binder::Injector

Inherits:
Object
  • Object
show all
Defined in:
lib/puppet/pops/binder/injector.rb

Overview

The injector is the “lookup service” class

Initialization


The injector is initialized with a configured Binder. The Binder instance contains a resolved set of ‘key => “binding information”` that is used to setup the injector.

Lookup


It is possible to lookup either the value, or a producer of the value. The #lookup method looks up a value, and the #lookup_producer looks up a producer. Both of these methods can be called with three different signatures; ‘lookup(key)`, `lookup(type, name)`, and `lookup(name)`, with the corresponding calls to obtain a producer; `lookup_producer(key)`, `lookup_producer(type, name)`, and `lookup_producer(name)`.

It is possible to pass a block to #lookup and #lookup_producer, the block is passed the result of the lookup and the result of the block is returned as the value of the lookup. This is useful in order to provide a default value.

Singleton or Not


The lookup of a value is always based on the lookup of a producer. For *singleton producers* this means that the value is determined by the first value lookup. Subsequent lookups via ‘lookup` or `lookup_producer` will produce the same instance.

*Non singleton producers* will produce a new instance on each request for a value. For constant value producers this means that a new deep-clone is produced for mutable objects (but not for immutable objects as this is not needed). Custom producers should have non singleton behavior, or if this is not possible ensure that the produced result is immutable. (The behavior if a custom producer hands out a mutable value and this is mutated is undefined).

Custom bound producers capable of producing a series of objects when bound as a singleton means that the producer is a singleton, not the value it produces. If such a producer is bound as non singleton, each ‘lookup` will get a new producer (hence, typically, restarting the series). However, the producer returned from `lookup_producer` will not recreate the producer on each call to `produce`; i.e. each `lookup_producer` returns a producer capable of returning a series of objects.

Assisted Inject


The injector supports lookup of instances of classes *even if the requested class is not explicitly bound*. This is possible for classes that have a zero argument ‘initialize` method, or that has a class method called `inject` that takes two arguments; `injector`, and `scope`. This is useful in ruby logic as a class can then use the given injector to inject details. An `inject` class method wins over a zero argument `initialize` in all cases.

Access to key factory and type calculator


It is important to use the same key factory, and type calculator as the binder. It is therefor possible to obtain these with the methods #key_factory, and #type_calculator.

Special support for producers


There is one method specially designed for producers. The #get_contributions method returns an array of all contributions to a given *contributions key*. This key is obtained from the #key_factory for a given multibinding. The returned set of contributed bindings is sorted in descending precedence order. Any conflicts, merges, etc. is performed by the multibinding producer configured for a multibinding.

Examples:

Lookup with default value

injector.lookup('favourite_food') {|x| x.nil? ? 'bacon' : x }

Using assisted inject

# Class with assisted inject support
class Duck
  attr_reader :name, :year_of_birth

  def self.inject(injector, scope, binding, *args)
    # lookup default name and year of birth, and use defaults if not present
    name = injector.lookup(scope,'default-duck-name') {|x| x ? x : 'Donald Duck' }
    year_of_birth = injector.lookup(scope,'default-duck-year_of_birth') {|x| x ? x : 1934 }
    self.new(name, year_of_birth)
  end

  def initialize(name, year_of_birth)
    @name = name
    @year_of_birth = year_of_birth
  end
end

injector.lookup(scope, Duck)
# Produces a Duck named 'Donald Duck' or named after the binding 'default-duck-name' (and with similar treatment of
# year_of_birth).

See Also:

Defined Under Namespace

Modules: Private

Constant Summary collapse

Producers =
Puppet::Pops::Binder::Producers

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(configured_binder, parent_injector = nil) ⇒ Injector

An Injector is initialized with a configured Binder.

Parameters:

  • configured_binder (Puppet::Pops::Binder::Binder, nil)

    The configured binder containing effective bindings. A given value of nil creates an injector that returns or yields nil on all lookup requests.

Raises:

  • ArgumentError if the given binder is not fully configured



164
165
166
167
168
169
170
# File 'lib/puppet/pops/binder/injector.rb', line 164

def initialize(configured_binder, parent_injector = nil)
  if configured_binder.nil?
    @impl = Private::NullInjectorImpl.new()
  else
    @impl = Private::InjectorImpl.new(configured_binder, parent_injector)
  end
end

Class Method Details

.create(name, &block) ⇒ Object

Creates an injector with a single bindings layer created with the given name, and the bindings produced by the given block. The block is evaluated with self bound to a BindingsContainerBuilder.

Examples:

Injector.create('mysettings') do
  bind('name').to(42)
end


110
111
112
113
114
# File 'lib/puppet/pops/binder/injector.rb', line 110

def self.create(name, &block)
  factory = Puppet::Pops::Binder::BindingsFactory
  layered_bindings = factory.layered_bindings(factory.named_layer(name+'-layer',factory.named_bindings(name, &block).model))
  self.new(Puppet::Pops::Binder::Binder.new(layered_bindings))
end

.create_from_hash(name, key_value_hash) ⇒ Object



93
94
95
96
97
98
# File 'lib/puppet/pops/binder/injector.rb', line 93

def self.create_from_hash(name, key_value_hash)
  factory = Puppet::Pops::Binder::BindingsFactory
  named_bindings = factory.named_bindings(name) { key_value_hash.each {|k,v| bind.name(k).to(v) }}
  layered_bindings = factory.layered_bindings(factory.named_layer(name+'-layer',named_bindings.model))
  self.new(Puppet::Pops::Binder::Binder.new(layered_bindings))
end

.create_from_model(layered_bindings_model) ⇒ Object



89
90
91
# File 'lib/puppet/pops/binder/injector.rb', line 89

def self.create_from_model(layered_bindings_model)
  self.new(Puppet::Pops::Binder::Binder.new(layered_bindings_model))
end

.null_injectorObject

Returns an Injector that returns (or yields) nil on all lookups, and produces an empty structure for contributions This method is intended for testing purposes.



337
338
339
# File 'lib/puppet/pops/binder/injector.rb', line 337

def self.null_injector
  self.new(nil)
end

Instance Method Details

#get_contributions(scope, contributions_key) ⇒ Array<Puppet::Pops::Binder::InjectorEntry>

Returns the contributions to a multibind given its contribution key (as produced by the KeyFactory). This method is typically used by multibind value producers, but may be used for introspection of the injector’s state.

Parameters:

  • scope (Puppet::Parser::Scope)

    the scope to use

  • contributions_key (Object)

    Opaque key as produced by KeyFactory as the contributions key for a multibinding

Returns:



330
331
332
# File 'lib/puppet/pops/binder/injector.rb', line 330

def get_contributions(scope, contributions_key)
  @impl.get_contributions(scope, contributions_key)
end

#key_factoryPuppet::Pops::Binder::KeyFactory

The KeyFactory used to produce keys in this injector. The factory is shared with the Binder to ensure consistent translation to keys. A compatible type calculator can also be obtained from the key factory.

Returns:



179
180
181
# File 'lib/puppet/pops/binder/injector.rb', line 179

def key_factory()
  @impl.key_factory
end

#lookup(scope, key) ⇒ Object #lookup(scope, type, name = '') ⇒ Object #lookup(scope, name) ⇒ Object

Lookup (a.k.a “inject”) of a value given a key. The lookup may be called with different parameters. This method is a convenience method that dispatches to one of #lookup_key or #lookup_type depending on the arguments. It also provides the ability to use an optional block that is called with the looked up value, or scope and value if the block takes two parameters. This is useful to provide a default value or other transformations, calculations based on the result of the lookup.

Overloads:

  • #lookup(scope, key) ⇒ Object

    Parameters:

  • #lookup(scope, type, name = '') ⇒ Object

    Parameters:

  • #lookup(scope, name) ⇒ Object

    Lookup of Data type with given name.

    Parameters:

    • scope (Puppet::Parser::Scope)

      the scope to use for evaluation

    • name (String)

      the Data/name to lookup

    See Also:

Yields:

  • (value)

    passes the looked up value to an optional block and returns what this block returns

  • (scope, value)

    passes scope and value to the block and returns what this block returns

Yield Parameters:

Raises:

  • (ArgumentError)

    if the block has an arity that is not 1 or 2



226
227
228
# File 'lib/puppet/pops/binder/injector.rb', line 226

def lookup(scope, *args, &block)
  @impl.lookup(scope, *args, &block)
end

#lookup_key(scope, key) ⇒ Object?

Looks up the key and returns the entry, or nil if no entry is found. Produced type is checked for type conformance with its binding, but not with the lookup key. (This since all subtypes of PDataType are looked up using a key based on PDataType). Use the Puppet::Pops::Types::TypeCalculator#instance? method to check for conformance of the result if this is wanted, or use #lookup_type.

Parameters:

  • key (Object)

    lookup of key as produced by the key factory

Returns:

  • (Object, nil)

    produced value of type that conforms with bound type (type conformance with key not guaranteed).

Raises:

  • (ArgumentError)

    if the produced value does not conform with the bound type



259
260
261
# File 'lib/puppet/pops/binder/injector.rb', line 259

def lookup_key(scope, key)
  @impl.lookup_key(scope, key)
end

#lookup_producer(scope, key) ⇒ Puppet::Pops::Binder::Producers::Producer, ... #lookup_producer(scope, type, name = '') ⇒ Puppet::Pops::Binder::Producers::Producer, ... #lookup_producer(scope, name) ⇒ Puppet::Pops::Binder::Producers::Producer, ...

Lookup (a.k.a “inject”) producer of a value given a key. The producer lookup may be called with different parameters. This method is a convenience method that dispatches to one of #lookup_producer_key or #lookup_producer_type depending on the arguments. It also provides the ability to use an optional block that is called with the looked up producer, or scope and producer if the block takes two parameters. This is useful to provide a default value, call a custom producer method, or other transformations, calculations based on the result of the lookup.

Overloads:

Yields:

  • (producer)

    passes the looked up producer to an optional block and returns what this block returns

  • (scope, producer)

    passes scope and producer to the block and returns what this block returns

Yield Parameters:

Returns:

Raises:

  • (ArgumentError)

    if the block has an arity that is not 1 or 2



298
299
300
# File 'lib/puppet/pops/binder/injector.rb', line 298

def lookup_producer(scope, *args, &block)
  @impl.lookup_producer(scope, *args, &block)
end

#lookup_producer_key(scope, key) ⇒ Puppet::Pops::Binder::Producers::Producer?

Looks up a Producer given an opaque binder key.

Returns:



307
308
309
# File 'lib/puppet/pops/binder/injector.rb', line 307

def lookup_producer_key(scope, key)
  @impl.lookup_producer_key(scope, key)
end

#lookup_producer_type(scope, type, name = '') ⇒ Puppet::Pops::Binder::Producers::Producer?

Note:

The result is not type checked (it cannot be until the producer has produced an instance).

Looks up a Producer given a type/name key.

Returns:



317
318
319
# File 'lib/puppet/pops/binder/injector.rb', line 317

def lookup_producer_type(scope, type, name='')
  @impl.lookup_producer_type(scope, type, name)
end

#lookup_type(scope, type, name = '') ⇒ Object?

Looks up a (typesafe) value based on a type/name combination. Creates a key for the type/name combination using a KeyFactory. Specialization of the Data type are transformed to a Data key, and the result is type checked to conform with the given key.

Parameters:

  • type (Puppet::Pops::Types::PAnyType)

    the type to lookup as defined by Puppet::Pops::Types::TypeFactory

  • name (String) (defaults to: '')

    the (optional for non ‘Data` types) name of the entry to lookup. The name may be an empty String (the default), but not nil. The name is required for lookup for subtypes of `Data`.

Returns:

  • (Object, nil)

    the looked up bound object, or nil if not found (type conformance with given type is guaranteed)

Raises:

  • (ArgumentError)

    if the produced value does not conform with the given type



243
244
245
# File 'lib/puppet/pops/binder/injector.rb', line 243

def lookup_type(scope, type, name='')
  @impl.lookup_type(scope, type, name)
end

#override(name, &block) ⇒ Object

Creates an overriding injector with a single bindings layer created with the given name, and the bindings produced by the given block. The block is evaluated with self bound to a BindingsContainerBuilder.

Examples:

an_injector.override('myoverrides') do
  bind('name').to(43)
end


127
128
129
130
131
# File 'lib/puppet/pops/binder/injector.rb', line 127

def override(name, &block)
  factory = Puppet::Pops::Binder::BindingsFactory
  layered_bindings = factory.layered_bindings(factory.named_layer(name+'-layer',factory.named_bindings(name, &block).model))
  self.class.new(Puppet::Pops::Binder::Binder.new(layered_bindings, @impl.binder))
end

#override_with_hash(name, key_value_hash) ⇒ Object

Creates an overriding injector with a single bindings layer created with the given name, and the bindings given in the key_value_hash



149
150
151
152
153
154
# File 'lib/puppet/pops/binder/injector.rb', line 149

def override_with_hash(name, key_value_hash)
  factory = Puppet::Pops::Binder::BindingsFactory
  named_bindings = factory.named_bindings(name) { key_value_hash.each {|k,v| bind.name(k).to(v) }}
  layered_bindings = factory.layered_bindings(factory.named_layer(name+'-layer',named_bindings.model))
  self.class.new(Puppet::Pops::Binder::Binder.new(layered_bindings, @impl.binder))
end

#override_with_model(layered_bindings) ⇒ Object

Creates an overriding injector with bindings from a bindings model (a LayeredBindings) which may consists of multiple layers of bindings.



138
139
140
141
142
143
# File 'lib/puppet/pops/binder/injector.rb', line 138

def override_with_model(layered_bindings)
  unless layered_bindings.is_a?(Puppet::Pops::Binder::Bindings::LayeredBindings)
    raise ArgumentError, "Expected a LayeredBindings model, got '#{bindings_model.class}'"
  end
  self.class.new(Puppet::Pops::Binder::Binder.new(layered_bindings, @impl.binder))
end

#type_calculatorPuppet::Pops::Types::TypeCalculator

Returns the TypeCalculator in use for keys. The same calculator (as used for keys) should be used if there is a need to check type conformance, or infer the type of Ruby objects.

Returns:



189
190
191
# File 'lib/puppet/pops/binder/injector.rb', line 189

def type_calculator()
  @impl.type_calculator()
end