the black magic of ruby metaprogramming

71
The black magic of Ruby metaprogramming

Upload: itnig

Post on 08-May-2015

2.189 views

Category:

Technology


0 download

DESCRIPTION

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

TRANSCRIPT

Page 1: The Black Magic of Ruby Metaprogramming

The black magic ofRuby

metaprogramming

Page 2: The Black Magic of Ruby Metaprogramming

I am Cristian…

Gawyn

@cristianplanas

Page 3: The Black Magic of Ruby Metaprogramming

… and this is the story of how I fell in

love with Ruby

Page 4: The Black Magic of Ruby Metaprogramming

It was 2011, when I read this awesome book

(the smart part of this talk is based on it)

Page 5: The Black Magic of Ruby Metaprogramming

I was so excited that I created my first gem

just to play with metaprogramming.

Page 6: The Black Magic of Ruby Metaprogramming

Easyregexp

http://github.com/Gawyn/easyregexp

Page 7: The Black Magic of Ruby Metaprogramming

It was a regular expressions generator.

It relied heavily in metaprogramming.

It was never much useful.

Easyregexp

Page 8: The Black Magic of Ruby Metaprogramming

Anyway, I felt like this

Page 9: The Black Magic of Ruby Metaprogramming

I mean, people who use metaprogramming do

have superpowers.

Page 10: The Black Magic of Ruby Metaprogramming

They even know mysterious,

incomprehensible spells!

Page 11: The Black Magic of Ruby Metaprogramming

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

Page 12: The Black Magic of Ruby Metaprogramming

Sorry, today we won’t talk about

eigenclasses.

Page 13: The Black Magic of Ruby Metaprogramming

We will focus on the most down to earth

part of metaprogramming.

Page 14: The Black Magic of Ruby Metaprogramming

When I started using metaprogramming in my production code, I remembered an old

movie.

Page 15: The Black Magic of Ruby Metaprogramming
Page 16: The Black Magic of Ruby Metaprogramming

In it, Mickey Mouse is the hard-working apprentice of a powerful sorcerer.

Page 17: The Black Magic of Ruby Metaprogramming

One day, the sorcerer leaves, and Mickey can

play with magic…

Page 18: The Black Magic of Ruby Metaprogramming

So let’s learn some tricks!

Page 19: The Black Magic of Ruby Metaprogramming

Monkey patching

Page 20: The Black Magic of Ruby Metaprogramming

Adding code to an already defined class.

Monkey patching

Page 21: The Black Magic of Ruby Metaprogramming

class String

def say_hello

p “hello”

end

end

“an string”. say_hello

=> “hello”

An example

Page 22: The Black Magic of Ruby Metaprogramming

This means that we can extend any class with

our own methods.

Page 23: The Black Magic of Ruby Metaprogramming

You can also redefine an already existing

method!

Page 24: The Black Magic of Ruby Metaprogramming

Dynamic methods

Page 25: The Black Magic of Ruby Metaprogramming

Define methods with an algorythm in runtime.

Dynamic methods

Page 26: The Black Magic of Ruby Metaprogramming

Imagine a class full of boring, repeating methods.

A typical example

Page 27: The Black Magic of Ruby Metaprogramming

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

Page 28: The Black Magic of Ruby Metaprogramming

Now let’s add some more roles:

ROLES = [“guest”, “user”, “confirmed_user”,

“moderator”, “manager”, “admin”, “superadmin”]

A typical example

Page 29: The Black Magic of Ruby Metaprogramming

Damn!

Page 30: The Black Magic of Ruby Metaprogramming

Metaprogramming to the rescue!

A typical example

Page 31: The Black Magic of Ruby Metaprogramming

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

Page 32: The Black Magic of Ruby Metaprogramming

Great!

Page 33: The Black Magic of Ruby Metaprogramming

Evaluating strings as code

Page 34: The Black Magic of Ruby Metaprogramming

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.

Page 35: The Black Magic of Ruby Metaprogramming

class Movie

