The Revenge of method_missing()

3,807 views
3,687 views

Published on

Slides of my presentation at EuRuKo 2011 in Berlin.

Published in: Technology, Business
4 Comments
8 Likes
Statistics
Notes
  • That's totally reasonable, and I had similar feedback from Matz himself - so next time I deliver the speech, I'll make it clear that you can make it work. :)
    My point is that getting it right in the first place is enough work that the alternative (define_method()) turns out to be easier in general.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Yep the presentation was very good on these pitfalls (and funny), it's just that I don't like to see code not working when it could work :)
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Agreed (Matz also called my attention to this). My point is not that you cannot make it work - you can, but the solution is complicated enough, and the pitfalls along the way slippery enough, that my final decision is not to use method_missing() in most cases where I can use define_method() instead.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • https://gist.github.com/997005 is a way to make it work
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
3,807
On SlideShare
0
From Embeds
0
Number of Embeds
79
Actions
Shares
0
Downloads
0
Comments
4
Likes
8
Embeds 0
No embeds

No notes for slide
  • aka...\n
  • I’m assuming you know what m_m() is, but I’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’s do some refactoring\n
  • \n
  • \n
  • \n
  • \n
  • - any method you can call is a ghost\n - fix: always select the methods you'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 “scary” 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’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
  • 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 frommy 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. Person.find :user_name => name, :password => pwd
    6. 6. class NilClass # ... def method_missing(method, *args, &block) if klass = METHOD_CLASS_MAP[method] raise_nil_warning_for klass, method, caller else super end endend
    7. 7. end of disclaimer
    8. 8. class InfoDesk def flights # ... end def trains # ... end def hotels # ... end # ...end
    9. 9. 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
    10. 10. 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) endend# At 12:30...Sign.new.flights # => “Out for lunch”
    11. 11. Ghost Methods
    12. 12. 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 endend
    13. 13. Ghost Methods vs.Dynamic Methods
    14. 14. the four pitfallsof method_missing()
    15. 15. 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) endend
    16. 16. we have a problem
    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) endend# 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) endend# At 12:30...Sign.new.flights # => ?
    19. 19. 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) endend# At 12:30...Sign.new.flights # => SystemStackError: stack level too deep
    20. 20. the first pitfall: the Ghost House
    21. 21. class Sign def initialize @desk = InfoDesk.new end def method_missing(name, *args) super unless @desk.respond_to? name return “Out for lunch” if Clock.lunch_time? @desk.send(name, *args) endend
    22. 22. we have a problem
    23. 23. Sign.new.respond_to? :flights # => false
    24. 24. the second pitfall: the Liar Object
    25. 25. class Sign def initialize @desk = ::InfoDesk.new end def method_missing(name, *args) 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 endendSign.new.respond_to? :flights # => true
    26. 26. we have a problem
    27. 27. class InfoDesk def flights # ... end def local_transports # ... end # ... def display # ... endend
    28. 28. Sign.new.display # => #<Sign:0x000001008451b0>
    29. 29. the third pitfall: the Fake Ghost
    30. 30. 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) 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 endendSign.new.display # => InfoDesk#display() called
    31. 31. class Sign < BasicObject def initialize @desk = ::InfoDesk.new end def method_missing(name, *args) 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 endendSign.new.display # => InfoDesk#display() called
    32. 32. we have a problem (this is getting old already)
    33. 33. the fourth pitfall: the Snail Ghost
    34. 34. class Sign < BasicObject def initialize @desk = ::InfoDesk.new end def method_missing(name, *args) 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 endend# At 12:30...Sign.new.flights # => ?
    35. 35. class Sign < BasicObject def initialize @desk = ::InfoDesk.new end def method_missing(name, *args) 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 endend# At 12:30...Sign.new.flights # => SystemStackError: stack level too deep
    36. 36. class Sign < BasicObject def initialize @desk = ::InfoDesk.new end def method_missing(name, *args) 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 endend# At 12:30...Sign.new.flights # => SystemStackError: stack level too deep
    37. 37. should I use method_missing() to remove duplication in my code?
    38. 38. not if you can usedefine_method() instead
    39. 39. just a rule of thumb
    40. 40. remember the four pitfalls: the Ghost House the Liar Object the Fake Ghost the Snail Ghost
    41. 41. thank you.

    ×