0
Metaprogramming    in Ruby
Things I Wish I KnewWhen I Started Ruby
Who?Joshua Hull@jjhttps://github.com/joshbuddy
What?
What?Writing programs that write programs.
What?Writing programs that write programs.        NOT code generation!
Why?We all do it.
Why?           We all do it.Ruby   attr_accessor :my_attribute
Why?           We all do it.Ruby   attr_accessor :my_attribute       def my_attribute         @my_attribute       end     ...
Why?           We all do it.Ruby   attr_accessor :my_attributeJava   public int getAttribute() {         return attribute;...
Why?               We all do it.   ner in RubyW          attr_accessor :my_attribute    Java   public int getAttribute() {...
Why?def age  @age || not setenddef gender  @gender || not setenddef name  @name || not setend
Why?              def age                @age || not set              end              def gender                @gender |...
Drawbacks
DrawbacksYou can write some difficult-to-understand code.
DrawbacksYou can write some difficult-to-understand code.
Method dispatch
Method dispatch   class MyObject     attr_accessor :name     def say_hello       puts "hello"       puts name     end   end
Method dispatch   class MyObject     attr_accessor :name     def say_hello       puts "hello"       puts name     end   en...
Method dispatch   class MyObject     attr_accessor :name     def say_hello       puts "hello"       puts self.name     end...
Method dispatch   class MyObject     attr_accessor :name     def say_hello       puts "hello"       puts self.name     end...
Method dispatch   class MyObject     attr_accessor :name     def say_hello       puts "hello"       puts name     end   end
Method dispatch  class MyObject    self.attr_accessor :name    def say_hello      puts "hello"      puts name    end  end
Method dispatch              class MyObjectWho is self     self.attr_accessor :name here?          def say_hello          ...
Method dispatch              class MyObjectWho is self     self.attr_accessor :name  here?         def say_hello          ...
Method dispatch              Module#attrWho is self  here?The class   is!
Ruby classes
Ruby classesclass NewClass  def hey    puts hello!  endend
Ruby classes                 class NewClass             {                   def hey                     puts hello!       ...
Ruby classesclass NewClass  def hey    puts hello!  endend     is the same asNewClass = Class.new do  def hey    puts hell...
Ruby classesclass ParsingError < RuntimeErrorend
Ruby classesclass ParsingError < RuntimeErrorend            is the same asParsingError = Class.new(RuntimeError)
Ruby classesdef class_with_accessors(*attributes)  Class.new do    attr_accessor *attributes  endend
Ruby classesdef class_with_accessors(*attributes)  Class.new do    attr_accessor *attributes  endend                     R...
Ruby classesdef class_with_accessors(*attributes)  Class.new do    attr_accessor *attributes  endend                     R...
eval, instance_eval,     class_eval
eval, instance_eval,        class_evalMethodevalinstance_evalclass_eval
eval, instance_eval,        class_evalMethod          Contexteval            your current contextinstance_eval   the objec...
eval, instance_eval,       class_evalevaleval "puts hello"# hello
eval, instance_eval,        class_evalinstance_evalclass MyClassendMyClass.instance_eval("def hi; hi; end")
eval, instance_eval,        class_evalinstance_evalclass MyClassendMyClass.instance_eval("def hi; hi; end")MyClass.hi# hi
eval, instance_eval,        class_evalinstance_evalclass MyClassendMyClass.instance_eval("def hi; hi end")obj = MyClass.ne...
eval, instance_eval,        class_evalclass_evalclass MyClassendMyClass.class_eval("def hi; hi end")
eval, instance_eval,        class_evalclass_evalclass MyClassendMyClass.class_eval("def hi; hi end")MyClass.hi# NoMethodEr...
eval, instance_eval,        class_evalclass_evalclass MyClassendMyClass.class_eval("def hi; hi end")obj = MyClass.new# <My...
eval, instance_eval,                     class_evalclass_eval                                instance_evalclass MyClass   ...
eval, instance_eval,       class_evalNinja’s will attack you if ...you don’t use __FILE__, __LINE__
eval, instance_eval,               class_evalclass HiThereendHiThere.class_eval "def hi; raise; end"HiThere.class_eval "de...
eval, instance_eval,               class_evalclass HiThereendHiThere.class_eval "def hi; raise; end"HiThere.class_eval "de...
eval, instance_eval,               class_evalclass HiThereendHiThere.class_eval "def hi; raise; end"HiThere.class_eval "de...
eval, instance_eval,     class_eval  Implement attr_accessor!
eval, instance_eval,           class_evalclass Module  def create_attr(attribute)    class_eval("def #{attribute}; @#{attr...
eval, instance_eval,           class_evalclass Module  def create_attr(attribute)    class_eval("def #{attribute}; @#{attr...
eval, instance_eval,           class_evalclass Module  def create_attr(attribute)    class_eval("def #{attribute}; @#{attr...
Defining methods
Defining methods              For an objecto = Object.newo.instance_eval("def just_this_object; end")o.just_this_object
Defining methods              For an objecto = Object.newo.instance_eval("def just_this_object; end")o.just_this_objectObje...
Defining methods              For an objecto = Object.newo.instance_eval("def just_this_object; end")o.just_this_objectObje...
Defining methods      For an object   o = Object.new   o.extend(Module.new {      def just_this_object      end   })
Defining methods               For a classMyClass = Class.newclass MyClass  def new_method  endendMyClass.new.respond_to?(:...
Defining methods                For a classMyClass = Class.newMyClass.class_eval "     MyClass.class_eval do  def new_metho...
Defining methods                      AliasingModule#alias_method
Scoping
Scopingmodule Project  class Main    def run      # ...    end  endend
Scoping   module Project     class Main       def run         # ...       end     end   end Class definitionsModule definiti...
Scopinga = hellomodule Project  class Main    def run      puts a    end  endendProject::Main.new.run
Scoping                       a = hello                       module Project                         class Main           ...
Scoping  module Project    class Main    end  end  a = hello  Project::Main.class_eval do    define_method(:run) do      p...
ScopingExample: Connection Sharingmodule AddConnections  def self.add_connection_methods(cls, host, port)    cls.class_eva...
ScopingExample: Connection Sharingmodule AddConnections  def self.add_connection_methods(cls, host, port)    cls.class_eva...
ScopingKernel#bindingLet’s you leak the current “bindings”
ScopingKernel#bindingLet’s you leak the current “bindings”                def create_connection(bind)                  eva...
ScopingKernel#bindingLet’s you leak the current “bindings”                def create_connection(bind)                  eva...
ScopingKernel#bindingLet’s you leak the current “bindings”                def create_connection(bind)                  eva...
ScopingKernel#bindingLet’s you leak the current “bindings”       def create_connection(bind)         eval            conne...
ScopingKernel#bindingLet’s you leak the current “bindings”       def create_connection(bind)         eval            conne...
ScopingKernel#bindingLet’s you leak the current “bindings”       def create_connection(bind)         eval            conne...
ScopingKernel#bindingTOPLEVEL_BINDING
ScopingKernel#bindingTOPLEVEL_BINDING       a = hello       module Program         class Main           def run           ...
Interception!(aka lazy magic)
Interception!method_missing(method, *args, &blk)
Interception!method_missing(method, *args, &blk)class MethodMissing  def method_missing(m, *args, &blk)    puts "method_mi...
Interception!method_missing(method, *args, &blk)class MethodMissing  def method_missing(m, *args, &blk)    puts "method_mi...
Interception!Example: Timingmodule MethodsWithTiming  def method_missing(m, *args, &blk)    if timed_method = m.to_s[/^(.*...
Interception!  Example: Timing   module MethodsWithTiming     def method_missing(m, *args, &blk)       if timed_method = m...
Interception!Example: Proxyclass Proxy  def initialize(backing)    @backing = backing  end  def method_missing(m, *args, &...
Interception!Example: Proxyclass LoggingProxy  def initialize(backing)    @backing = backing  end  def method_missing(m, *...
Interception!Example: Simple DSL class NameCollector   attr_reader :names   def initialize     @names = []   end   def met...
Interception!Object#respond_to?(sym)
Interception!   Object#respond_to?(sym)   Example: Timingmodule MethodsWithTiming  alias_method :original_respond_to?, :re...
Interception!   Object#respond_to?(sym)   Example: Timingmodule MethodsWithTiming                                       ge...
Interception!   Object#respond_to_missing?(sym) (1.9 only)   Example: Timingmodule MethodsWithTiming  def method_missing(m...
Interception!const_missing(sym)
Interception!const_missing(sym)MyClass::MyOtherClass# MyClass.const_missing(:MyOtherClass)
Interception!const_missing(sym)MyClass::MyOtherClass# MyClass.const_missing(:MyOtherClass)Example: Loaderclass Loader  def...
Interception!Example: Loaderclass Loader  def self.const_missing(sym)    file = File.join(File.dirname(__FILE__), "#{sym.t...
Interception!Example: Loaderclass Loader  def self.const_missing(sym)    file = File.join(File.dirname(__FILE__), "#{sym.t...
Callbacks
CallbacksModule#method_added
CallbacksModule#method_added
CallbacksModule#method_addedclass MyClass  def self.method_added(m)    puts "adding #{m}"  end  puts "defining my method" ...
CallbacksModule#method_addedclass MyClass                      defining my method  def self.method_added(m)         adding...
CallbacksModule#method_addedExample: Thor!class Tasks  def self.desc(desc)    @desc = desc  end  def self.method_added(m) ...
CallbacksModule#method_addedExample: Thor!                                               Record the descriptionclass Tasks...
CallbacksModule#method_addedExample: Thor!                                               Record the descriptionclass Tasks...
CallbacksModule#method_addedExample: Thor!                                               Query your methods!class Tasks   ...
CallbacksObject#singleton_method_added
CallbacksObject#singleton_method_addedclass ClassWithMethods  def self.singleton_method_added(m)    puts "ADDING! #{m}"  e...
CallbacksObject#singleton_method_addedclass ClassWithMethods  def self.singleton_method_added(m)    puts "ADDING! #{m}"  e...
CallbacksObject#singleton_method_addedclass ClassWithMethods  def self.singleton_method_added(m)    puts "ADDING! #{m}"  e...
CallbacksModule#includedmodule Logger  def self.included(m)    puts "adding logging to #{m}"  endendclass Server  include ...
CallbacksModule#includedExample: ClassMethods patternmodule Logger                       class Server  def self.included(m...
CallbacksModule#includedExample: ClassMethods patternmodule Logger                  class Server  def self.included(m)    ...
CallbacksModule#extendedmodule One  def self.extended(obj)    puts "#{self} has been extended by #{obj}"  endendObject.new...
CallbacksClass#inheritedclass Parent  def self.inherited(o)    puts "#{self} was inherited by #{o}"  endendclass Child < P...
CallbacksGuarding callbacksModule#append_features         includeModule#extend_object           extend def self.extend_obj...
CallbacksGuarding callbacksModule#append_features                     includeModule#extend_object                       ex...
CallbacksKernel#callerdef one  twoenddef two                                                   method name  three         ...
CallbacksModule#nestingmodule A  module B    module C      p Module.nesting    end  endend# [A::B::C, A::B, A]
There and back again, a     parsing tale
There and back again, a     parsing tale   gem install ruby_parser   gem install sexp_processor   gem install ruby2ruby   ...
There and back again, a               parsing taleParsing            require rubygems            require ruby_parser      ...
There and back again, a               parsing taleParsing            require rubygems            require ruby_parser      ...
There and back again, a                parsing taleParsing          RubyParser.new.process("string + string")s(:call, s(:s...
There and back again, a                parsing taleParsing          RubyParser.new.process("string + string")  s(:call, ni...
There and back again, a              parsing taleAnd, back again...      require rubygems      require ruby2ruby      Ruby...
There and back again, a              parsing taleAnd, back again...      require rubygems      require ruby2ruby      Ruby...
There and back again, a                parsing taleRoundtrip require sexp_processor require ruby2ruby require ruby_parser ...
There and back again, a               parsing taleRoundtrip class JarJarify < SexpProcessor   def initialize     self.stri...
There and back again, a               parsing taleRoundtrip class JarJarify < SexpProcessor   def initialize     self.stri...
IT’S OVER!
Metaprogramming in Ruby
Metaprogramming in Ruby
Upcoming SlideShare
Loading in...5
×

Metaprogramming in Ruby

2,326

Published on

Published in: Technology
0 Comments
7 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,326
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
67
Comments
0
Likes
7
Embeds 0
No embeds

No notes for slide

Transcript of "Metaprogramming in Ruby"

  1. 1. Metaprogramming in Ruby
  2. 2. Things I Wish I KnewWhen I Started Ruby
  3. 3. Who?Joshua Hull@jjhttps://github.com/joshbuddy
  4. 4. What?
  5. 5. What?Writing programs that write programs.
  6. 6. What?Writing programs that write programs. NOT code generation!
  7. 7. Why?We all do it.
  8. 8. Why? We all do it.Ruby attr_accessor :my_attribute
  9. 9. Why? We all do it.Ruby attr_accessor :my_attribute def my_attribute @my_attribute end def my_attribute=(my_attribute) @my_attribute = my_attribute end
  10. 10. Why? We all do it.Ruby attr_accessor :my_attributeJava public int getAttribute() { return attribute; } public void setAttribute(int newAttribute) { attribute = newAttribute; }
  11. 11. Why? We all do it. ner in RubyW attr_accessor :my_attribute Java public int getAttribute() { return attribute; } public void setAttribute(int newAttribute) { attribute = newAttribute; }
  12. 12. Why?def age @age || not setenddef gender @gender || not setenddef name @name || not setend
  13. 13. Why? def age @age || not set end def gender @gender || not set end def name @name || not set end[:age, :gender, :name].each do |attr| define_method(attr) do instance_variable_get(:"@#{attr}") || not set endend
  14. 14. Drawbacks
  15. 15. DrawbacksYou can write some difficult-to-understand code.
  16. 16. DrawbacksYou can write some difficult-to-understand code.
  17. 17. Method dispatch
  18. 18. Method dispatch class MyObject attr_accessor :name def say_hello puts "hello" puts name end end
  19. 19. Method dispatch class MyObject attr_accessor :name def say_hello puts "hello" puts name end end This is a method call, but who receives it?
  20. 20. Method dispatch class MyObject attr_accessor :name def say_hello puts "hello" puts self.name end end
  21. 21. Method dispatch class MyObject attr_accessor :name def say_hello puts "hello" puts self.name end end self is always the implied receiver!
  22. 22. Method dispatch class MyObject attr_accessor :name def say_hello puts "hello" puts name end end
  23. 23. Method dispatch class MyObject self.attr_accessor :name def say_hello puts "hello" puts name end end
  24. 24. Method dispatch class MyObjectWho is self self.attr_accessor :name here? def say_hello puts "hello" puts name end end
  25. 25. Method dispatch class MyObjectWho is self self.attr_accessor :name here? def say_hello puts "hello"The class puts name end is! end
  26. 26. Method dispatch Module#attrWho is self here?The class is!
  27. 27. Ruby classes
  28. 28. Ruby classesclass NewClass def hey puts hello! endend
  29. 29. Ruby classes class NewClass { def hey puts hello! end endThis is normal code!
  30. 30. Ruby classesclass NewClass def hey puts hello! endend is the same asNewClass = Class.new do def hey puts hello! endend
  31. 31. Ruby classesclass ParsingError < RuntimeErrorend
  32. 32. Ruby classesclass ParsingError < RuntimeErrorend is the same asParsingError = Class.new(RuntimeError)
  33. 33. Ruby classesdef class_with_accessors(*attributes) Class.new do attr_accessor *attributes endend
  34. 34. Ruby classesdef class_with_accessors(*attributes) Class.new do attr_accessor *attributes endend Returns a new class!
  35. 35. Ruby classesdef class_with_accessors(*attributes) Class.new do attr_accessor *attributes endend Returns a new class!class Person < class_with_accessors(:name, :age, :sex) # ...end
  36. 36. eval, instance_eval, class_eval
  37. 37. eval, instance_eval, class_evalMethodevalinstance_evalclass_eval
  38. 38. eval, instance_eval, class_evalMethod Contexteval your current contextinstance_eval the objectclass_eval the object’s class
  39. 39. eval, instance_eval, class_evalevaleval "puts hello"# hello
  40. 40. eval, instance_eval, class_evalinstance_evalclass MyClassendMyClass.instance_eval("def hi; hi; end")
  41. 41. eval, instance_eval, class_evalinstance_evalclass MyClassendMyClass.instance_eval("def hi; hi; end")MyClass.hi# hi
  42. 42. eval, instance_eval, class_evalinstance_evalclass MyClassendMyClass.instance_eval("def hi; hi end")obj = MyClass.new# <MyClass:0x10178aff8>obj.hi# NoMethodError: undefined method `hi for #<MyClass>
  43. 43. eval, instance_eval, class_evalclass_evalclass MyClassendMyClass.class_eval("def hi; hi end")
  44. 44. eval, instance_eval, class_evalclass_evalclass MyClassendMyClass.class_eval("def hi; hi end")MyClass.hi# NoMethodError: undefined method `hi for MyClass:Class
  45. 45. eval, instance_eval, class_evalclass_evalclass MyClassendMyClass.class_eval("def hi; hi end")obj = MyClass.new# <MyClass:0x101849688>obj.hi# "hi"
  46. 46. eval, instance_eval, class_evalclass_eval instance_evalclass MyClass class MyClassend endMyClass.class_eval("def hi; hi; end") MyClass.instance_eval("def hi; hi; end")obj = MyClass.new MyClass.hi# <MyClass:0x101849688> # hiobj.hi# "hi"
  47. 47. eval, instance_eval, class_evalNinja’s will attack you if ...you don’t use __FILE__, __LINE__
  48. 48. eval, instance_eval, class_evalclass HiThereendHiThere.class_eval "def hi; raise; end"HiThere.class_eval "def hi_with_niceness; raise; end", __FILE__, __LINE__HiThere.new.hiHiThere.new.hi_with_niceness
  49. 49. eval, instance_eval, class_evalclass HiThereendHiThere.class_eval "def hi; raise; end"HiThere.class_eval "def hi_with_niceness; raise; end", __FILE__, __LINE__HiThere.new.hiHiThere.new.hi_with_niceness(eval):1:in `hi: unhandled exception from my_file.rb:7my_file.rb:5:in `hi_with_niceness: unhandled exception from my_file.rb:7
  50. 50. eval, instance_eval, class_evalclass HiThereendHiThere.class_eval "def hi; raise; end"HiThere.class_eval "def hi_with_niceness; raise; end", __FILE__, __LINE__HiThere.new.hiHiThere.new.hi_with_niceness(eval):1:in `hi: unhandled exception from my_file.rb:7 So nice. <3my_file.rb:5:in `hi_with_niceness: unhandled exception from my_file.rb:7
  51. 51. eval, instance_eval, class_eval Implement attr_accessor!
  52. 52. eval, instance_eval, class_evalclass Module def create_attr(attribute) class_eval("def #{attribute}; @#{attribute}; end") endend
  53. 53. eval, instance_eval, class_evalclass Module def create_attr(attribute) class_eval("def #{attribute}; @#{attribute}; end") endendclass M create_attr :hiend
  54. 54. eval, instance_eval, class_evalclass Module def create_attr(attribute) class_eval("def #{attribute}; @#{attribute}; end") endend def Mclass M def hi create_attr :hi @hiend end end
  55. 55. Defining methods
  56. 56. Defining methods For an objecto = Object.newo.instance_eval("def just_this_object; end")o.just_this_object
  57. 57. Defining methods For an objecto = Object.newo.instance_eval("def just_this_object; end")o.just_this_objectObject.new.just_this_object# NoMethodError: undefined method `just_this_object
  58. 58. Defining methods For an objecto = Object.newo.instance_eval("def just_this_object; end")o.just_this_objectObject.new.just_this_object# NoMethodError: undefined method `just_this_objecto = Object.newo.instance_eval { def just_this_object end}
  59. 59. Defining methods For an object o = Object.new o.extend(Module.new { def just_this_object end })
  60. 60. Defining methods For a classMyClass = Class.newclass MyClass def new_method endendMyClass.new.respond_to?(:new_method) # true
  61. 61. Defining methods For a classMyClass = Class.newMyClass.class_eval " MyClass.class_eval do def new_method def new_method end end" endMyClass.send(:define_method, :new_method) { # your method body}
  62. 62. Defining methods AliasingModule#alias_method
  63. 63. Scoping
  64. 64. Scopingmodule Project class Main def run # ... end endend
  65. 65. Scoping module Project class Main def run # ... end end end Class definitionsModule definitionsMethod definitions
  66. 66. Scopinga = hellomodule Project class Main def run puts a end endendProject::Main.new.run
  67. 67. Scoping a = hello module Project class Main def run puts a end end end Project::Main.new.run# undefined local variable or method `a for #<Project::Main> (NameError)
  68. 68. Scoping module Project class Main end end a = hello Project::Main.class_eval do define_method(:run) do puts a end endProject::Main.new.run # => hello
  69. 69. ScopingExample: Connection Sharingmodule AddConnections def self.add_connection_methods(cls, host, port) cls.class_eval do define_method(:get_connection) do puts "Getting connection for #{host}:#{port}" end define_method(:host) { host } define_method(:port) { port } end endend
  70. 70. ScopingExample: Connection Sharingmodule AddConnections def self.add_connection_methods(cls, host, port) cls.class_eval do define_method(:get_connection) do puts "Getting connection for #{host}:#{port}" end define_method(:host) { host } define_method(:port) { port } end endendClient = Class.newAddConnections.add_connection_methods(Client, localhost, 8080)Client.new.get_connection # Getting connection for localhost:8080Client.new.host # localhostClient.new.port # 8080
  71. 71. ScopingKernel#bindingLet’s you leak the current “bindings”
  72. 72. ScopingKernel#bindingLet’s you leak the current “bindings” def create_connection(bind) eval connection = "I am a connection" , bind end connection = nil create_connection(binding) connection # => I am a connection
  73. 73. ScopingKernel#bindingLet’s you leak the current “bindings” def create_connection(bind) eval connection = "I am a connection" , bind end Calls connection = nilwith the create_connection(binding)current connection # => I am a connection state
  74. 74. ScopingKernel#bindingLet’s you leak the current “bindings” def create_connection(bind) eval connection = "I am a connection" , bind end Calls connection = nilwith the create_connection(binding)current connection # => I am a connection state MAGIC!
  75. 75. ScopingKernel#bindingLet’s you leak the current “bindings” def create_connection(bind) eval connection = "I am a connection" , bind end # connection = nil create_connection(binding) connection # undefined local variable or method `connection
  76. 76. ScopingKernel#bindingLet’s you leak the current “bindings” def create_connection(bind) eval connection = "I am a connection" , bind end # connection = nil create_connection(binding) connection # undefined local variable or method `connection You can’t add to the local variables via binding
  77. 77. ScopingKernel#bindingLet’s you leak the current “bindings” def create_connection(bind) eval connection = "I am a connection" , bind end eval "connection = nil" create_connection(binding) connection # undefined local variable or method `connection You can’t add to the local variables via eval
  78. 78. ScopingKernel#bindingTOPLEVEL_BINDING
  79. 79. ScopingKernel#bindingTOPLEVEL_BINDING a = hello module Program class Main def run puts eval("a", TOPLEVEL_BINDING) end end end Program::Main.new.run # => hello
  80. 80. Interception!(aka lazy magic)
  81. 81. Interception!method_missing(method, *args, &blk)
  82. 82. Interception!method_missing(method, *args, &blk)class MethodMissing def method_missing(m, *args, &blk) puts "method_missing #{m} #{args.inspect} #{blk.inspect}" super endend
  83. 83. Interception!method_missing(method, *args, &blk)class MethodMissing def method_missing(m, *args, &blk) puts "method_missing #{m} #{args.inspect} #{blk.inspect}" super endendmm = MethodMissing.newmm.i_dont_know_this(1, 2, 3)# method_missing i_dont_know_this [1, 2, 3] nil# NoMethodError: undefined method `i_dont_know_this for #<MethodMissing>
  84. 84. Interception!Example: Timingmodule MethodsWithTiming def method_missing(m, *args, &blk) if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and respond_to?(timed_method) respond = nil measurement = Benchmark.measure { respond = send(timed_method, *args, &blk) } puts "Method #{m} took #{measurement}" respond else super end endend
  85. 85. Interception! Example: Timing module MethodsWithTiming def method_missing(m, *args, &blk) if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and respond_to?(timed_method) respond = nil measurement = Benchmark.measure { respond = send(timed_method, *args, &blk) } puts "Method #{m} took #{measurement}" respond else super end end end sc = SlowClass.newclass SlowClass sc.slow include MethodsWithTiming def slow sleep 1 sc.slow_with_timing end # Method slow_with_timing took 0.000000 0.000000 0.000000 ( 1.000088)end
  86. 86. Interception!Example: Proxyclass Proxy def initialize(backing) @backing = backing end def method_missing(m, *args, &blk) @backing.send(m, *args, &blk) endend
  87. 87. Interception!Example: Proxyclass LoggingProxy def initialize(backing) @backing = backing end def method_missing(m, *args, &blk) puts "Calling method #{m} with #{args.inspect}" @backing.send(m, *args, &blk) endend
  88. 88. Interception!Example: Simple DSL class NameCollector attr_reader :names def initialize @names = [] end def method_missing(method, *args, &blk) args.empty? ? @names.push(method.to_s.capitalize) : super end end nc = NameCollector.new nc.josh nc.bob nc.jane nc.names.join( ) # => Josh Bob Jane
  89. 89. Interception!Object#respond_to?(sym)
  90. 90. Interception! Object#respond_to?(sym) Example: Timingmodule MethodsWithTiming alias_method :original_respond_to?, :respond_to? def method_missing(m, *args, &blk) if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and original_respond_to?(timed_method) respond = nil measurement = Benchmark.measure { respond = send(timed_method, *args, &blk) } puts "Method #{m} took #{measurement}" respond else super end end def respond_to?(sym) (timed_method = sym.to_s[/^(.*)_with_timing$/, 1]) ? original_respond_to?(timed_method.to_sym) : original_respond_to?(sym) endend
  91. 91. Interception! Object#respond_to?(sym) Example: Timingmodule MethodsWithTiming ge ts alias_method :original_respond_to?, :respond_to? It def method_missing(m, *args, &blk) r! if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and original_respond_to?(timed_method) te respond = nil et measurement = Benchmark.measure { b respond = send(timed_method, *args, &blk) } puts "Method #{m} took #{measurement}" respond else super end end def respond_to?(sym) (timed_method = sym.to_s[/^(.*)_with_timing$/, 1]) ? original_respond_to?(timed_method.to_sym) : original_respond_to?(sym) endend
  92. 92. Interception! Object#respond_to_missing?(sym) (1.9 only) Example: Timingmodule MethodsWithTiming def method_missing(m, *args, &blk) if timed_method = m.to_s[/^(.*)_with_timing$/, 1] and respond_to?(timed_method) respond = nil measurement = Benchmark.measure { respond = send(timed_method, *args, &blk) } puts "Method #{m} took #{measurement}" respond else super end end def respond_to_missing?(sym) (timed_method = sym.to_s[/^(.*)_with_timing$/, 1]) ? respond_to?(timed_method.to_sym) : super endend
  93. 93. Interception!const_missing(sym)
  94. 94. Interception!const_missing(sym)MyClass::MyOtherClass# MyClass.const_missing(:MyOtherClass)
  95. 95. Interception!const_missing(sym)MyClass::MyOtherClass# MyClass.const_missing(:MyOtherClass)Example: Loaderclass Loader def self.const_missing(sym) file = File.join(File.dirname(__FILE__), "#{sym.to_s.downcase}.rb") if File.exist?(file) require file Object.const_defined?(sym) ? Object.const_get(sym) : super else puts "cant find #{file}, sorry!" super end endend
  96. 96. Interception!Example: Loaderclass Loader def self.const_missing(sym) file = File.join(File.dirname(__FILE__), "#{sym.to_s.downcase}.rb") if File.exist?(file) require file Object.const_defined?(sym) ? Object.const_get(sym) : super else puts "cant find #{file}, sorry!" super end endend
  97. 97. Interception!Example: Loaderclass Loader def self.const_missing(sym) file = File.join(File.dirname(__FILE__), "#{sym.to_s.downcase}.rb") if File.exist?(file) require file Object.const_defined?(sym) ? Object.const_get(sym) : super else puts "cant find #{file}, sorry!" super end endendLoader::Auto# cant find ./auto.rb, sorry!# NameError: uninitialized constant Loader::Auto# or, if you have an ./auto.rbLoader::Auto# => Auto
  98. 98. Callbacks
  99. 99. CallbacksModule#method_added
  100. 100. CallbacksModule#method_added
  101. 101. CallbacksModule#method_addedclass MyClass def self.method_added(m) puts "adding #{m}" end puts "defining my method" def my_method two end puts "done defining my method"end
  102. 102. CallbacksModule#method_addedclass MyClass defining my method def self.method_added(m) adding my_method puts "adding #{m}" done defining my method end puts "defining my method" def my_method two end puts "done defining my method"end
  103. 103. CallbacksModule#method_addedExample: Thor!class Tasks def self.desc(desc) @desc = desc end def self.method_added(m) (@method_descs ||= {})[m] = @desc @desc = nil end def self.method_description(m) method_defined?(m) ? @method_descs[m] || "This action isnt documented" : "This action doesnt exist" end desc "Start server" def start end def stop endend
  104. 104. CallbacksModule#method_addedExample: Thor! Record the descriptionclass Tasks def self.desc(desc) @desc = desc When a method is added, end record the description associated def self.method_added(m) with that method (@method_descs ||= {})[m] = @desc @desc = nil end Provide the description for a def self.method_description(m) method, or, if not found, some method_defined?(m) ? default string. @method_descs[m] || "This action isnt documented" : "This action doesnt exist" end desc "Start server" def start end def stop endend
  105. 105. CallbacksModule#method_addedExample: Thor! Record the descriptionclass Tasks def self.desc(desc) @desc = desc When a method is added, end record the description associated def self.method_added(m) with that method (@method_descs ||= {})[m] = @desc @desc = nil end Provide the description for a def self.method_description(m) method, or, if not found, some method_defined?(m) ? default string. @method_descs[m] || "This action isnt documented" : "This action doesnt exist" end desc "Start server" def start end Described! def stop endend
  106. 106. CallbacksModule#method_addedExample: Thor! Query your methods!class Tasks puts Tasks.method_description(:start) def self.desc(desc) # => Start server @desc = desc puts Tasks.method_description(:stop) end # => This action isnt documented def self.method_added(m) puts Tasks.method_description(:restart) (@method_descs ||= {})[m] = @desc # => This action doesnt exist @desc = nil end def self.method_description(m) method_defined?(m) ? @method_descs[m] || "This action isnt documented" : "This action doesnt exist" end desc "Start server" def start end def stop endend
  107. 107. CallbacksObject#singleton_method_added
  108. 108. CallbacksObject#singleton_method_addedclass ClassWithMethods def self.singleton_method_added(m) puts "ADDING! #{m}" end def self.another endend
  109. 109. CallbacksObject#singleton_method_addedclass ClassWithMethods def self.singleton_method_added(m) puts "ADDING! #{m}" end def self.another endend# ADDING! singleton_method_added# ADDING! another
  110. 110. CallbacksObject#singleton_method_addedclass ClassWithMethods def self.singleton_method_added(m) puts "ADDING! #{m}" end def self.another endend Holy meta!# ADDING! singleton_method_added# ADDING! another
  111. 111. CallbacksModule#includedmodule Logger def self.included(m) puts "adding logging to #{m}" endendclass Server include Loggerend# adding logging to Server
  112. 112. CallbacksModule#includedExample: ClassMethods patternmodule Logger class Server def self.included(m) include Logger puts "adding logging to #{m}" end def self.create log("Creating server!") def self.log(message) end puts "LOG: #{message}" end endendServer.create# `create: undefined method `log for Server:Class (NoMethodError)
  113. 113. CallbacksModule#includedExample: ClassMethods patternmodule Logger class Server def self.included(m) include Logger m.extend(ClassMethods) end def self.create log("Creating server!") module ClassMethods end def log(message) end puts "LOG: #{message}" end endendServer.create# LOG: Creating server!
  114. 114. CallbacksModule#extendedmodule One def self.extended(obj) puts "#{self} has been extended by #{obj}" endendObject.new.extend(One)# One has been extended by #<Object:0x1019614a8>
  115. 115. CallbacksClass#inheritedclass Parent def self.inherited(o) puts "#{self} was inherited by #{o}" endendclass Child < Parentend# Parent was inherited by Child
  116. 116. CallbacksGuarding callbacksModule#append_features includeModule#extend_object extend def self.extend_object(o) super end def self.append_features(o) super end
  117. 117. CallbacksGuarding callbacksModule#append_features includeModule#extend_object extenddef self.append_features(o) o.instance_method(:<=>) ? super : warn(you no can uze)end
  118. 118. CallbacksKernel#callerdef one twoenddef two method name three file name line (optional)enddef three p callerend# ["method.rb:156:in `two", "method.rb:152:in `one", "method.rb:163"] https://github.com/joshbuddy/callsite
  119. 119. CallbacksModule#nestingmodule A module B module C p Module.nesting end endend# [A::B::C, A::B, A]
  120. 120. There and back again, a parsing tale
  121. 121. There and back again, a parsing tale gem install ruby_parser gem install sexp_processor gem install ruby2ruby Let’s go!
  122. 122. There and back again, a parsing taleParsing require rubygems require ruby_parser RubyParser.new.process("string") s(:str, "string") Type Arguments...
  123. 123. There and back again, a parsing taleParsing require rubygems require ruby_parser RubyParser.new.process("string") s(:str, "string") [:str, "string"] # Sexp Sexp.superclass # Array
  124. 124. There and back again, a parsing taleParsing RubyParser.new.process("string + string")s(:call, s(:str, "string"), :+, s(:arglist, s(:str, "string"))) Method Method Receiver Arguments call name
  125. 125. There and back again, a parsing taleParsing RubyParser.new.process("string + string") s(:call, nil, :puts, s(:arglist, s(:str, "hello world"))) Method Receiver Method Arguments call name
  126. 126. There and back again, a parsing taleAnd, back again... require rubygems require ruby2ruby Ruby2Ruby.new.process [:str, "hello"] # => "hello"
  127. 127. There and back again, a parsing taleAnd, back again... require rubygems require ruby2ruby Ruby2Ruby.new.process [:str, "hello"] # => "hello" Ruby2Ruby.new.process [:lit, :symbol] # => :symbol
  128. 128. There and back again, a parsing taleRoundtrip require sexp_processor require ruby2ruby require ruby_parser class JarJarify < SexpProcessor def initialize self.strict = false super end def process_str(str) new_string = "YOUZA GONNA SAY #{str[-1]}" str.clear s(:str, new_string) end end
  129. 129. There and back again, a parsing taleRoundtrip class JarJarify < SexpProcessor def initialize self.strict = false super end def process_str(str) new_string = "YOUZA GONNA SAY #{str[-1]}" str.clear s(:str, new_string) end end ast = RubyParser.new.process(puts "hello") Ruby2Ruby.new.process(JarJarify.new.process(ast)) # => puts("YOUZA GONNA SAY hello")
  130. 130. There and back again, a parsing taleRoundtrip class JarJarify < SexpProcessor def initialize self.strict = false Process type :str super end def process_str(str) new_string = "YOUZA GONNA SAY #{str[-1]}" str.clear s(:str, new_string) Consume the current sexp end end Return a new one ast = RubyParser.new.process(puts "hello") Ruby2Ruby.new.process(JarJarify.new.process(ast)) # => puts("YOUZA GONNA SAY hello")
  131. 131. IT’S OVER!
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×