Conject, my dependency injection tool for Ruby, has seen a handful of small revisions since I last blogged about it in May 2012. The current release (0.1.0) has a few new features that you might find useful:
- Lazy, on-demand dependency resolution as an optional alternative to constructor injection.
- All objects in the Ruby runtime may now access their context via the
- You can now omit
module-nameprefixes when listing dependencies in
provide_with_objectsfor objects in the same module.
Lazy Dependency Resolution: Alternative to construct_with
construct_with method is specifically designed to imply that your object must be constructed with a specific set of objects from the context within which it is being constructed. This is sometimes called “constructor injection”; an object’s dependencies are resolved before the object is constructed, and it may not be constructed without them. If your design says, “a car is built with an engine and a chassis,” first an Engine is constructed, then a Chassis, and those instances are passed as arguments to the constructor of a new Car instance.
This approach to object composition gives you a lot of expedience, clarity, and convenience… if you’re in charge of the design of your objects and you’re able to hold to a consistent class/object naming convention. Conject can do most of the heavy lifting by recursively building up your object graph without configuration, and all you need to do is ask for your objects by name.
But sometimes you’re not in a good position to support constructor-time injection, because you’re not in charge of how or when some of your objects are constructed. Some frameworks (such as Rails) use inheritance and automated factories to build objects for you. (You may write the methods for your Rails Controllers, but you don’t usually write the code that instantiates them… so, neither will Conject).
construct_with won’t do you much good if you can’t get Conject to build your object for you.
provide_with_objects, which enables your object to refer to its named dependencies in the same way you would if you’d used
construct_with, but doesn’t require that you let Conject build the object for you. Dependencies are resolved when your code actually attempts to use them, pulled from your object’s context when you invoke one of your internal accessors.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# Constructor-time injection: class MessageHandler construct_with :parser, :broker def handle(data) broker.dispatch parser.parse(data) end end # Lazy dependency provision: class MessageHandlerActor include Celluloid provide_with_objects :parser, :broker def handle(data) broker.dispatch parser.parse(data) end end
In both examples, a message handler uses a parser and broker to handle incoming messages. However, the second example is using Celluloid (a cool actor library for Ruby) which works some of its own subtle magic on your object’s constructor, in such a way that
construct_with won’t function properly. However,
provide_with_objects generates a
parser accessor, which will pull the necessary object from the message handler’s context on first use.
Furthermore, you can use
provide_with_objects within a Class:
1 2 3 4 5 6 7 8 9
class MessageHandler class << self provide_with_objects :parser, :broker end def self.handle(data) broker.dispatch parser.parse(data) end end
To summarize how
provide_with_objects differs from
- It does not interfere with a class’s #new or #initialize methods.
- Dependencies are not resolved at object creation time.
- Dependencies that are never referenced are never resolved.
All Objects now Have #object_context
An object will now retain knowledge of the ObjectContext within which it was built. If the object wasn’t built by Conject, but manually put into an ObjectContext,
object_context will refer to that context. If the object hasn’t been anywhere near Conject,
object_context will reference
Conject.default_object_context. All Class assume the context of
Module-Relative Object Names
When listing dependencies in “construct_with” and “provide_with_objects,” you can now (optionally) omit the module prefix portion of object names if they exist within the same Module. This is convenient when you’re building up namespaces for your code, but find yourself constantly, needlessly repeating the module name when referring to other objects. In cases where you don’t really need or want to see the fully qualified object name, this can be really nice.
1 2 3 4 5 6 7 8 9 10 11 12 13
class Sports::Baseball::Players; end cjlass Sports::Baseball::Field; end # Using canonically named objects: class Sports::Baseball::Game construct_with "sports/baseball/players", "sports/baseball/field" end #...or using module-relative object names: class Sports::Baseball::Game construct_with :players, :game end
The mechanism that translates your object names into class names will only consider the module of the declaring object (in this case, Baseball). There’s no magic (yet?) for walking back up the tree, so if you try
construct_with :announcer within the Sports::Game::Baseball class, Conject won’t find Sports::Announcer.
Conject will continue to improve, change, and grow as we find more patterns of use for it. If you’ve got any suggestions of your own, file a feature request on the Conject Github page!