In my current spare time application for example I needed to have a Recommender in my controller for recommending movies for the current user.
It works simply like this recommender.recommendations_for(user). I know that I could probably make a module and enrich my domain object with it, so I would say something like user.recommendations, and that would give me the recommendations for that user.
I will probably do that later on, but for now, I want to keep the concept of a separate recommendations engine or service.
The Recommender constructor looked something like this:
def initialize(extractor, similarity_data)
Where extractor and similarity_data are involved objects to build themselves.I obviously didn't want to build it in the controller itself. Also I wanted a unique instance to be shared (singleton) everywhere I used the recommender, but I didn't want to use the class itself as the singleton. I wanted an actual instance of the class.
I could then create a factory method on the class to have the singleton like Recommender.getInstance, which is ok, or an independent factory, but I didn't want to do this for all the dependencies and control them individually. And it still doesn't feel as nice as the @Autowired feature in Spring.
So I decided to make a very simple way of injecting new properties in my objects. I would be depending on a central object container (Which I called ObjectContainer) that will have all the objects I intend to use for injection.
I created My ObjectContainer:
class ObjectContainer
@objects = {}
def self.init
recommender = Recommender.new(FileRatingsExtractor.new, MongoBackedSimilarityData.new)
@objects[:recommender]=recommender
#..... more objects and stuff .......
end
def self.get(name)
@objects[name]
end
end
ObjectContainer.init
@objects = {}
def self.init
recommender = Recommender.new(FileRatingsExtractor.new, MongoBackedSimilarityData.new)
@objects[:recommender]=recommender
#..... more objects and stuff .......
end
def self.get(name)
@objects[name]
end
end
ObjectContainer.init
And then I created my inject support:
require_relative "object_container"
module Inject
def method_missing(meth, *args)
return super unless meth == :inject
args.each do |to_inject|
self.class_eval do
define_method(to_inject) do
ObjectContainer.get(to_inject.to_sym)
end
end
end
end
end
class Object
extend Inject
end
module Inject
def method_missing(meth, *args)
return super unless meth == :inject
args.each do |to_inject|
self.class_eval do
define_method(to_inject) do
ObjectContainer.get(to_inject.to_sym)
end
end
end
end
end
class Object
extend Inject
end
I created a Spec like this:
require 'spec_helper'
describe "Inject Support" do
it "should inject properties in my objects" do
class AnyClass
inject :recommender
end
a = AnyClass.new
a.recommender.should_not be_nil
end
end
describe "Inject Support" do
it "should inject properties in my objects" do
class AnyClass
inject :recommender
end
a = AnyClass.new
a.recommender.should_not be_nil
end
end
This is in a Rails application I'm working on. The files init_object_container.rb and init_inject_support.rb are in the initializers folder. When I ran my test it was passing, showing that I have a value on the recommender property of an instane of my class AnyClass.
Now in my controller (haven't tested it yet :)) I should be able to do something like this:
class HomeController < ApplicationController
inject :recommender
.....
end
inject :recommender
.....
end
And doing that I would have the recommender accesible as a property in my controller.
I don't know if this is completely anti-Ruby, or anti-Rails. But it just feels very nice and unubtrusive to work like this. Not to say familiar to someone very used to DI and Spring.