Object Context

Retrieve and relate objects within contexts. Provides dependency injection convenience inspired by the simplicity of Google's Guice.

Basic composition

Declare required component objects for a class using .construct_with

require 'conject'

class Wood
  def to_s; "Wood"; end
end

class Nails
  def to_s; "Nails"; end
end

class Fence
  construct_with :wood, :nails

  def to_s
    "I'm made of #{wood} and #{nails}"
  end
end

fence = Conject.default_object_context.get(:fence)
puts fence
#=> "I'm made of Wood and Nails"

Configuring objects

Call #configure_object or #configure_objects to configure one or more objects wrt their construction or handling.

  • :cache [true|false] - Enable/disable object caching in context. If false, a new instance is created for every request for an object with the given name. Default true.
  • :construct [Proc|Lambda] - Anonymous function to call to provide the named object. Will be cached like any other object, subject to :cache configuration for this context.

Eg. context.configure_objects({ :wood => { :cache => false }, :nails => { :construct => lambda do "The nails" end}, }) # context[:wood] != context[:wood] # context[:nails] == "The nails"

Modules as namespaces

module Chart
  class Presenter
    construct_with 'chart/model', 'chart/view'

    def to_s
      "I'm a Chart::Presenter composed of a #{model} and a #{view}"
    end
  end
end

Subcontexts

first_fence = nil
second_fence = nil

Conject.default_object_context.in_subcontext do |sub|
  first_fence = sub[:fence]
end

Conject.default_object_context.in_subcontext do |sub|
  second_fence = sub[:fence]
end

# second_fence != first_fence

If you'd like to ensure that certain dependencies needed by objects built in subcontexts are actually housed in the parent context, but they have not necessarily been cached or injected in advance, AND you've got an "owner"-like object living in that context, you can declare object peers within the class definition of that owner object. This ensures that collaborators in the subcontext will not cause the peer objects to be instantiated in those subcontexts as well.

class Game
  object_peers: :missile_coordinator, :wind_affector
end

In this example, the instantiation of a Game instance in an object context will cause missile coordinator and wind affector to be "anchored" in the same context as the game instance, meaning they prefer to be instantiated here, if needed by any objects in this same context or any subcontexts thereof. It also means that they will be preferred over any objects of the same name in a super context.

My ObjectContext

All classes built within an ObjectContext are able to reference the context directly via the private accessor method "#object_context", which is available as early as the call to #initialize.

Aliasing

The following will cause one object to be fulfilled by another. In this case, 'album' is not expected to be built as an instance of the Album class, but rather will be set up as another name for 'and_justice_for_all':

context.configure_objects album: { is: 'and_justice_for_all' }

Both 'album' and 'and_justice_for_all' will be built in the context when 'album' is first requested. This is more or less expressive shorthand for:

context['album'] = context['and_justice_for_all'] 

...EXCEPT that it's a lazy approach: using 'is' means nothing actually gets built or set in the context before it is requested or composed into another object.

Specialization via Composition

To declare objects as instances of a specific class, you may specify the :class of the object via #configure_objects. Furthermore, you can specialize the instance by indicating which specific objects you would like to supply as the object's collaborators:

context.configure_objects({
  adventure_time: {
    class: Team, 
    specialize: { 
      hero: "finn",
      sidekick: "jake" }},
  dark_knight: {
    class: Team,
    specialize: {
      hero: "batman",
      sidekick: "robin" }}
})

The keys in :specialize match some or all of the keys defined in the class (in this case, the Team class is defined to be #construct_with :hero, :sidekick, :clock. The values are object names within the ObjectContext; they will be resolved in the usual manner. Components that a Class wants to be built with, but which you omit from the :specialize Hash, will be resolved in the usual way.

Using :class and :specialize is NOT THE SAME as building objects within a subcontext. Objects mentioned here are expected to be found and kept within the defining ObjectContext.