Let's write some weird Ruby

David Copeland / @davetron5000

The Billion Dollar Mistake

I was designing the first comprehensive type system for references in an object oriented language…I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement.

This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

No More nil


class Person
  attr_accessor :name, 
                :birthdate, 
                :title
def initialize(name,birthdate,title=nil)
@name = name @birthdate = birthdate @title = title end def greeting if title.nil? "Hello #{@name}" else "Hello #{@title} #{@name}" end end end

class Person
  attr_accessor :name, 
                :birthdate, 
                :title
  def initialize(name,birthdate,title=nil)
    @name      = name
    @birthdate = birthdate
    @title     = title
  end
def greeting if title.nil? "Hello #{@name}" else "Hello #{@title} #{@name}" end end
end
$nil = Object.new do
  def nil?
    true
  end
end
$nil = Object.new do def nil? true end end

nil

class Unassigned; end
class Unknown;    end
class NoValue;    end
class Empty;      end
class Person
  attr_accessor :name, :birthdate, :title
def initialize(name, birthdate, title=NoValue)
@name = name @birthdate = birthdate @title = title end end
class Person
  attr_accessor :name, :birthdate, :title
def initialize(name, birthdate, title=Unknown)
@name = name @birthdate = birthdate @title = title end end
class Person
  def greet
if title == Unknown || title == NoValue || title == Unassigned || title == Empty
"Hello #{name}" else "Hello #{title} #{name}" end end end
class Person
  def greet
    title.when_value {
      "Hello #{title} #{name}"
    }.or_else {
      "Hello #{name}"
    }
  end
end
class BasicObject
  def when_value(&block)
    block.call(self)
  end
  def or_else(*)
    self
  end
end
class NilLikeSentinel
  def self.when_value(*)
    self
  end

  def self.or_else(&block)
    block.call
  end
end

Unassigned = Class.new(NilLikeSentinel)
Unknown    = Class.new(NilLikeSentinel)
NoValue    = Class.new(NilLikeSentinel)
Empty      = Class.new(NilLikeSentinel)
class Person
  def greet
    title.when_value {
      "Hello #{title} #{name}"
    }.or_else {
      "Hello #{name}"
    }
  end
end
class Person
  def greet
    title.when_value {
      "Hello #{title} #{name}"
    }.or_else {
if title == NoValue || title == Empty
"Hello #{name}" else "Not sure how to greet you, #{name}" end } end end
class BasicObject
  def when_known(&block)
    when_value(&block)
  end
end
class NilLikeSentinel
  def self.when_known(*)
    self
  end
end
class KnownNilLikeSentinel < NilLikeSentinel
  def self.when_known(&block)
    block.call(self)
  end
end

NoValue = Class.new(KnownNilLikeSentinel)
Empty   = Class.new(KnownNilLikeSentinel)
class Person
  def greet
    title.when_value {
      "Hello #{title} #{name}"
    }.when_known {
      "Hello #{name}"
    }.or_else {
      "Not sure how to greet you, #{name}"
    }
  end
end

Attributes

class PersonGreeter
  def greet(person)
    if person.first_name.present?
      "Hi, #{person.first_name}"
    elsif person.last_name.present?
      if person.gender.present? && person.gender.salutation.present?
        "Hello #{person.gender.salutation} #{person.last_name}"
      else
        "Hello #{person.last_name}"
      end
    else
      "Hello"
    end
  end
end
class PersonGreeterAttributes
  def greet(person)
    person.with_attributes { |first_name|
      "Hi, #{first_name}"
    }.else_with_attributes { |gender_salutation,last_name|
      "Hello #{gender_salutation} #{last_name}"
    }.else_with_attributes { |last_name|
      "Hello #{last_name}"
    }.or_else {
      "Hello"
    }
  end
end
class AddressNormalizer
  def normalize_address(person)
    person.with_attributes { |city, zip_code|
      person.city     = city.strip.downcase
      person.zip_code = zipcode.strip.downcase
    }
  end
