metaprogramming code-that-writes-code
TRANSCRIPT
The Boss’ Challenge
1 class Person 2 include CheckedAttributes 3 attr_checked :age do |v| 4 v >= 18 5 end 6 end 7 8 me = Person.new 9 me.age = 39 # OK 10 me.age = 12 # Exception
Requirements• Applies when class includes CheckedAttributs
Module
• Class Macro: attr_checked
• First Argument as attribute name
• Second Argument can accept a block as validator of attribute
Bill steps
• Kernel method “add_checked_attribute” by Kernel#eval
• Refactoring: remove Kernel#eval
• Block attribute validator
• Replace method to class macro for all classes
• A Module with attr_checked method, hooks to class
Kernel#eval• Spells: String of Code.
• The most Strings of Code feature some kind of string substitution.
• Ex: ……
1 array = [10, 20] 2 element = 30 3 eval("array << element") # => [10, 20, 30]
eval(string [, binding [, filename [,lineno]]]) → obj
• Evaluates the Ruby expression(s) in String
• If binding is given, which must be a Binding object, the evaluation is performed in its context.
• If the optional filename and lineno parameters are present, they will be used when reporting syntax errors
Ex 1. REST Client• Its simple HTTP library for sending HTTP request.
1 #Originally, It needs to declare 4 methods 2 def get(path, *args, &b) 3 r[path].get(*args, &b) 4 end 5 6 def set(path, *args, &b) 7 r[path].set(*args, &b) 8 end 9 10 def put(path, *args, &b) 11 r[path].put(*args, &b) 12 end 13 14 def delete(path, *args, &b) 15 r[path].delete(*args, &b) 16 end
Ex 1. REST Client• Its simple HTTP library for sending HTTP request.
1 # Using "String Of Code" 2 POSSIBLE_VERBS = ['get', 'put', 'post', 3 'delete'] 4 POSSIBLE_VERBS.each do |m| 5 #syntax of heredoc 6 eval <<-end_eval 7 def #{m}(path, *args, &b) 8 r[path].#{m}(*args, &b) 9 end 10 end_eval 11 end
Here Document 1 hdoc = <<-hdoc_end 2 This is here document 3 hdoc_end 4 5 puts "Show:#{hdoc}" 6 #=> Show: This is here document
1 puts("Show:" + <<-hdoc_end) 2 This is here document 3 hdoc_end 4 #=> Show: This is here document
1 puts("Show:" + <<-hdoc_end.gsub(/^\s+/, '')) 2 This is here document 3 hdoc_end 4 #=> Show:This is here document
Binding objects• Objects of class Binding encapsulate the
execution context at some particular place in the code and retain this context for future use.
• Kernel#binding returns the binding object. 1 class MyClass 2 def my_method 3 @x = 1 4 binding 5 end 6 end 7 b = MyClass.new.my_method 8 eval "p @x", b
Strings of Code vs. Blocks
• instance_eval and class_eval also accept String as argument.
• Which one should you choose?
Avoid String of Code whenever you have an alternative.
The Trouble with eval()• Difficult to read and modify.
• Won’t report syntax error until the string is evaluate.
• Security issue: Code injection!
Code Injection 1 def explore_array(method_name) 2 code = "[1,2,3,4].#{method_name}" 3 puts "Evaluating: #{code}" 4 eval code 5 end 6 7 orga_input = "find_index(2)" 8 p explore_array(orga_input) # => 1 9 10 otis_input = "map!(&:next)" 11 p explore_array(otis_input) # => [2, 3, 4, 5] 12 13 sunkai_input = "object_id; Dir.glob('*')" 14 p explore_array(sunkai_input) # => Some 15 sensitive information!!!
Defending • Dynamic Method & Dynamic Dispatch
• Tainted Objects: object#tainted?
• Safe Levels: $SAFE = 0 ~ 3
• Clean Room: proc()
Step 1 - Checked Attributes
• Kernel method: add_checked_attribute.
• Raise exception if value of attribute is nil or false.
• Source Code
Step 2 - Remove eval()• class name is variable: Open Class by class_eval
• method name is variable: Dynamic Methods
• instance variable name is variable: instance_variable_get/instance_variable_set
• Source Code
Step 3 - Block Validator• Attribute validation according to return value of
block.
• Source Code
Step 4 - Class Marco• Class marco: attr_checked
• A class marco for all classes: Instance method of Class/Module.
• Source Code
Hook Methods• The object model is an eventful place.
• Class inherited
• Module included, prepended and extended
• Instance method added, removed and undefined
• Singleton method added, removed and undefined
Quiz• Q: undef_method Vs remove_method?
• undef_method: added an exception to that method.
• remove_method: remove the method at current class, receiver will try to find it from ancestor.
Quiz• Q: which one
method is useless?
1 #Which one method_hook is useless? 2 class Person 3 def self.singleton_method_added(m) 4 p "M1: #{m}" 5 end 6 def self.method_added(m) 7 p "M2: #{m}" 8 end 9 def singleton_method_added(m) 10 p "M3: #{m}" 11 end 12 def method_added(m) 13 p "M4: #{m}" 14 end 15 def hello 16 end 17 def self.hehe 18 end 19 end 20 21 person = Person.new 22 def person.yo 23 end
• method_added
Quiz• Q: Other approaches for hook method?
• Override method and call super in the end of method
• Around Alias
Step 5 - Final step• Declare an CheckedAttributes Module
• Include module as class method: ClassMehtods-plus-hook.
• Source Code
Epilogue
• Are you smart enough to forget what you have learned?
• There’s no such thing as metaprogramming. It’s just programming all the way through
Shuhari (Kanji: 守破離 Hiragana: ゅはり)
• Shu(protect, obey): learning fundamentals, techniques, heuristics, proverbs.
• Ha(detach, digress): detachment from the illusions of self.
• Ri(leave, separate): there are no techniques or proverbs, all moves are natural, becoming one with spirit alone without clinging to forms; transcending the physical