« Back to Index

Refactoring Ruby -> not all conditionals can be removed, and those that can can’t necessarily use the standard refactoring methods such as “Replace Type Code with Module Extension”, “Replace Type Code with Polymorphism” or “Replace Type Code with State/Strategy”. The below examples demonstrate this.

View original Gist on GitHub

Bad Design.rb

class Foo
  def initialize(a=[], b=[])
    @a = a
    @b = b
  end
  
  def add(item)
    if item.is_a? A
      @a.push item
    else
      @b.push item
    end
  end
end

class A
  def initialize(name="Unknown")
    @name = "A: #{name}"
    @time = Time.now
  end
end

class B
  def initialize(name="Unknown")
    @name = "B: #{name}"
    @time = Time.now
  end
end

foo = Foo.new
foo.add(A.new)
foo.add(B.new)

Better Design.rb

# Thanks to @clauswitt for his guidance

class Foo
  def initialize(storage={})
    @storage = storage
  end
  
  def add(item)
    @storage[item.class] = [] unless @storage.has_key?(item.class)
    @storage[item.class] << item
  end
end

class A
  def initialize(name="Unknown")
    @name = "A: #{name}"
    @time = Time.now
  end
end

class B
  def initialize(name="Unknown")
    @name = "B: #{name}"
    @time = Time.now
  end
end

foo = Foo.new
foo.add(A.new)
foo.add(B.new)

Dan Scotton Variation.rb

# - I've assumed you've separated the items into two arrays
#   because you want to select those items at some point later.
#   I've refactored to 'filter' the items on the way out, rather
#   than filtering them on the way in.
# - Also added a :type method rather than class checking.
# - You could even use Set instead of [] here

class Foo
  def initialize(initial_items = [])
    @items = initial_items
  end

  def add(item)
    @items << item
  end

  def a
    select(:a)
  end

  def b
    select(:b)
  end

  private

  def select(type)
    @items.select { |item| item.type == type }
  end
end

# If you wanted to create more types in the future, say :c, you
# could dynamically generate the accessors?

class A
  def initialize(name="Unknown")
    @name = "A: #{name}"
    @time = Time.now
  end
  
  def type
    :a
  end
end

class B
  def initialize(name="Unknown")
    @name = "B: #{name}"
    @time = Time.now
  end
  
  def type
    :b
  end
end

foo = Foo.new
foo.add(A.new)
foo.add(B.new)