Refactor Like A Boss
a few techniques for everyday Ruby hacking
Refactoring
The process of changing code
 without modifying behavior
Ungoals of refactoring



• Brevity   for the sake of brevity

• To   demonstrate mastery of Ruby or design patterns
Goals of refactoring

• Improve   readability

• Improve   maintainability

• Improve   extensibility

• Promote   an expressive API

• Reduce   complexity
Ruby freebies
DRY Assignment


if 1 > 0
  @foo = 'bar'
else
  @foo = 'baz'
end

@foo # => 'bar'
DRY Assignment


@foo = if 1 > 0
         'bar'
       else
         'baz'
       end

@foo # => 'bar'
DRY Assignment

@foo = case 1
       when 0..1
         'bar'
       else
         'baz'
       end

@foo # => 'bar'
Ternary operator



@foo = 1 > 0 ? 'bar' : 'qux'

@foo # => 'bar'
Ternary operator


def postive?(number)
  number > 0 ? 'yes' : 'no'
end

positive?(100) # => 'yes'
Bang bang


def foo?
  if @foo
    true
  else
    false
  end
end
Bang bang



def foo?
  @foo ? true : false
end
Bang bang



def foo?
  !!@foo
end
Conditional assignment



if not @foo
  @foo = 'bar'
end
Conditional assignment



unless @foo
  @foo = 'bar'
end
Conditional assignment



@foo = 'bar' unless @foo
Conditional assignment



@foo ||= 'bar'
Parallel assignment



@foo   = 'baz'
@bar   = 'qux'
# =>   "baz"
# =>   "qux"
Parallel assignment



@foo, @bar = 'baz', 'qux'
# => ["baz", "qux"]

@foo # => "baz"
Multiple return

def get_with_benchmark(uri)
  res = nil
  bench = Benchmark.measure do
    res = Net::HTTP.get_response(uri)
  end
  return res, bench.real
end

@response, @benchmark = get_with_benchmark(@uri)
# => [#<Net::HTTPOK 200 OK>, 0.123]
Implied begin

def my_safe_method
  begin
    do_something_dangerous()
    true
  rescue
    false
  end
end
Implied begin


def my_safe_method
  do_something_dangerous()
  true
rescue
  false
end
Exception lists
def self.likelihood_of_rain
  Hpricot::XML(weather_xml)/'probability-of-rain'
rescue Net::HTTPBadResponse,
       Net::HTTPHeaderSyntaxError,
       Net::ProtocolError
  return false
end

def self.likelihood_of_snow
  Hpricot::XML(weather_xml)/'probability-of-snow'
rescue Net::HTTPBadResponse,
       Net::HTTPHeaderSyntaxError,
       Net::ProtocolError
  return false
end
Exception lists
NET_EXCEPTIONS = [ Net::HTTPBadResponse,
                   Net::HTTPHeaderSyntaxError,
                   Net::ProtocolError ]

def self.likelihood_of_rain
  Hpricot::XML(weather_xml)/'probability-of-rain'
rescue NET_EXCEPTIONS
  return false
end

def self.likelihood_of_snow
  Hpricot::XML(weather_xml)/'probability-of-snow'
rescue NET_EXCEPTIONS
  return false
end
Symbol to Proc



(1..5).map{|number| number.to_s }
# => ["1", "2", "3", "4", "5"]
Symbol to Proc



(1..5).map(&:to_s)
# => ["1", "2", "3", "4", "5"]
MapReduce


def fibonacci_sum
  sum = 0
  [1,1,2,3,5,8,13].each{|int| sum += int }
  sum
end

fibonacci_sum() # => 33
MapReduce


def fibonacci_sum
  [1,1,2,3,5,8,13].reduce(0){|sum, int| sum + int }
end

fibonacci_sum() # => 33
MapReduce


{:foo => 'bar'}.inject({}) do |memo, (key, value)|
  memo[value] = key
  memo
end

# => {"bar" => :foo}
Regex captures


match_data = 'my lil string'.match(/my (w+) (w+)/)
match_data.captures[0] # => "lil"
match_data.captures[1] # => "string"
match_data.captures[2] # => nil
Regex captures


'my lil   string'.match(/my (w+) (w+)/)
$1 # =>   "lil"
$2 # =>   "string"
$3 # =>   nil
Regex captures


'my lil   string' =~ /my (w+) (w+)/
$1 # =>   "lil"
$2 # =>   "string"
$3 # =>   nil
tap


def Resource.create
  resource = Resource.new
  resource.save
  resource
end
# => #<Resource:0xffffff>
tap


def Resource.create
  Resource.new.tap{|resource| resource.save }
end
# => #<Resource:0xffffff>
sprintf

sprintf("%d as hexadecimal: %04x", 123, 123)
# => "123 as hexadecimal: 007b"

"%d as hexadecimal: %04x" % [123, 123]
# => "123 as hexadecimal: 007b"