end
class AddressNormalizer
  def normalize_address(person)
    person.with_attributes { |city, zip_code|
      person.update_attributes do |update|
update.city.to city.strip.downcase update.zip_code.to zip_code.strip.downcase
end } end end
class Person
  def update_attributes(&block)
    update = Update.new
    block.(update)

    update.each_attribute do |name,new_value|
      instance_variable_set("@#{name}",new_value)
    end
  end
end
class Person
  def update_attributes(&block)
    updater = Updater.new
    block.call(updater)
transaction do
updater.changed_attributes { |attribute,new_value| self.instance_variable_set("@#{attribute}",value) }
end
end end
class Person
  def update_attributes(&block)
    updater = Updater.new
    block.call(updater)
updater.when_updated do |city,zip_code| unless zip_and_city_consistent?(updater.city,updater.zip_code) raise "address not consistent" end end
updater.changed_attributes { |attribute,new_value| self.instance_variable_set("@#{attribute}",value) } end end
class Person
  def update_attributes(&block)
    updater = Updater.new
    block.call(updater)
    updater.when_updated { |city,zip_code|
      unless zip_and_city_consistent?(updater.city,updater.zip_code)
        raise "address not consistent"
      end
}.else_when_updated { |city| raise "you cannot update the city without also updating the zipcode" }
updater.changed_attributes { |attribute,new_value| self.instance_variable_set("@#{attribute}",value) } end end

if

class CreditCardCharger
  def charge_customer(customer,amount)
    result = CreditCardService.charge!(customer,amount)
    unless result.success?
      if result.error_code == CreditCardService::EXPIRED
        "Your card has expired-update and try again"
      else
        "Sorry, your card was declined"
      end
    end
  rescue => ex
    ex.message
  end
end
class CreditCardChargerWeird
  def charge_customer(customer,amount)
    CreditCardServiceWeird.charge!(customer,amount) do |outcome|
      outcome.on_success {
        # ok!
      }.on_decline {
        "Sorry, your card was declined"
      }.on_expiration {
        "Your card has expired-update and try again"
      }.on_exception { |ex|
        ex.message
      }
    end
  end
end
class CreditCardChargerWeird
  def charge_customer(customer,amount)
    CreditCardServiceWeird.charge!(customer,amount) do |outcome|
      outcome.on_success {
        # ok!
      }.on_decline {
        "Sorry, your card was declined"
}.on_expiration { "Your card has expired-update and try again"
}.on_exception { |ex| ex.message } end end end
class CreditCardChargerMistake
  def charge_customer(customer,amount)
    CreditCardServiceStrict.charge!(customer,amount) do |outcome|
      outcome.on_success {
        # ok!
      }.on_decline {
        "Sorry, your card was declined"
      }.on_exception { |ex|
        ex.message
      }
    end
  end
end
class CreditCardServiceStrict
  def self.charge!(customer,amount,&block)
    outcome = Outcome.new
    block.call(outcome)
outcome.validate_all_outcomes_handled!
# ... end end
class CreditCardServiceStrict
  def self.charge!(customer,amount,&block)
    outcome = Outcome.new
    block.call(outcome)
    outcome.validate_all_outcomes_handled!

outcome.handle( rest_call(make_url(customer,amount)) )
private def rest_call(_) # ... end def make_url(customer,amount) # ... end end end
class OutcomeNoIf < Outcome
  def handle(result)
    status = result.status

method_name = what_to_call(status)
self.send(method_name) end end
class OutcomeNoIf < Outcome

private 

  def what_to_call(status)
    if status == 200
      :call_on_success
    elsif status < 500
      :call_on_decline
    else
      :call_on_exception
    end
  end
end
class OutcomeNoIf < Outcome

private 

  def what_to_call(status)

                if ( status == 200 )  :call_on_success
                if ( status <  500 )  :call_on_decline



    else      :call_on_exception
  end
end
class OutcomeNoIf < Outcome

private 

  def what_to_call(status)

       -> (status) { status == 200 }, :call_on_success
       -> (status) { status <  500 }, :call_on_decline



    else      :call_on_exception
  end
