The Black Magic of Ruby Metaprogramming
-
Upload
itnig -
Category
Technology
-
view
2.189 -
download
0
description
Transcript of The Black Magic of Ruby Metaprogramming
The black magic ofRuby
metaprogramming
I am Cristian…
Gawyn
@cristianplanas
… and this is the story of how I fell in
love with Ruby
It was 2011, when I read this awesome book
(the smart part of this talk is based on it)
I was so excited that I created my first gem
just to play with metaprogramming.
Easyregexp
http://github.com/Gawyn/easyregexp
It was a regular expressions generator.
It relied heavily in metaprogramming.
It was never much useful.
Easyregexp
Anyway, I felt like this
I mean, people who use metaprogramming do
have superpowers.
They even know mysterious,
incomprehensible spells!
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.
Repeat with me
Sorry, today we won’t talk about
eigenclasses.
We will focus on the most down to earth
part of metaprogramming.
When I started using metaprogramming in my production code, I remembered an old
movie.
In it, Mickey Mouse is the hard-working apprentice of a powerful sorcerer.
One day, the sorcerer leaves, and Mickey can
play with magic…
So let’s learn some tricks!
Monkey patching
Adding code to an already defined class.
Monkey patching
class String
def say_hello
p “hello”
end
end
“an string”. say_hello
=> “hello”
An example
This means that we can extend any class with
our own methods.
You can also redefine an already existing
method!
Dynamic methods
Define methods with an algorythm in runtime.
Dynamic methods
Imagine a class full of boring, repeating methods.
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
A typical example
Now let’s add some more roles:
ROLES = [“guest”, “user”, “confirmed_user”,
“moderator”, “manager”, “admin”, “superadmin”]
A typical example
Damn!
Metaprogramming to the rescue!
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
A typical example
Great!
Evaluating strings as code
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.
class Movie
attr_reader :title_en, :title_es, :title_it, :title_pt
def title
send(“title_#{I18n.locale}”)
end
end
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
An example
You can even define new methods like this!
String.class_eval(“def say_hello; puts ‘hello’; end”)
“a string”.say_hello
#=> “hello”
method_missing
It’s the method that gets executed when the called
method it’s not found.
method_missing
We can monkey patch it!
class MetalDetector
def method_missing(method, *args, &block)
if method =~ /metal/
puts “Metal detected!”
else
super
end
end
end
metal_detector = MetalDetector.new
metal_detector.bringing_some_metal_with_me
# => “Metal detected!”
An example
A pretty exemplary use of method_missing use
are Rails’ dynamic finders
(deprecated in Rails 4)
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”)
So metaprogramming is pretty cool, isn’t it?
It can get cooler
Let’s check how ActiveRecord defines
its setter methods
(in an abbreviated version)
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
Defining setters
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
Finally it gets to something like this:
(as told, it’s a pretty abbreviated version)
To know more
Episode 8 of Metaprogramming Ruby: Inside ActiveRecord
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_methods.rb
https://github.com/rails/rails/blob/master/activemodel/lib/active_model/attribute_methods.rb
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_methods/write.rb
But in the end of the movie, all the magic backslashes…
Metaprogramming has its dangers
Unexpected method override
It happens when you rewrite an existing method changing its
behavior without checking the consequences.
That means all the code that rely in the old method behavior
will fail.
class Fixnum
alias :old_minus :-
alias :- :+
alias :+ :old_minus
end
4 + 3
#=> 1
5 – 2
#=> 7
Dynamic methods like the ones of the example can also
accidentally monkey patch critical methods!
Code injection through
evaluation
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!
Even if users should be able to evaluate code in your server,
there are ways to protect you:
Clean Rooms
Ghost methods
Methods working from inside method_missing don’t “really”
exist for Ruby.
class MetalDetector
def method_missing(method, *args)
if method =~ /metal/
puts “Metal detected!”
else
super
end
end
end
metal_detector = MetalDetector.new
metal_detector.metal
# => “Metal detected!”
metal_detector.respond_to?(:metal)
#=> false
There is a work-around: to monkey patch the respond_to?
method.
Feels kinda hacky.
And still, it can be hard to maintain.
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()
Some final thoughts
1. I look pretty cool with Superman trunks.
2. Metaprogramming is a name for different
techniques: you can use some and avoid others.
Personally, I use plenty of dynamic methods and avoid method_missing.
3. Just be sure of what you do when you use it.
The sorcerer won’t come to save you!
Thanks!