"%s string" % ['my']
# => "my string"
case equality

def cerealize(val)
  if val.is_a?(Numeric) || val.is_a?(String)
    val
  elsif val.is_a?(Enumerable)
    val.to_json
  else
    val.to_s
  end
end
case equality

def cerealize(val)
  case val
  when Numeric, String
    val
  when Enumerable
    val.to_json
  else
    val.to_s
  end
end
case equality

if command =~ /sudo/
  raise 'Danger!'
elsif command =~ /^rm /
  puts 'Are you sure?'
else
  puts 'Run it.'
end
case equality

case command
when /sudo/
  raise 'Danger!'
when /^rm /
  puts 'Are you sure?'
else
  puts 'Run it.'
end
Splat Array
def shopping_list(ingredients)
  unless ingredients.is_a?(Array)
    ingredients = [ingredients]
  end
  ingredients.join(", ")
end

shopping_list("eggs")
# => "eggs"
shopping_list(["eggs", "bacon"])
# => "eggs, bacon"
Splat Array
def shopping_list(ingredients)
  [ingredients].flatten.join(", ")
end

shopping_list("eggs")
# => "eggs"
shopping_list(["eggs", "bacon"])
# => "eggs, bacon"
Splat Array
def shopping_list(ingredients)
  [*ingredients].join(", ")
end

shopping_list("eggs")
# => "eggs"
shopping_list(["eggs", "bacon"])
# => "eggs, bacon"
Splat args
def shopping_list(*ingredients)
  ingredients.join(", ")
end

shopping_list("eggs")
# => "eggs"
shopping_list(["eggs", "bacon"])
# => "eggs, bacon"

shopping_list("eggs", "bacon")
# => "eggs, bacon"
Rails freebies
blank?


if @user.name and !@user.name.empty?
  puts @user.name
else
  puts "no name"
end
blank?


if @user.name.blank?
  puts "no name"
else
  puts @user.name
end
present?


if @user.name.present?
  puts @user.name
else
  puts "no name"
end
presence



puts @user.name.presence || "no name"
truncate


opening = "A long time ago in a galaxy far, far away"
if opening.size > 20
  opening[0..16] + "..."
end
# => "A long time ago i..."
truncate


opening = "A long time ago in a galaxy far, far away"
opening.truncate(20)
# => "A long time ago i..."
truncate


opening = "A long time ago in a galaxy far, far away"
opening.truncate(20, :separator => ' ')
# => "A long time ago..."
try



@existing = User.find_by_email(@new.email)
@existing.destroy if @existing
try



User.find_by_email(@new.email).try(:destroy)
in?



if admin_roles.include? @user.role
  puts "Hi Admin!"
end
in?



if @user.role.in? admin_roles
  puts "Hi Admin!"
end
Delegation
class User

  has_one :account

  def balance
    self.account.balance
  end

  def balance=(amount)
    self.account.balance=(amount)
  end

end
Delegation

class User

  has_one :account

  delegate :balance, :balance=, :to => :account

end
Memoization
class Avatar

  def file_size
    if @file_size
      return @file_size
    else
      result = some_expensive_calculation
      result += more_expensive_calculation
      @file_size = result
    end
  end

end
Memoization
class Avatar

  extend ActiveSupport::Memoizable

  def file_size
    result = some_expensive_calculation
    result += more_expensive_calculation
  end
  memoize :file_size

end
alias_method_chain

alias_method :translate_without_log, :translate

def translate_with_log(*args)
  result = translate_without_log(*args)
  Rails.logger.info result
  result
end

alias_method :translate, :translate_with_log
alias_method_chain


def translate_with_log(*args)
  result = translate_without_log(*args)
  Rails.logger.info result
  result
end

alias_method_chain :translate, :log
class_attribute
class Resource
  class < self

    def host=(name)
      @host = hame
    end
    def host
      @host
    end

  end
end
class_attribute


class Resource
  class < self

    attr_accessor :host

  end
end
class_attribute


class Resource

  class_attribute :host

end
Hash#symbolize_keys


my_hash = { 'foo' => 123 }.symbolize_keys
my_hash['foo']
# => nil
my_hash[:foo]
# => 123
Hash#stringify_keys


my_hash = { :foo => 123 }.stringify_keys
my_hash['foo']
# => 123
my_hash[:foo]
# => nil
HashWithIndifferentAccess


my_hash = { :foo => 123 }
my_hash['foo']
# => nil
my_hash[:foo]
# => 123
HashWithIndifferentAccess


my_hash = { :foo => 123 }.with_indifferent_access
my_hash['foo']
# => 123
my_hash[:foo]
# => 123
forty_two


my_array = []
my_array[41] = "the answer"

my_array[41]
# => "the answer"
forty_two


my_array = []
my_array[41] = "the answer"

my_array.forty_two
# => "the answer"
slideshare   gsterndale

   github    gsterndale

       irc   sternicus