diff --git a/features/class_injections.feature b/features/class_injections.feature new file mode 100644 index 0000000..020be2d --- /dev/null +++ b/features/class_injections.feature @@ -0,0 +1,47 @@ +@todo +Feature: Class Injections + + Dependor allows to inject classes and class properties + + @ignore + Scenario: Injecting class properties + Given dependor is required with core extensions + And a class is defined: + """ruby + class Knight + extend Dependor.takes(:king) + + def embark_on_a_quest + puts "For the King!" + king.greet_knight + end + end + """ + And a class is defined: + """ruby + class King + extend Dependor.class_takes(:monarchy_name) + + def self.greet_knight + puts "Welcome to #{monarchy_name}'s army, fellow Sword Master." + end + end + """ + When I run: + """ruby + registry = Dependor.registry do + autoinject(Object) + king(as: :class) + monarchy_name { "FarFarAway" } + end + + knight = registry[:knight] + + knight.embark_on_a_quest + """ + Then the output should be: + """ + For the King! + Welcome to FarFarAway's army, fellow Sword Master. + """ + diff --git a/lib/dependor.rb b/lib/dependor.rb index be4f0d2..945c342 100644 --- a/lib/dependor.rb +++ b/lib/dependor.rb @@ -14,6 +14,10 @@ require "dependor/registry" require "dependor/lookup_chain" +require "dependor/injectable_class" +require "dependor/class_takes_ext" +require "dependor/subclass_builder" + module Dependor def self.registry(&block) Registry.build(&block) @@ -22,4 +26,8 @@ def self.registry(&block) def self.takes(*dependency_names) TakesExt.Takes(*dependency_names) end + + def self.class_takes(*dependency_names) + ClassTakesExt.ClassTakes(*dependency_names) + end end diff --git a/lib/dependor/class_takes_ext.rb b/lib/dependor/class_takes_ext.rb new file mode 100644 index 0000000..bc4ca51 --- /dev/null +++ b/lib/dependor/class_takes_ext.rb @@ -0,0 +1,13 @@ +module Dependor + module ClassTakesExt + module_function + def ClassTakes(*names) + Module.new do + define_singleton_method :extended do |klass| + klass.send(:extend, Dependor::InjectableClass) + klass.add_dependencies(*names) + end + end + end + end +end diff --git a/lib/dependor/core_ext.rb b/lib/dependor/core_ext.rb index 26bfb95..cd4f397 100644 --- a/lib/dependor/core_ext.rb +++ b/lib/dependor/core_ext.rb @@ -1,2 +1,3 @@ Object.extend Dependor::TakesExt +Object.extend Dependor::ClassTakesExt Transient = Dependor::Transient diff --git a/lib/dependor/injectable_class.rb b/lib/dependor/injectable_class.rb new file mode 100644 index 0000000..7c59e7a --- /dev/null +++ b/lib/dependor/injectable_class.rb @@ -0,0 +1,8 @@ +module Dependor + module InjectableClass + attr_accessor :class_takes + def add_dependencies(*names) + @class_takes = names + end + end +end diff --git a/lib/dependor/instantiator.rb b/lib/dependor/instantiator.rb index 1eeb5dc..18d399a 100644 --- a/lib/dependor/instantiator.rb +++ b/lib/dependor/instantiator.rb @@ -20,6 +20,11 @@ def new(klass_name, overwrites = {}) klass.new(args) end + def get_class(klass_name, overwrites = {}) + klass = @class_lookup.lookup(klass_name) + SubclassBuilder.subclass(klass, @injector, overwrites) + end + def method_missing(name, *args, &block) super if args.any? @injector[name] diff --git a/lib/dependor/object_definition.rb b/lib/dependor/object_definition.rb index 3d0f45b..1b26b40 100644 --- a/lib/dependor/object_definition.rb +++ b/lib/dependor/object_definition.rb @@ -5,7 +5,10 @@ def self.default_for(klass) new(klass.name.to_sym, opts, proc{ new(klass) }) end - def self.build(name, transient: false, &block) + def self.build(name, transient: false, as: :instance, &block) + if as == :class + block ||= proc{ get_class(name) } + end block ||= proc{ new(name) } new(name, {transient: transient}, block) end diff --git a/lib/dependor/subclass_builder.rb b/lib/dependor/subclass_builder.rb new file mode 100644 index 0000000..c152180 --- /dev/null +++ b/lib/dependor/subclass_builder.rb @@ -0,0 +1,22 @@ +module Dependor + class SubclassBuilder + def self.get_dependency(name, injector, overwrites) + return overwrites.fetch(name) { @injector[name] } + end + + def self.subclass(klass, injector, overwrites = {}) + return klass unless klass.respond_to?(:class_takes) + + Class.new(klass) do + klass.class_takes.each do |name| + define_singleton_method name do + unless instance_variable_get("@#{name}") + instance_variable_set("@#{name}", overwrites.fetch(name) { injector[name] }) + end + instance_variable_get("@#{name}") + end + end + end + end + end +end diff --git a/spec/features/automagic_injection_spec.rb b/spec/features/automagic_injection_spec.rb index 7ab25bf..d9d0e12 100644 --- a/spec/features/automagic_injection_spec.rb +++ b/spec/features/automagic_injection_spec.rb @@ -33,6 +33,10 @@ class Crown class MagicalSword < Sword extend Takes(:name) end + + class Kingdom + extend ClassTakes(:king, :name) + end end end @@ -61,6 +65,7 @@ class MagicalSword < Sword excalibur { new(:MagicalSword, name: "Excalibur") } king { arthur } knights { [lancelot, galahad] } + kingdom { get_class(:Kingdom, name: "Far Far Away") } end } @@ -76,6 +81,14 @@ class MagicalSword < Sword expect(registry[:king]).to equal(registry[:arthur]) end + it "allows class injections" do + expect(registry[:kingdom]).to be_an_instance_of(Class) + end + + it "allows for overrides when injecting classes" do + expect(registry[:kingdom].name).to eq("Far Far Away") + end + it "makes objects singletons by default" do first_camelot = registry[:camelot] second_camelot = registry[:camelot]