end
class OutcomeNoIf < Outcome

private 

  def what_to_call(status)
    [
     [ -> (status) { status == 200 }, :call_on_success ],
     [ -> (status) { status <  500 }, :call_on_decline ],
    ]
    
    
    else      :call_on_exception
  end
end
class OutcomeNoIf < Outcome

private 

  def what_to_call(status)
    [
     [ -> (status) { status == 200 }, :call_on_success ],
     [ -> (status) { status <  500 }, :call_on_decline ],
    ].detect { |predicate,_|
      predicate.(status)
    }
    else      :call_on_exception
  end
end
class OutcomeNoIf < Outcome

private 

  def what_to_call(status)
    _,method = [
      [ ->(status) { status == 200 }, :call_on_success ],
      [ ->(status) { status <  500 }, :call_on_decline ],
    ].detect { |predicate,_|
      predicate.(status)
    }
    method || :call_on_exception
  end
end
class Outcome
  def handle(result)
    status = result.status
    method_name = if status < 500
                    :call_on_decline
                  elsif status == 200
                    :call_on_success
                  else
                    :call_on_exception
                  end
    self.send(method_name)
  end
end
class OutcomeNoIf < Outcome

private 

  def what_to_call(status)
    _,method = [
      [ ->(status) { status <  500 }, :call_on_decline ],
      [ ->(status) { status == 200 }, :call_on_success ],
    ].detect { |predicate,_|
      predicate.(status)
    }
    method || :call_on_exception
  end
end
class OutcomeNoIf < Outcome

private 

  def what_to_call(status)
    logic = Logic.new

    logic.on { |status| status  < 500 }.then_return(:call_on_decline)
    logic.on { |status| status == 200 }.then_return(:call_on_success)

    logic.evaluate!(status, or_else: :call_on_exception)

  end
end
class OutcomeNoIf < Outcome

private 

  def what_to_call(status)
    logic = Logic.new

    logic.on { |status| status  < 500 }.then_return(:call_on_decline)
    logic.on { |status| status == 200 }.then_return(:call_on_success)

logic.evaluate!(status, or_else: :call_on_exception) # => BOOM: NonExclusiveDisjunctionError
end end
class OutcomeNoIf < Outcome

private 

  def what_to_call(status)
    logic = Logic.new

    logic.on { |status| status == 200 }.then_return(:call_on_success)
logic.on { |status| status != 200 &&
status < 500 }.then_return(:call_on_decline) logic.evaluate!(status, or_else: :call_on_exception) # => All good! end end
class OutcomeNoIf < Outcome

private 

  def what_to_call(status)
    logic = Logic.new

logic.on(&Success).then_return(:call_on_success) logic.on(&Decline).then_return(:call_on_decline)
logic.evaluate!(status, or_else: :call_on_exception) end
Success = ->(status) { status == 200 } Decline = ->(status) { !Success.(status) && status < 500 }
end

Whew!

Stitch Fix

Product-minded Engineers

No Pro{ject,duct} Managers

A simple and functional business model

Info tech.stitchfix.com

THANKS!

Slides

@

bit.ly/weird-ruby

Source

@

bit.ly/weird-ruby-source

Jobs

@

tech.stitchfix.com

 

Buy The Senior
Software Engineer
 

@

sweng.me
$12.50 with code
ancient

Colophon


Titles in Serifa (Century Schoolbook on web)

Code Listings in Source Code Pro (Consolas or Courier on web)

Other Text in Avenir (Century Gothic or Helvetica on web)


Make your own slideshow with trickster

CC-licensed images used:

http://farm2.staticflickr.com/1185/5112065674_84e67f1b41_o.jpg
http://farm3.staticflickr.com/2568/4170365807_9964740034_b.jpg
http://farm6.staticflickr.com/5229/5817035735_e99b206ae0_o.jpg
http://farm7.staticflickr.com/6154/6254480784_1b80df41ee_b.jpg
http://farm3.staticflickr.com/2554/3737568827_5f903cf17e_b.jpg