Successfully reported this slideshow.
Your SlideShare is downloading. ×

The Revenge of method_missing()

Ad

The revenge of method_missing()




       Paolo “Nusco” Perrotta

Ad

Should I use method_missing()
  to remove duplication from
my code, or should I avoid it
       like the plague?

      Pa...

Ad

disclaimer

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Loading in …3
×

Check these out next

1 of 49 Ad
1 of 49 Ad

More Related Content

The Revenge of method_missing()

  1. 1. The revenge of method_missing() Paolo “Nusco” Perrotta
  2. 2. Should I use method_missing() to remove duplication from my code, or should I avoid it like the plague? Paolo “Nusco” Perrotta
  3. 3. disclaimer
  4. 4. Person.find_by_user_name_and_password name, pwd
  5. 5. class NilClass # ... def method_missing(method, *args, &block) if klass = METHOD_CLASS_MAP[method] raise_nil_warning_for klass, method, caller else super end end end
  6. 6. end of disclaimer
  7. 7. class InfoDesk def flights # ... end def trains # ... end def hotels # ... end # ... end
  8. 8. class Sign def initialize @desk = InfoDesk.new end def flights return “Out for lunch” if Clock.lunch_time? @desk.flights end def trains return “Out for lunch” if Clock.lunch_time? @desk.local_transports end def hotels return “Out for lunch” if Clock.lunch_time? @desk.local_transports end # ... end
  9. 9. class Sign def initialize @desk = InfoDesk.new end def method_missing(name, *args) return “Out for lunch” if Clock.lunch_time? @desk.send(name, *args) end end # At 12:30... Sign.new.flights # => “Out for lunch”
  10. 10. Ghost Methods
  11. 11. class Sign def initialize @desk = InfoDesk.new end InfoDesk.public_instance_methods.each do |m| define_method(m) do return “Out for lunch” if Clock.lunch_time? @desk.send(m) end end end
  12. 12. Ghost Methods vs. Dynamic Methods
  13. 13. the four pitfalls of method_missing()
  14. 14. class Sign def initialize @desk = InfoDesk.new end def method_missing(name, *args) return “Out for lunch” if Clock.lunch_time? @desk.send(name, *args) end end
  15. 15. we have a problem
  16. 16. class Sign def initialize @desk = InfoDesk.new end def is_it_lunch_time_yet? Clock.lunch_time? end def method_missing(name, *args) return “Out for lunch” if is_it_lunchtime_yet? @desk.send(name, *args) end end # At 12:30... Sign.new.flights # => ?
  17. 17. class Sign def initialize @desk = InfoDesk.new end def is_it_lunch_time_yet? Clock.lunch_time? end def method_missing(name, *args) return “Out for lunch” if is_it_lunchtime_yet? @desk.send(name, *args) end end # At 12:30... Sign.new.flights # => ?
  18. 18. class Sign def initialize @desk = InfoDesk.new end def is_it_lunch_time_yet? Clock.lunch_time? end def method_missing(name, *args) return “Out for lunch” if is_it_lunchtime_yet? @desk.send(name, *args) end end # At 12:30... Sign.new.flights # => SystemStackError: stack level too deep
  19. 19. the first pitfall: the Ghost House
  20. 20. class Sign def initialize @desk = InfoDesk.new end def method_missing(name, *args) return super unless @desk.respond_to? name return “Out for lunch” if Clock.lunch_time? @desk.send(name, *args) end end
  21. 21. we have a problem
  22. 22. Sign.new.respond_to? :flights # => false
  23. 23. the second pitfall: the Liar Object
  24. 24. class Sign def initialize @desk = ::InfoDesk.new end def method_missing(name, *args) return super unless @desk.respond_to? name return “Out for lunch” if Clock.lunch_time? @desk.send(name, *args) end def respond_to?(method) @desk.respond_to?(method) || super end end Sign.new.respond_to? :flights # => true
  25. 25. we have a problem
  26. 26. class InfoDesk def flights # ... end def local_transports # ... end # ... def display # ... end end
  27. 27. Sign.new.display # => #<Sign:0x000001008451b0>
  28. 28. the third pitfall: the Fake Ghost
  29. 29. class Sign instance_methods.each do |m| undef_method m unless m.to_s =~ /^__|object_id/ end def initialize @desk = InfoDesk.new end def method_missing(name, *args) return super unless @desk.respond_to? name return "Out for lunch" if Clock.lunch_time? @desk.send(name, *args) end def respond_to?(method) @desk.respond_to? method || super end end Sign.new.display # => InfoDesk#display() called
  30. 30. class Sign < BasicObject def initialize @desk = ::InfoDesk.new end def method_missing(name, *args) return super unless @desk.respond_to? name return “Out for lunch” if ::Clock.lunch_time? @desk.send(name, *args) end def respond_to?(method) @desk.respond_to? method || super end end Sign.new.display # => InfoDesk#display() called
  31. 31. we have a problem (this is getting old already)
  32. 32. the fourth pitfall: the Snail Ghost
  33. 33. class Sign < BasicObject def initialize @desk = ::InfoDesk.new end def method_missing(name, *args) return super unless @desk.respond_to? name singleton_class.send :define_method, name do return “Out for lunch” if ::Clock.lunch_time? @desk.send name end send name end def respond_to?(method) @desk.respond_to? method || super end end # At 12:30... Sign.new.flights # => ?
  34. 34. class Sign < BasicObject def initialize @desk = ::InfoDesk.new end def method_missing(name, *args) return super unless @desk.respond_to? name singleton_class.send :define_method, name do return “Out for lunch” if ::Clock.lunch_time? @desk.send name end send name end def respond_to?(method) @desk.respond_to? method || super end end # At 12:30... Sign.new.flights # => SystemStackError: stack level too deep
  35. 35. class Sign < BasicObject def initialize @desk = ::InfoDesk.new end def method_missing(name, *args) return super unless @desk.respond_to? name singleton_class.send :define_method, name do return “Out for lunch” if ::Clock.lunch_time? @desk.send name end send name end def respond_to?(method) @desk.respond_to? method || super end end # At 12:30... Sign.new.flights # => SystemStackError: stack level too deep
  36. 36. should I use method_missing() to remove duplication in my code?
  37. 37. not if you can use define_method() instead
  38. 38. just a rule of thumb
  39. 39. remember the four pitfalls: the Ghost House the Liar Object the Fake Ghost the Snail Ghost
  40. 40. thank you.

