The injector is the “lookup service” class
Initialization
The injector is initialized with a configured {Puppet::Pops::Binder::Binder Binder}. The Binder instance contains a resolved set of `key => “binding information”` that is used to setup the injector.
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.
@example Lookup with default value
injector.lookup('favourite_food') {|x| x.nil? ? 'bacon' : x }
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.
@see Puppet::Pops::Binder::Binder Binder, for details about how to bind keys to producers @see Puppet::Pops::Binder::BindingsFactory BindingsFactory, for a convenient way to create a Binder and bindings
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.
@example 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 Puppet::Pops::Binder::Producers::AssistedInjectProducer AssistedInjectProducer, for more details on assisted injection
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.
@api public
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.
@param scope [Puppet::Parser::Scope] the scope to use @param contributions_key [Object] Opaque key as produced by KeyFactory as the contributions key for a multibinding @return [Array<Puppet::Pops::Binder::InjectorEntry>] the contributions sorted in deecending order of precedence
@api public
# File lib/puppet/pops/binder/injector.rb, line 330 def get_contributions(scope, contributions_key) @impl.get_contributions(scope, contributions_key) end
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. @return [Puppet::Pops::Binder::KeyFactory] the key factory in use
@api public
# File lib/puppet/pops/binder/injector.rb, line 179 def key_factory() @impl.key_factory end
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.
@overload lookup(scope, key)
(see #lookup_key) @param scope [Puppet::Parser::Scope] the scope to use for evaluation @param key [Object] an opaque object being the full key
@overload lookup(scope, type, name = ”)
(see #lookup_type) @param scope [Puppet::Parser::Scope] the scope to use for evaluation @param type [Puppet::Pops::Types::PAnyType] the type of what to lookup @param name [String] the name to use, defaults to empty string (for unnamed)
@overload lookup(scope, name)
Lookup of Data type with given name. @see #lookup_type @param scope [Puppet::Parser::Scope] the scope to use for evaluation @param name [String] the Data/name to lookup
@yield [value] passes the looked up value to an optional block and returns what this block returns @yield [scope, value] passes scope and value to the block and returns what this block returns @yieldparam scope [Puppet::Parser::Scope] the scope given to lookup @yieldparam value [Object, nil] the looked up value or nil if nothing was found
@raise [ArgumentError] if the block has an arity that is not 1 or 2
@api public
# File lib/puppet/pops/binder/injector.rb, line 226 def lookup(scope, *args, &block) @impl.lookup(scope, *args, &block) end
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.
@param key [Object] lookup of key as produced by the key factory @return [Object, nil] produced value of type that conforms with bound type (type conformance with key not guaranteed). @raise [ArgumentError] if the produced value does not conform with the bound type
@api public
# File lib/puppet/pops/binder/injector.rb, line 259 def lookup_key(scope, key) @impl.lookup_key(scope, key) end
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.
@overload #lookup_producer(scope, key)
(see #lookup_proudcer_key) @param scope [Puppet::Parser::Scope] the scope to use for evaluation @param key [Object] an opaque object being the full key
@overload #lookup_producer(scope, type, name = ”)
(see #lookup_type) @param scope [Puppet::Parser::Scope] the scope to use for evaluation @param type [Puppet::Pops::Types::PAnyType], the type of what to lookup @param name [String], the name to use, defaults to empty string (for unnamed)
@overload #lookup_producer(scope, name)
Lookup of Data type with given name. @see #lookup_type @param scope [Puppet::Parser::Scope] the scope to use for evaluation @param name [String], the Data/name to lookup
@return [Puppet::Pops::Binder::Producers::Producer, Object, nil] a producer, or what the optional block returns
@yield [producer] passes the looked up producer to an optional block and returns what this block returns @yield [scope, producer] passes scope and producer to the block and returns what this block returns @yieldparam producer [Puppet::Pops::Binder::Producers::Producer, nil] the looked up producer or nil if nothing was bound @yieldparam scope [Puppet::Parser::Scope] the scope given to lookup
@raise [ArgumentError] if the block has an arity that is not 1 or 2
@api public
# File lib/puppet/pops/binder/injector.rb, line 298 def lookup_producer(scope, *args, &block) @impl.lookup_producer(scope, *args, &block) end
Looks up a Producer given an opaque binder key. @return [Puppet::Pops::Binder::Producers::Producer, nil] the bound producer, or nil if no such producer was found.
@api public
# File lib/puppet/pops/binder/injector.rb, line 307 def lookup_producer_key(scope, key) @impl.lookup_producer_key(scope, key) end
Looks up a Producer given a type/name key. @note The result is not type checked (it cannot be until the producer has produced an instance). @return [Puppet::Pops::Binder::Producers::Producer, nil] the bound producer, or nil if no such producer was found
@api public
# File lib/puppet/pops/binder/injector.rb, line 317 def lookup_producer_type(scope, type, name='') @impl.lookup_producer_type(scope, type, name) end
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.
@param type [Puppet::Pops::Types::PAnyType] the type to lookup as defined by Puppet::Pops::Types::TypeFactory @param name [String] 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`.
@return [Object, nil] the looked up bound object, or nil if not found (type conformance with given type is guaranteed) @raise [ArgumentError] if the produced value does not conform with the given type
@api public
# File lib/puppet/pops/binder/injector.rb, line 243 def lookup_type(scope, type, name='') @impl.lookup_type(scope, type, name) end
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.
@example
an_injector.override('myoverrides') do
bind('name').to(43)
end
@api public
# 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
Creates an overriding injector with a single bindings layer created with the given name, and the bindings given in the key_value_hash @api public
# 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
Creates an overriding injector with bindings from a bindings model (a LayeredBindings) which may consists of multiple layers of bindings.
@api public
# 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
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.
@return [Puppet::Pops::Types::TypeCalculator] the type calculator that is in use for keys @api public
# File lib/puppet/pops/binder/injector.rb, line 189 def type_calculator() @impl.type_calculator() end
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.
@example
Injector.create('mysettings') do
bind('name').to(42)
end
@api public
# 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
# 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
# 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
An Injector is initialized with a configured {Puppet::Pops::Binder::Binder Binder}.
@param 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.
@raise ArgumentError if the given binder is not fully configured
@api public
# 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
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.
# File lib/puppet/pops/binder/injector.rb, line 337 def self.null_injector self.new(nil) end