never really liked skinny controller fat model • Started splitting models into modules • Now using service objects, presenters, etc • This led to thinking about IoC as we do in .NET Wednesday, 3 July 13
that’s possible in a dynamic language class Library def send_reminders Borrowing.overdue.each do |borrowing| MemberMailer.overdue_notice(borrowing).deliver borrowing.reminder_sent! end end end Wednesday, 3 July 13
class Library def send_reminders(notifier) Borrowing.overdue.each do |borrowing| notifier.send_overdue_notice(borrowing) borrowing.reminder_sent! end end end Collaborator is passed in Wednesday, 3 July 13
change by passing in different collaborators • Helps with open-closed principle • Better tests (not a reason but positive side effect) Wednesday, 3 July 13
notifier = EmailNotifier.new library = Library.new library.send_reminders(notifier) Nice and simple, collaborators created and injected where needed. Problem with duplication if collaborator used in multiple methods Wednesday, 3 July 13
end end notifier = EmailNotifier.new library = Library.new library.notifier = notifier library.send_reminders Simple, collaborators can be reused across methods. What if the attribute is not set? Wednesday, 3 July 13
def send_reminders # @notifier... end end notifier = EmailNotifier.new library = Library.new(notifier) library.send_reminders As collaborators are a dependency, enforces injection and allows reuse. Big object graphs are a pain to create. Wednesday, 3 July 13
SMS end end class Library include Notifier def send_reminders # send_overdue_notice(borrowing) end end Not so hardcoded. Change module implementation to switch strategy Wednesday, 3 July 13
enhance multiple classes • Don’t use a module when the behaviour should be in its own class • Single Responsibility Principle - does that count after mixin or before? • Don’t use Modules in one class just used to break down file size Wednesday, 3 July 13
"Hello, the time is #{time}" end end describe Foo it "says hello with the current time" do Time.stub(:now){ Time.new(2013, 6, 20, 12, 0, 0) } subject.hello.should == "Hello, the time is 12:00" end end We have powerful tools but is this what we should do? Wednesday, 3 July 13
is #{clock.now}" end end describe Foo it "says hello with the current time" do clock = stub(now: "12:00") subject.hello(clock).should == "Hello, the time is 12:00" end end IoC simplifies testing :) Allow us to just use dumb stubs Wednesday, 3 July 13
Glue def to_s "glue" end end class HandSaw def to_s "hand saw" end end class Electricity def to_s "electrical" end end class Carpenter def initialize(tool, fixings) @tool = tool @fixings = fixings end def build_something puts "Building something in wood with #{@fixings} and #{@tool}" end end class PowerSaw def initialize(power_source) @power_source = power_source end def to_s "#{@power_source} power saw" end end Wednesday, 3 July 13
= PowerSaw.new(power_source) craftsman = Carpenter.new(tool, fixings) craftsman.build_something # => Building something in wood with nails and electrical power saw Separate class for each responsibility and can easily change strategies but creating all these instances is a pain. Wednesday, 3 July 13
key • Allows registering of components in a single place • Instantiates registered implementations • Recursively resolves all dependencies • Injects dependencies • Allows environment specific implementations • Manages component lifecycle Wednesday, 3 July 13
of the container (PORO) • Should be built into the framework • Configure using code not XML • When using, only resolve the root object Wednesday, 3 July 13
} public class Carpenter : ICraftsman { private ITool tool; public Carpenter(ITool tool) { this.tool = tool; } public void BuildSomething() { Console.WriteLine("Building something in wood with a {0}", tool.Name); } } Code to interfaces Wednesday, 3 July 13
container.Resolve<ICraftsman>(); craftsman.BuildSomething(); Register the components against an interface. Resolve the root interface. Wednesday, 3 July 13
to get around the lack of interfaces: container.register(:tool, HandSaw) class Carpenter inject :tool def build_something puts “Building something in wood with a #{@tool.name}“ end end Yuck Wednesday, 3 July 13
:tool, PowerSaw c.register :fixings, Nails c.register :power_source, Electricity end craftsman = Injectr.resolve(:craftsman) craftsman.build_something # => Building something in wood with nails and electrical power saw Register your components with the container then ask for the root and all dependancies will be instantiated automatically Wednesday, 3 July 13
self.new yield @container @container end def initialize @registry = {} end def register(key, klass) @registry[key] = klass end def self.resolve(key) klass = @container.registry[key] create(klass) end def self.create(klass) constructor_params = klass.instance_method(:initialize).parameters dependancies = constructor_params.map{|p| resolve(p.last)} klass.new(*dependancies) end end Recursively resolves objects using the constructor param names as the key Wednesday, 3 July 13
Metal < AbstractController::Base def self.action(name, klass = ActionDispatch::Request) middleware_stack.build(name.to_s) do |env| new.dispatch(name, klass.new(env)) end end end end Instantiates the controller here with no args. Self is your controller class. Wednesday, 3 July 13
self.action(name, klass = ActionDispatch::Request) middleware_stack.build(name.to_s) do |env| Injectr.create(self).dispatch(name, klass.new(env)) end end end end Create an instance of the controller and inject any constructor dependancies Wednesday, 3 July 13
call super(). class HomeController < ApplicationController def initialize(craftsman) super() @craftsman = craftsman end def index render :text => @craftsman.build_something end end $ curl http://localhost:3000 => Building something in wood with nails and electrical power saw Wednesday, 3 July 13