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

Making Ruby Dance

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Ernie Miller 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/

Avatar for Ernie Miller

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