Skip to content

Instantly share code, notes, and snippets.

@avdi
Created December 5, 2011 22:17
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save avdi/1435647 to your computer and use it in GitHub Desktop.
Save avdi/1435647 to your computer and use it in GitHub Desktop.
Experimenting with Object#when in Ruby
def maybe_a_sandwich
nil
end
# Methods that might return nil are annoying. We want to write a nice
# confident chain:
result = nil
result = maybe_a_sandwich.add_horseradish.get_into_my_belly! rescue $!
result # => #<NoMethodError: undefined method `add_horseradish' for nil:NilClass>
# But then we find out it might return nil and have to rewrite it with
# special-case handling:
class NoSandwich
def method_missing(*)
self
end
def to_s
"I'm so hungry!"
end
end
sandwich = (maybe_a_sandwich || NoSandwich.new)
result = sandwich.add_horseradish.get_into_my_belly!
result # => I'm so hungry!
# It would be cool if we could just insert the special case into the
# chain.
class Object
def when(matcher)
if matcher === self then yield(self) else self end
end
end
result = maybe_a_sandwich.when(nil){NoSandwich.new}.add_horseradish.get_into_my_belly!
result # => I'm so hungry!
# I can think of lots of other uses for this. Like a special case for
# an empty collection:
def sandwiches
[]
end
result = begin
sandwiches.when(->(c){c.empty?}){raise "Out of sandwiches!"}.each do |s|
s.get_into_my_belly!
end
rescue
$!
end
result # => #<RuntimeError: Out of sandwiches!>
# Or a modifying filter:
class Sandwich
attr_accessor :contents
def get_into_my_belly!
"yum"
end
def gross?
contents.include?(:liver)
end
end
sandwich = Sandwich.new
sandwich.contents = [:roast_beef, :swiss_chees, :liver]
result = sandwich.when(->(s){s.gross?}){|s|
s.contents.delete(:liver); s
}.get_into_my_belly!
result # => "yum"
# Man for some reason I'm hungry for a sandwich now.
# P.S. The number of keywords Ruby will let you also use as method
# names is frankly a little shocking. Here's Object#if:
class Object
def if(predicate)
if predicate.to_proc.(self)
yield(self)
else
self
end
end
end
result = sandwich.if(:gross?){|s|
s.contents.delete(:liver); s
}.get_into_my_belly!
result # => "yum"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment