The Black Magic of Ruby Metaprogramming


Published on

What is metaprogramming is and how can we use it in our everyday code - by Cristian Planas, CTO at Playfulbet

Published in: Technology
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

The Black Magic of Ruby Metaprogramming

  1. 1. The black magic of Ruby metaprogramming
  2. 2. I am Cristian… Gawyn @cristianplanas
  3. 3. … and this is the story of how I fell in love with Ruby
  4. 4. It was 2011, when I read this awesome book (the smart part of this talk is based on it)
  5. 5. I was so excited that I created my first gem just to play with metaprogramming.
  6. 6. Easyregexp
  7. 7. Easyregexp It was a regular expressions generator. It relied heavily in metaprogramming. It was never much useful.
  8. 8. Anyway, I felt like this
  9. 9. I mean, people who use metaprogramming do have superpowers.
  10. 10. They even know mysterious, incomprehensible spells!
  11. 11. Repeat with me The superclass of the eigenclass of an object is the object’s class. The superclass of the eigenclass of a class is the eigenclass of the class’s superclass.
  12. 12. Sorry, today we won’t talk about eigenclasses.
  13. 13. We will focus on the most down to earth part of metaprogramming.
  14. 14. When I started using metaprogramming in my production code, I remembered an old movie.
  15. 15. In it, Mickey Mouse is the hard-working apprentice of a powerful sorcerer.
  16. 16. One day, the sorcerer leaves, and Mickey can play with magic…
  17. 17. So let’s learn some tricks!
  18. 18. Monkey patching
  19. 19. Monkey patching Adding code to an already defined class.
  20. 20. An example class String def say_hello p “hello” end end “an string”. say_hello => “hello”
  21. 21. This means that we can extend any class with our own methods.
  22. 22. You can also redefine an already existing method!
  23. 23. Dynamic methods
  24. 24. Dynamic methods Define methods with an algorythm in runtime.
  25. 25. A typical example Imagine a class full of boring, repeating methods.
  26. 26. A typical example class User ROLES = [“user”, “admin”] # scopes for each role scope :user, where(role: “user”) scope :admin, where(role: “admin”) # Identifying methods for each role def user? role == “user” end def admin? role == “admin” end end
  27. 27. A typical example Now let’s add some more roles: ROLES = [“guest”, “user”, “confirmed_user”, “moderator”, “manager”, “admin”, “superadmin”]
  28. 28. Damn!
  29. 29. A typical example Metaprogramming to the rescue!
  30. 30. A typical example class User ROLES = [“guest”, “user”, “confirmed_user”, “moderator”, “manager”, “admin”, “superadmin”] ROLES.each do |user_role| scope user_role.to_sym, where(role: user_role) define_method “#{user_role}?” do role == user_role end end end
  31. 31. Great!
  32. 32. Evaluating strings as code
  33. 33. It does just that: run a string as if it was code. class_eval and instance_eval do the same, just changing the context. We can also use it on an object using send.
  34. 34. An example class Movie attr_reader :title_en, :title_es, :title_it, :title_pt def title send(“title_#{I18n.locale}”) end end
  35. 35. An example class Movie translate :title, :overview def translate(*attributes) attributes.each do |attr| define_method attr do send(“#{attr}_#{I18n.locale}”) end end end end
  36. 36. You can even define new methods like this! String.class_eval(“def say_hello; puts „hello‟; end”) “a string”.say_hello #=> “hello”
  37. 37. method_missing
  38. 38. method_missing It’s the method that gets executed when the called method it’s not found.
  39. 39. We can monkey patch it!
  40. 40. An example class MetalDetector def method_missing(method, *args, &block) if method =~ /metal/ puts “Metal detected!” else super end end end metal_detector = metal_detector.bringing_some_metal_with_me # => “Metal detected!”
  41. 41. A pretty exemplary use of method_missing use are Rails’ dynamic finders (deprecated in Rails 4)
  42. 42. Calling finding methods with any combination of attributes will work. User.find_by_name_and_surname(“Mickey”, “Mouse”) User.find_by_surname_and_movie(“Mouse”, “Fantasia”) User.find_by_movie_and_job_and_name(“Fantasia”, “sorcerer”, “Mickey”)
  43. 43. So metaprogramming is pretty cool, isn’t it?
  44. 44. It can get cooler
  45. 45. Let’s check how ActiveRecord defines its setter methods (in an abbreviated version)
  46. 46. Defining setters def method_missing(method, *args, &block) unless self.class.attribute_methods_generated? self.class.define_attribute_methods if respond_to_without_attributes?(method) send(method, *args, &block) else super end else super end end
  47. 47. Finally it gets to something like this: def define_write_method(attr_name) method_definition = “def #{attr_name}=(new_value); write_attribute(„#{attr_name}‟, new_value); end” class_eval(method_definition, __FILE__, __LINE) end (as told, it’s a pretty abbreviated version)
  48. 48. To know more Episode 8 of Metaprogramming Ruby: Inside ActiveRecord ecord/attribute_methods.rb model/attribute_methods.rb ecord/attribute_methods/write.rb
  49. 49. But in the end of the movie, all the magic backslashes…
  50. 50. Metaprogramming has its dangers
  51. 51. Unexpected method override
  52. 52. It happens when you rewrite an existing method changing its behavior without checking the consequences.
  53. 53. That means all the code that rely in the old method behavior will fail.
  54. 54. class Fixnum alias :old_minus :alias :- :+ alias :+ :old_minus end 4+3 #=> 1 5–2 #=> 7
  55. 55. Dynamic methods like the ones of the example can also accidentally monkey patch critical methods!
  56. 56. Code injection through evaluation
  57. 57. If you use any kind of eval, remember to think in all possible cases, specially if users are involved. You don’t want to evaluate “User.destroy_all” on your own application!
  58. 58. Even if users should be able to evaluate code in your server, there are ways to protect you: Clean Rooms
  59. 59. Ghost methods
  60. 60. Methods working from inside method_missing don’t “really” exist for Ruby.
  61. 61. class MetalDetector def method_missing(method, *args) if method =~ /metal/ puts “Metal detected!” else super end end end metal_detector = metal_detector.metal # => “Metal detected!” metal_detector.respond_to?(:metal) #=> false
  62. 62. There is a work-around: to monkey patch the respond_to? method. Feels kinda hacky. And still, it can be hard to maintain.
  63. 63. If you want to know more about the dangers of method_missing, Paolo Perrotta (the author of Metaprogramming Ruby) has a full presentation about it: The revenge of method_missing()
  64. 64. Some final thoughts
  65. 65. 1. I look pretty cool with Superman trunks.
  66. 66. 2. Metaprogramming is a name for different techniques: you can use some and avoid others.
  67. 67. Personally, I use plenty of dynamic methods and avoid method_missing.
  68. 68. 3. Just be sure of what you do when you use it.
  69. 69. The sorcerer won’t come to save you!
  70. 70. Thanks!