Code Reuse in Ruby

by Sava Virtosu

Code reuse aims to save time and resources and reduce redundancy by taking advantage of assets that have already been created in some form within the software product development process.

1. Inheritance

  class Car
    def drive
      p 'Drive'
    end

    def color
      p 'Black'
    end

  end

  class RedCar < Car
    def color
      p 'Red'
    end
  end

  class YellowCar < Car
    def color
      p 'Yellow'
    end
  end

  car = Car.new
  car.color

  redCar = RedCar.new
  redCar.color
ApplicationController becomes a dumping ground of random methods that are needed by “a lot” of controllers - it’s effectively a global scope for code that doesn’t have an obvious place to go.

Framework (like rails) love Inheritance

developers don't :)

David Bryant Copeland

require 'rails_helper'

describe SomeModel do
  # code
end
require 'some_module'

class DummyObject  
  include SomeModule  
end

describe "SomeModule" do
  # code
end

~ 7 sec

~ 0.2 sec

Framework overhead

2. Mixins

  module BlackColor
    def color
      p "Black"
    end
  end

  module RedColor
    def color
      p "Red"
    end
  end

  module YellowColor
    def color
      p "Yellow"
    end
  end
  class Car
    include BlackColor
    def drive
      p "Drive"
    end
  end

  class RedCar
    include RedColor
    def drive
      p "Drive"
    end
  end

  simpleCar = Car.new
  simpleCar.color

  simpleRedCar = RedCar.new
  simpleRedCar.color

Simple Mixin

DCI (Data Context Interaction)
  class Car
    attr_reader :color_module

    def initialize(color_module=:black)
      @color_module = color_module
      include_modules
    end

    def drive
      p "Drive"
    end

    def include_modules
      case @color_module
      when :red
        class << self
          include RedColor
        end
      when :yellow
        class << self
          include YellowColor
        end
      when :black
        class << self
          include BlackColor
        end
      else
        raise UnsupportedModule.new
      end
    end
  end

Dynamic Mixin

  blackCar = Car.new
  blackCar.color

  redCar = Car.new :red
  redCar.color

Dynamic Mixin

  class Car
    def drive
      p "Drive"
    end
  end

  class ColorModuleMapping
    class UnsupportedModule < StandardError; end

    def self.include_color_in(color=:black, object)
      case color
      when :red
        class << object
          include RedColor
        end
      when :yellow
        class << object
          include YellowColor
        end
      when :black
        class << object
          include BlackColor
        end
      else
        raise UnsupportedModule.new
      end
    end
  end

Dynamic Module Mapping Mixin

  car = Car.new
  ColorModuleMapping.include_color_in car
  car.color

  car = Car.new
  ColorModuleMapping.include_color_in :red, car
  car.color

Dynamic Module Mapping Mixin


  car = Car.new
  ColorModuleMapping.include_color_in car unless car.respond_to? :color
  car.color

This is cool, because ruby is cool ^_^ 

3. Composition

“composition” == “call methods on a private object”
Code reuse results in dependency on the component being reused.
  class Car
    attr_reader :color_obj

    def initialize(color_obj)
      @color_obj = color_obj
    end

    def color
      color_obj.get_color
    end

    def drive
      p "Drive"
    end
  end

  class CarColor
    attr_reader :color

    def initialize(color=:black)
      @color = color
    end

    def get_color
      case @color
      when :red
        p 'Red'
      when :yellow
        p 'Yellow'
      when :black
        p 'Black'
      end
    end
  end
  carColor = CarColor.new
  car = Car.new carColor
  car.color

  carColor = CarColor.new :red
  car = Car.new carColor
  car.color
Dependency Injection!

(DHH will kill you XD)
  class Car
    attr_reader :color_obj

    def build(color)
     @color_obj = CarColor.new color
    end

    def initialize(color=:black)
      build(color)
    end

    def color
      color_obj.get_color
    end

    def drive
      p "Drive"
    end
  end
  blackCar = Car.new
  blackCar.color

  redCar = Car.new :red
  redCar.color

Conclusion: Hard to test

The Memory! 

require 'memory_profiler'
report = MemoryProfiler.report do
  # run your code here
end

report.pretty_print
  class Car
    def drive
      p 'Drive'
    end

    def color
      p 'Black'
    end

  end

  class RedCar < Car
    def color
      p 'Red'
    end
  end

  class YellowCar < Car
    def color
      p 'Yellow'
    end
  end

  car = Car.new
  car.color

  redCar = RedCar.new
  redCar.color
  class Car
    attr_reader :color_obj

    def initialize(color_obj)
      @color_obj = color_obj
    end

    def color
      color_obj.get_color
    end

    def drive
      p "Drive"
    end
  end

  class CarColor
    attr_reader :color

    def initialize(color=:black)
      @color = color
    end

    def get_color
      case @color
      when :red
        p 'Red'
      when :yellow
        p 'Yellow'
      when :black
        p 'Black'
      end
    end
  end
  class Car
    include BlackColor
    def drive
      p "Drive"
    end
  end

  class RedCar
    include RedColor
    def drive
      p "Drive"
    end
  end

  simpleCar = Car.new
  simpleCar.color

  simpleRedCar = RedCar.new
  simpleRedCar.color
  module BlackColor
    def color
      p "Black"
    end
  end

  module RedColor
    def color
      p "Red"
    end
  end

  module YellowColor
    def color
      p "Yellow"
    end
  end
  class Car
    attr_reader :color_module

    def initialize(color_module=:black)
      @color_module = color_module
      include_modules
    end

    def drive
      p "Drive"
    end

    def include_modules
      case @color_module
      when :red
        class << self
          include RedColor
        end
      when :yellow
        class << self
          include YellowColor
        end
      when :black
        class << self
          include BlackColor
        end
      else
        raise UnsupportedModule.new
      end
    end
  end
  class Car
    def drive
      p "Drive"
    end
  end

  class ColorModuleMapping
    class UnsupportedModule < StandardError; end

    def self.include_color_in(color=:black, object)
      case color
      when :red
        class << object
          include RedColor
        end
      when :yellow
        class << object
          include YellowColor
        end
      when :black
        class << object
          include BlackColor
        end
      else
        raise UnsupportedModule.new
      end
    end
  end

4. Copy & Paste

"A little copying is better than a little dependency"

Rob Pike

"You can actually make your app harder to change by “drying up” code that really isn’t the same by design."

David Bryant Copeland

Conclusion

What to use?

  • Will you use this functionaloty across the system - use Mixins 
  • Is this your first ruby/rails project - DON'T use Mixins, Use Inheritance :)
  • Is your functionality a customization of an existing type - Use Inheritance
  • Can’t figure out what to do? Use composition
  • Don''t use Copy & Paste - it was a joke

Thanks

Code Reuse in Ruby

By Sava Virtosu

Code Reuse in Ruby

  • 417