attr_reader :title_en, :title_es, :title_it, :title_pt

def title

send(“title_#{I18n.locale}”)

end

end

An example

Page 36: The Black Magic of Ruby Metaprogramming

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

Page 37: The Black Magic of Ruby Metaprogramming

You can even define new methods like this!

String.class_eval(“def say_hello; puts ‘hello’; end”)

“a string”.say_hello

#=> “hello”

Page 38: The Black Magic of Ruby Metaprogramming

method_missing

Page 39: The Black Magic of Ruby Metaprogramming

It’s the method that gets executed when the called

method it’s not found.

method_missing

Page 40: The Black Magic of Ruby Metaprogramming

We can monkey patch it!

Page 41: The Black Magic of Ruby Metaprogramming

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

Page 42: The Black Magic of Ruby Metaprogramming

A pretty exemplary use of method_missing use

are Rails’ dynamic finders

(deprecated in Rails 4)

Page 43: The Black Magic of Ruby Metaprogramming

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”)

Page 44: The Black Magic of Ruby Metaprogramming

So metaprogramming is pretty cool, isn’t it?

Page 45: The Black Magic of Ruby Metaprogramming

It can get cooler

Page 46: The Black Magic of Ruby Metaprogramming

Let’s check how ActiveRecord defines

its setter methods

(in an abbreviated version)

Page 47: The Black Magic of Ruby Metaprogramming

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

Page 48: The Black Magic of Ruby Metaprogramming

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)

Page 49: The Black Magic of Ruby Metaprogramming

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

Page 50: The Black Magic of Ruby Metaprogramming

But in the end of the movie, all the magic backslashes…

Page 51: The Black Magic of Ruby Metaprogramming

Metaprogramming has its dangers

Page 52: The Black Magic of Ruby Metaprogramming

Unexpected method override

Page 53: The Black Magic of Ruby Metaprogramming

It happens when you rewrite an existing method changing its

behavior without checking the consequences.

Page 54: The Black Magic of Ruby Metaprogramming

That means all the code that rely in the old method behavior

will fail.

Page 55: The Black Magic of Ruby Metaprogramming

class Fixnum

alias :old_minus :-

alias :- :+

alias :+ :old_minus

end

4 + 3

#=> 1

5 – 2

#=> 7

Page 56: The Black Magic of Ruby Metaprogramming

Dynamic methods like the ones of the example can also

accidentally monkey patch critical methods!

Page 57: The Black Magic of Ruby Metaprogramming

Code injection through

evaluation

Page 58: The Black Magic of Ruby Metaprogramming

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!

Page 59: The Black Magic of Ruby Metaprogramming

Even if users should be able to evaluate code in your server,

there are ways to protect you:

Clean Rooms

Page 60: The Black Magic of Ruby Metaprogramming

Ghost methods

Page 61: The Black Magic of Ruby Metaprogramming

Methods working from inside method_missing don’t “really”

exist for Ruby.

Page 62: The Black Magic of Ruby Metaprogramming

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

Page 63: The Black Magic of Ruby Metaprogramming

There is a work-around: to monkey patch the respond_to?

method.

Feels kinda hacky.

And still, it can be hard to maintain.

Page 64: The Black Magic of Ruby Metaprogramming

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()

Page 65: The Black Magic of Ruby Metaprogramming

Some final thoughts

Page 66: The Black Magic of Ruby Metaprogramming

1. I look pretty cool with Superman trunks.

Page 67: The Black Magic of Ruby Metaprogramming

2. Metaprogramming is a name for different

techniques: you can use some and avoid others.

Page 68: The Black Magic of Ruby Metaprogramming

Personally, I use plenty of dynamic methods and avoid method_missing.

Page 69: The Black Magic of Ruby Metaprogramming

3. Just be sure of what you do when you use it.

Page 70: The Black Magic of Ruby Metaprogramming

The sorcerer won’t come to save you!

Page 71: The Black Magic of Ruby Metaprogramming

Thanks!