Editor's Notes

  • aka...\n
  • I&amp;#x2019;m assuming you know what m_m() is, but I&amp;#x2019;m also going to show a basic example\n
  • - question at RubyKaigi\n- blog post on RubyLearning\n- disclaimer: this is about using method_missing() even when there are alternate techniques\n- there are cases where there is no alternative\n
  • \n
  • \n
  • (accessors, writing a DSL - think hard before you do this!)\nnot just for cutesy\n
  • example: whiny nils\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • ghost methods\n
  • \n
  • ghost methods\n
  • my own experiment\n
  • back to our original solution\nlet&amp;#x2019;s do some refactoring\n
  • \n
  • \n
  • \n
  • \n
  • - any method you can call is a ghost\n - fix: always select the methods you&apos;re using\n\n\n
  • - fix: always remember to call super\n\n
  • \n
  • \n
  • no respond_to?\n(also, breaks the IDE and searches)\n\n
  • - fix: always implement respond_to?)\n- limitations of fix (methods(), etc)\n\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • - fix: inherit from a Blank Slate\n\n
  • the &amp;#x201C;scary&amp;#x201D; solution\nwhich methods should I remove exactly?\n
  • \n
  • \n
  • \n
  • \n
  • - fix: (Reincarnation) lazily define methods with define_method() in method_missing()\n\n\n
  • ...but it doesn&amp;#x2019;t work!\n
  • why it breaks\nwhat a mess! the experiment is over...\n
  • talking to jake scruggs\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n

×