Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Making Ruby Dance

Ernie Miller
September 22, 2012

Making Ruby Dance

Lightning talk from Ruby Hoedown 2012 discussing dependency injection and assorted Ruby shenanigans.

Code: https://gist.github.com/3772859 https://gist.github.com/3772848
Video: http://www.youtube.com/watch?v=so8-u4F6K5s
Blog post: http://erniemiller.org/2012/09/23/making-ruby-dance/

Ernie Miller

September 22, 2012
Tweet

More Decks by Ernie Miller

Other Decks in Programming

Transcript

  1. Everyone loves dependency injection! • Decoupling your code makes it

    more reusable FACTS: Monday, September 24, 12
  2. Everyone loves dependency injection! • Decoupling your code makes it

    more reusable • Dependency injection is a great way to decouple your code FACTS: Monday, September 24, 12
  3. Everyone loves dependency injection! • Decoupling your code makes it

    more reusable • Dependency injection is a great way to decouple your code • Dependency injection will make you more attractive to the opposite sex FACTS: Monday, September 24, 12
  4. class Dancer attr_reader :name def initialize(name, dance_style) @name = name

    @dance_style = dance_style end def dance! @dance_style.dance!(self.name) end end Monday, September 24, 12
  5. BUT... • We need to... • ... know the dependency

    we’re going to inject at instantiation time, or... Monday, September 24, 12
  6. BUT... • We need to... • ... know the dependency

    we’re going to inject at instantiation time, or... • ... add a mutator to our class. Monday, September 24, 12
  7. BUT... • We need to... • ... know the dependency

    we’re going to inject at instantiation time, or... • ... add a mutator to our class. • OR DO WE?!!?!? Monday, September 24, 12
  8. CODE! class Injection < Module attr_reader :name, :object, :as, :methods,

    :prefix, :suffix alias to_s name def initialize(object, *methods) opts = (methods.pop if Hash === methods.last) || {} @name, @object, @methods = "Injection(#{object})", object, methods @prefix, @suffix, @as = opts.values_at :prefix, :suffix, :as define_reader(as, object) define_delegators(object, methods) define_reflection end def extended(base) @object.injected(base) if @object.instance_eval { defined?(injected) } end def inspect @inspect ||= name.dup.tap do |name| name.insert(-2, " as: ##{as}") if as name.insert(-2, " methods: #{methods}") unless methods.empty? name.insert(-2, " format: #{affix('<method>')}") if prefix || suffix end end Monday, September 24, 12
  9. CODE! @inspect ||= name.dup.tap do |name| name.insert(-2, " as: ##{as}")

    if as name.insert(-2, " methods: #{methods}") unless methods.empty? name.insert(-2, " format: #{affix('<method>')}") if prefix || suffix end end private def affix(name) [prefix, name, suffix].compact.join('_') end def define_delegators(object, methods) methods.each do |name| define_method affix(name) do |*args, &block| object.__send__(name, *args, &block) end end end def define_reader(as, object) define_method(as) { object } if as end def define_reflection define_method :injections do singleton_class.ancestors.grep(Injection) end end end Monday, September 24, 12
  10. #!/usr/bin/env ruby require './injection' require 'pry' class CallTracker < BasicObject

    attr_reader :tracked_calls def initialize @tracked_calls = ::Hash.new { |h, k| h[k] = 0 } @injected = nil end def to_s "CallTracker" end def track(method) @tracked_calls[method] += 1 end def injected(base) [:public, :protected, :private].each do |visibility| methods = base.send "#{visibility}_methods" define_methods(base, visibility, methods) end end Monday, September 24, 12
  11. def injected(base) [:public, :protected, :private].each do |visibility| methods = base.send

    "#{visibility}_methods" define_methods(base, visibility, methods) end end def define_methods(base, visibility, methods) tracker = self base.singleton_class.class_eval do methods.each do |method| define_method method do |*args, &block| tracker.track(method) super(*args, &block) end send(visibility, method) end end end end array = [] tracker = CallTracker.new array.extend Injection.new(tracker, :tracked_calls) binding.pry Monday, September 24, 12