diff --git a/.gitignore b/.gitignore index 2d74f5a..c5b194e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ pkg/* *.gem .bundle .rvmrc +tags diff --git a/Rakefile b/Rakefile index b55d873..0ec8fbd 100644 --- a/Rakefile +++ b/Rakefile @@ -9,3 +9,5 @@ RSpec::Core::RakeTask.new(:spec) do |t| t.pattern = "./spec/**/*_spec.rb" t.rspec_opts = ["--profile --color --format=documentation"] end + +task :default => :spec diff --git a/lib/dependor.rb b/lib/dependor.rb index 2d7cba0..872d07f 100644 --- a/lib/dependor.rb +++ b/lib/dependor.rb @@ -1,8 +1,17 @@ require 'dependor/injectable.rb' require 'dependor/meta_data.rb' +require 'dependor/injector.rb' +require 'dependor/dependency_to_class_name_converter.rb' -module Fake +module Dependor + def self.dependency_to_class_name_converter + @dependency_to_class_name_converter ||= DependencyToClassNameConverter.new + end + + def self.injector + @injector ||= Injector.new(dependency_to_class_name_converter) + end end -module Dependor +module Fake end diff --git a/lib/dependor/dependency_to_class_name_converter.rb b/lib/dependor/dependency_to_class_name_converter.rb new file mode 100644 index 0000000..16b63b5 --- /dev/null +++ b/lib/dependor/dependency_to_class_name_converter.rb @@ -0,0 +1,20 @@ +module Dependor + class DependencyToClassNameConverter + + def convert(dependency_name) + class_name = dashes_to_colons(dependency_name.to_s) + return camelize(class_name) + end + + private + + def camelize(string) + string.gsub(/^[a-z]/){|s| s.upcase}.gsub(/_+/, '_').gsub(/_[a-z]/){|s| s[1].upcase} + end + + def dashes_to_colons(string) + string.gsub('-', '::_') + end + + end +end diff --git a/lib/dependor/injectable.rb b/lib/dependor/injectable.rb index 2d98435..09bcc17 100644 --- a/lib/dependor/injectable.rb +++ b/lib/dependor/injectable.rb @@ -8,7 +8,7 @@ def self.included(klass) module InstanceMethods def inject! - self + Dependor.injector.inject(self) end end diff --git a/lib/dependor/injector.rb b/lib/dependor/injector.rb new file mode 100644 index 0000000..a75ddd4 --- /dev/null +++ b/lib/dependor/injector.rb @@ -0,0 +1,35 @@ +module Dependor + class Injector + + attr_reader :dependency_to_class_name_converter + + def initialize(dependency_to_class_name_converter) + @dependency_to_class_name_converter = dependency_to_class_name_converter + end + + def inject(instance) + meta_data = Dependor::MetaData.for(instance) + + meta_data.dependencies.each do |dependency_name| + klass = class_for_name(dependency_name) + dependency = inject(klass.new) + instance.send("#{dependency_name}=", dependency) + end + + return instance + end + + private + + def class_for_name(dependency_name) + class_name = dependency_to_class_name_converter.convert(dependency_name) + parts = class_name.split('::') + context = Object + parts.each do |part| + context = context.const_get(part) + end + return context + end + + end +end diff --git a/lib/dependor/meta_data.rb b/lib/dependor/meta_data.rb index 4dcf9f6..7d0e617 100644 --- a/lib/dependor/meta_data.rb +++ b/lib/dependor/meta_data.rb @@ -17,6 +17,8 @@ def dependencies def self.for(klass) if klass.respond_to?(:dependor_meta_data) return klass.dependor_meta_data + elsif !klass.is_a?(Class) + return self.for(klass.class) else return EmptyMetaData.new end diff --git a/spec/dependor/dependency_to_class_name_converter_spec.rb b/spec/dependor/dependency_to_class_name_converter_spec.rb new file mode 100644 index 0000000..77b167d --- /dev/null +++ b/spec/dependor/dependency_to_class_name_converter_spec.rb @@ -0,0 +1,29 @@ +require File.expand_path('../../spec_helper.rb', __FILE__) + +describe Dependor::DependencyToClassNameConverter do + + before(:each) do + @converter = Dependor::DependencyToClassNameConverter.new + end + + it "should convert simple names to camel case" do + @converter.convert(:foo).should == "Foo" + end + + it "should convert multi part underscore_names to CamelCase" do + @converter.convert(:some_component).should == "SomeComponent" + end + + it "should convert - to :: in names" do + @converter.convert("some_module-some_class").should == "SomeModule::SomeClass" + end + + it "should leave camelcase names unchanged" do + @converter.convert("SomeClass").should == "SomeClass" + end + + it "should leave camelcase names with :: in them unchanged" do + @converter.convert("SomeModule::SomeClass").should == "SomeModule::SomeClass" + end + +end diff --git a/spec/dependor/injectable_spec.rb b/spec/dependor/injectable_spec.rb index 5dd806f..15c2808 100644 --- a/spec/dependor/injectable_spec.rb +++ b/spec/dependor/injectable_spec.rb @@ -2,40 +2,6 @@ describe Dependor::Injectable do - class Foo - def name - "world" - end - end - - class Fake::Foo - def name - "fake" - end - end - - class Bar - include Dependor::Injectable - - depends_on :foo - - def hello - return "Hello #{foo.name}!" - end - end - - class Fake::Bar - def hello - "Hello?" - end - end - - class Baz - include Dependor::Injectable - - depends_on :bar - end - describe ".make" do let(:baz) { Baz.make } @@ -77,7 +43,7 @@ class DependencyInheritanceChild < DependencyInheritanceParent end it "should inject both parent and child dependencies" do - sample = DependencyInheritanceChild.new + sample = DependencyInheritanceChild.make sample.foo.should be_an_instance_of(Foo) sample.bar.should be_an_instance_of(Bar) diff --git a/spec/dependor/injector_spec.rb b/spec/dependor/injector_spec.rb new file mode 100644 index 0000000..f8d727b --- /dev/null +++ b/spec/dependor/injector_spec.rb @@ -0,0 +1,43 @@ +require File.expand_path('../../spec_helper.rb', __FILE__) + +describe Dependor::Injector do + + before(:each) do + @injector = Dependor::Injector.new(Dependor::DependencyToClassNameConverter.new) + end + + describe "for an instance of a class with no dependencies" do + class NoDependenciesSample + attr_accessor :foo + end + + it "should do nothing" do + instance = NoDependenciesSample.new + + @injector.inject(instance) + + instance.foo.should be_nil + end + end + + describe "for an instance of a class with dependencies" do + it "should inject those dependencies" do + bar = Bar.new + + @injector.inject(bar) + + bar.foo.should be_an_instance_of(Foo) + end + end + + describe "for a multi-level dependency hierarchy" do + it "should inject dependencies of dependencies too" do + baz = Baz.new + + @injector.inject(baz) + + baz.bar.foo.should be_an_instance_of(Foo) + end + end + +end diff --git a/spec/dependor/meta_data_spec.rb b/spec/dependor/meta_data_spec.rb index fcc1eb2..3b77cef 100644 --- a/spec/dependor/meta_data_spec.rb +++ b/spec/dependor/meta_data_spec.rb @@ -53,4 +53,20 @@ class InheritingFromClassWithNoDependenciesSample < NoMetaDataSample end end + describe "for an instance of a class with no dependencies declared" do + it "should return empty metadata" do + metadata = Dependor::MetaData.for(NoMetaDataSample.new) + + metadata.dependencies.should == Set.new + end + end + + describe "for an instance of a class with some dependencies declared" do + it "should return the same metadata as for the class" do + metadata = Dependor::MetaData.for(SomeDependenciesSample.new) + + metadata.dependencies.should == Set.new([:foo, :bar]) + end + end + end diff --git a/spec/sample_classes.rb b/spec/sample_classes.rb new file mode 100644 index 0000000..b27df39 --- /dev/null +++ b/spec/sample_classes.rb @@ -0,0 +1,33 @@ +class Foo + def name + "world" + end +end + +class Fake::Foo + def name + "fake" + end +end + +class Bar + include Dependor::Injectable + + depends_on :foo + + def hello + return "Hello #{foo.name}!" + end +end + +class Fake::Bar + def hello + "Hello?" + end +end + +class Baz + include Dependor::Injectable + + depends_on :bar +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a4ef8e9..97b7739 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,6 +5,8 @@ require 'dependor' +require File.expand_path('../sample_classes.rb', __FILE__) + RSpec.configure do |c| c.color_enabled = true end