Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Metaprogramming Ruby

Metaprogramming Ruby

Overview/intro to Ruby object model and metaprogramming

Matt Yoho

July 24, 2012
Tweet

More Decks by Matt Yoho

Other Decks in Programming

Transcript

  1. Building abstractions to simplify Create nifty Domain Specific Languages, such

    as ActiveRecord associations. Tuesday, July 24, 12
  2. For example, has_many is an abstraction around n-to-many relationships and

    all the associated duct work. Building abstractions to simplify Tuesday, July 24, 12
  3. class Feed < ActiveRecord::Base has_many :items belongs_to :user validates_presence_of :user_id

    before_create do |feed| feed.adopt_user_display_name end end Building abstractions to simplify Tuesday, July 24, 12
  4. Ruby Object Model •classes •objects •class objects •instance methods •class

    methods •singleton methods •singleton classes •virtual classes? Tuesday, July 24, 12
  5. Ruby Object Model •classes •objects •class objects •instance methods •class

    methods •singleton methods •singleton classes •virtual classes? OMG WTF BBQ Tuesday, July 24, 12
  6. Ruby Object Model classes objects superclass class methods: Object +

    to_s + nil? + respond_to? object_id: 703 obj obj = Object.new() Tuesday, July 24, 12
  7. Where am I? Who am I? class Foo self #

    self is Foo def self.foo #self is Foo return self end def bar # self is a Foo instance return self end end Tuesday, July 24, 12
  8. Explicit vs. implicit receiver class Foo def foo "foo" end

    def bar return self.foo end def baz return bar.upcase end end f = Foo.new f.baz Tuesday, July 24, 12
  9. Define instance methods superclass class methods: Person + say_name +

    say_age class Person attr_attribute :name, :age def initialize(name, age) @name, @age = name, age end def say_name "Hi, my name is #{name}." end def say_age "I'm #{@age} years old." end end Class Definition Tuesday, July 24, 12
  10. A class’s class is Class. superclass class methods: Person +

    say_name + say_age class Person attr_attribute :name, :age def initialize(name, age) @name, @age = name, age end def say_name "Hi, my name is #{name}." end def say_age "I'm #{@age} years old." end end Class Definition superclass class methods: Class + name + ancestors - include Tuesday, July 24, 12
  11. A class’s class is Class. superclass class methods: Person +

    say_name + say_age class Person attr_attribute :name, :age def initialize(name, age) @name, @age = name, age end def say_name "Hi, my name is #{name}." end def say_age "I'm #{@age} years old." end end Class Definition superclass class methods: Class + name + ancestors - include (Trippy.) Tuesday, July 24, 12
  12. Also, Class’s class is Class. superclass class methods: Person +

    say_name + say_age class Person attr_attribute :name, :age def initialize(name, age) @name, @age = name, age end def say_name "Hi, my name is #{name}." end def say_age "I'm #{@age} years old." end end Class Definition superclass class methods: Class + name + ancestors - include Tuesday, July 24, 12
  13. Also, Class’s class is Class. superclass class methods: Person +

    say_name + say_age class Person attr_attribute :name, :age def initialize(name, age) @name, @age = name, age end def say_name "Hi, my name is #{name}." end def say_age "I'm #{@age} years old." end end Class Definition superclass class methods: Class + name + ancestors - include (Ruby is an asshole.) Tuesday, July 24, 12
  14. superclass class methods: Person + say_name + say_age jeff =

    Person.new('Jeff', 31) jeff.say_age #=> "I'm 31 years old." jeff.say_name #=> "Hi, my name is Jeff." Class Definition name: “Jeff” age: 31 jeff Tuesday, July 24, 12
  15. superclass class methods: Person + say_name + say_age jeff =

    Person.new('Jeff', 31) jeff.say_age #=> "I'm 31 years old." jeff.say_name #=> "Hi, my name is Jeff." jeff.to_s #=> "#<Person:0x007ff52a14df80>" Class Definition name: “Jeff” age: 31 jeff Tuesday, July 24, 12
  16. superclass class methods: Person + say_name + say_age name: “Jeff”

    age: 31 jeff Where did that come from? jeff = Person.new('Jeff', 31) jeff.say_age #=> "I'm 31 years old." jeff.say_name #=> "Hi, my name is Jeff." jeff.to_s #=> "#<Person:0x007ff52a14df80>" Class Definition Tuesday, July 24, 12
  17. superclass class methods: Person + say_name + say_age name: “Jeff”

    age: 31 jeff superclass class methods: Object + clone + to_s ... Instance Method Lookup jeff.to_s Tuesday, July 24, 12
  18. superclass class methods: Person + say_name + say_age name: “Jeff”

    age: 31 jeff superclass class methods: Object + clone + to_s ... Instance Method Lookup jeff.to_s Nope! Tuesday, July 24, 12
  19. superclass class methods: Person + say_name + say_age name: “Jeff”

    age: 31 jeff superclass class methods: Object + clone + to_s ... Instance Method Lookup jeff.to_s Yep! Tuesday, July 24, 12
  20. superclass class methods: Person + say_name + say_age name: “Jeff”

    age: 31 jeff superclass class methods: Object + clone + to_s ... Instance Method Lookup jeff.to_s #=> "#<Person:0x007ff52a14df80>" Yep! Tuesday, July 24, 12
  21. Override the #to_s method superclass class methods: Instructor + to_s

    class Instructor < Person def to_s "Instructor #{name}." end end jeff = Instructor.new('Jeff', 31) jeff.to_s #=> "Instructor Jeff" Tuesday, July 24, 12
  22. name: “Jeff” age: 31 jeff Instance Method Lookup jeff =

    Instructor.new('Jeff', 31) jeff.to_s superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s Tuesday, July 24, 12
  23. name: “Jeff” age: 31 jeff Instance Method Lookup jeff =

    Instructor.new('Jeff', 31) jeff.to_s #=> "Instructor Jeff" superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s Yep! Tuesday, July 24, 12
  24. name: “Jeff” age: 31 jeff Instance Method Lookup jeff =

    Instructor.new('Jeff', 31) jeff.say_name superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s Tuesday, July 24, 12
  25. name: “Jeff” age: 31 jeff Instance Method Lookup jeff =

    Instructor.new('Jeff', 31) jeff.say_name superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s Nope! Tuesday, July 24, 12
  26. name: “Jeff” age: 31 jeff Instance Method Lookup jeff =

    Instructor.new('Jeff', 31) jeff.say_name #=> "Hi, my name is Jeff." superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s Yep! Tuesday, July 24, 12
  27. Define a module superclass class methods: Rubyist + write_code module

    Rubyist def write_code "Ruby!" end end class Person include Rubyist end Module Instance Methods Tuesday, July 24, 12
  28. A module’s class is Module superclass class methods: Rubyist +

    write_code module Rubyist def write_code "Ruby!" end end class Person include Rubyist end Module Instance Methods superclass class methods: Module + name + ancestors - include Tuesday, July 24, 12
  29. name: “chad” age: 33 chad Module Method Lookup class Person

    include Rubyist end chad = Person.new chad.write_code superclass class methods: Person + say_name superclass class methods: Object ... Tuesday, July 24, 12
  30. name: “chad” age: 33 chad Module Method Lookup class Person

    include Rubyist end chad = Person.new chad.write_code superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Rubyist + write_code Including a module inserts it into the classe’s superclass chain. Tuesday, July 24, 12
  31. name: “chad” age: 33 chad Module Method Lookup class Person

    include Rubyist end chad = Person.new chad.write_code superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Rubyist + write_code Nope! Tuesday, July 24, 12
  32. name: “chad” age: 33 chad Module Method Lookup class Person

    include Rubyist end chad = Person.new chad.write_code #=> "Ruby!" superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Rubyist + write_code Yep! Tuesday, July 24, 12
  33. name: “chad” age: 33 chad Module Method Lookup class Person

    include Rubyist include Lisper end chad = Person.new chad.write_code superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Rubyist + write_code superclass class methods: Lisper + write_code Tuesday, July 24, 12
  34. name: “chad” age: 33 chad Module Method Lookup class Person

    include Rubyist include Lisper end chad = Person.new chad.write_code superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Rubyist + write_code Tuesday, July 24, 12
  35. name: “chad” age: 33 chad Module Method Lookup class Person

    include Rubyist include Lisper end chad = Person.new chad.write_code superclass class methods: Person + say_name superclass class methods: Rubyist + write_code superclass class methods: Lisper + write_code Tuesday, July 24, 12
  36. name: “chad” age: 33 chad Module Method Lookup class Person

    include Rubyist include Lisper end chad = Person.new chad.write_code superclass class methods: Person + say_name superclass class methods: Rubyist + write_code superclass class methods: Lisper + write_code Nope! Tuesday, July 24, 12
  37. name: “chad” age: 33 chad Module Method Lookup class Person

    include Rubyist include Lisper end chad = Person.new chad.write_code #=> "Lithp!" superclass class methods: Person + say_name superclass class methods: Rubyist + write_code superclass class methods: Lisper + write_code Yep! Tuesday, July 24, 12
  38. name: “chad” age: 33 chad Precedence superclass class methods: Person

    + write_code superclass class methods: Object ... class Person def write_code "Type, type, type." end end chad = Person.new chad.write_code #=> "Type, type, type." Tuesday, July 24, 12
  39. name: “chad” age: 33 chad superclass class methods: Person +

    write_code superclass class methods: Object ... superclass class methods: Rubyist + write_code class Person def write_code "Type, type, type." end end Person.send(:include, Rubyist) chad = Person.new chad.write_code #=> Which? Precedence Tuesday, July 24, 12
  40. name: “chad” age: 33 chad superclass class methods: Person +

    write_code superclass class methods: Object ... superclass class methods: Rubyist + write_code class Person def write_code "Type, type, type." end end Person.send(:include, Rubyist) chad = Person.new chad.write_code #=> "Type, type, type." Precedence Yep! Tuesday, July 24, 12
  41. How can one ensure a class provides a method but

    allow it to be overridden by an included module? Tuesday, July 24, 12
  42. name: “chad” age: 33 chad class Person module Coder def

    write_code "Type, type, type." end end include Person::Coder end chad = Person.new chad.write_code superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: P::Coder + write_code Precedence Tuesday, July 24, 12
  43. name: “chad” age: 33 chad class Person module Coder def

    write_code "Type, type, type." end end include Person::Coder end Person.send(:include, Rubyist) chad = Person.new chad.write_code superclass class methods: Person + say_name Precedence superclass class methods: Rubyist + write_code superclass class methods: P::Coder + write_code Tuesday, July 24, 12
  44. Override a method on matt matt = Person.new('Matt', 29) def

    matt.say_name "I'm Batman." end matt.say_age #=> "I’m 29 years old." matt.say_name #=> "I'm Batman." name: “Matt” age: 29 matt Singleton Methods Tuesday, July 24, 12
  45. superclass class methods: Person + say_name name: “Matt” age: 29

    matt superclass class methods: Object ... Singleton Classes def matt.say_name "I'm Batman." end matt.say_name Tuesday, July 24, 12
  46. superclass class methods: Person + say_name name: “Matt” age: 29

    matt superclass class methods: Object ... Singleton Classes def matt.say_name "I'm Batman." end matt.say_name superclass class methods: Person’ + say_name Tuesday, July 24, 12
  47. superclass class methods: Person + say_name name: “Matt” age: 29

    matt superclass class methods: Object ... Singleton Classes def matt.say_name "I'm Batman." end matt.say_name #=> "I'm Batman." superclass class methods: Person’ + say_name Yep! Tuesday, July 24, 12
  48. Accessing the singleton class class Foo class << self #

    self is Class' for Foo end end f = Foo.new class << f # self is Foo' for f # and is equal to f.singleton_class end class << foo Tuesday, July 24, 12
  49. Class Methods class Person def self.fess_up "There are no class

    methods." end end Person.fess_up #=> "There are no class methods." superclass class methods: Person + say_name + say_age Tuesday, July 24, 12
  50. superclass class methods: Object ... superclass class methods: Object ...

    superclass class methods: Class ... superclass class methods: Person + say_name Class Methods class Person def self.fess_up "There are no class methods." end end Person.fess_up #=> "There are no class methods." Tuesday, July 24, 12
  51. superclass class methods: Object ... superclass class methods: Object ...

    superclass class methods: Class ... superclass class methods: Class’ + fess_up superclass class methods: Person + say_name Class Methods class Person def self.fess_up "There are no class methods." end end Person.fess_up #=> "There are no class methods." “Class” methods are methods defined on the class’s singleton class. name: “chad” age: 33 chad Tuesday, July 24, 12
  52. name: “chad” age: 33 chad Instance extends Module superclass class

    methods: Person + say_name superclass class methods: Object ... class Person end module Rubyist def write_code "Ruby!" end end chad = Person.new chad.extend Rubyist chad.write_code Tuesday, July 24, 12
  53. name: “chad” age: 33 chad superclass class methods: Person +

    say_name superclass class methods: Object ... superclass class methods: Rubyist + write_code Extending a module inserts it into your class’s superclass chain. Instance extends Module class Person end module Rubyist def write_code "Ruby!" end end chad = Person.new chad.extend Rubyist chad.write_code Tuesday, July 24, 12
  54. superclass class methods: Object ... superclass class methods: Object ...

    superclass class methods: Class ... superclass class methods: Person + say_name Class extends Module module Rubyist def write_code "Ruby!" end end class Person extend Rubyist end Person.write_code Tuesday, July 24, 12
  55. superclass class methods: Object ... superclass class methods: Object ...

    superclass class methods: Class ... superclass class methods: Person + say_name Class extends Module module Rubyist def write_code "Ruby!" end end class Person extend Rubyist end Person.write_code superclass class methods: Class’ ... Tuesday, July 24, 12
  56. superclass class methods: Object ... superclass class methods: Class ...

    superclass class methods: Person + say_name Class extends Module module Rubyist def write_code "Ruby!" end end class Person extend Rubyist end Person.write_code superclass class methods: Class’ ... superclass class methods: Rubyist + write_code Tuesday, July 24, 12
  57. To extend a module is equivalent to an include in

    your singleton class. Tuesday, July 24, 12
  58. class Shape attr_reader :dimensions def initialize(dimensions={}) @dimensions = dimensions end

    [:width, :height, :length].each do |dim| # Fetch value from the dimensions hash define_method(dim) do return dimensions[dim] end end end define_method Tuesday, July 24, 12
  59. class Instructor < Person def self.start_pomodoro puts "Ready, set, go"

    end def self.end_pomodoro puts "RINNGGGGG" end def give_lecture "Pomodoro, pomodoro, pomodoro." end end instance_eval and class_eval superclass class methods: Object ... superclass class methods: Object ... superclass class methods: Class ... superclass class methods: Class’ + start_pomo + end_pomo superclass class methods: Instructor + to_s Tuesday, July 24, 12
  60. class Instructor < Person def self.start_pomodoro puts "Ready, set, go"

    end def self.end_pomodoro puts "RINNGGGGG" end def give_lecture "Pomodoro, pomodoro, pomodoro." end end Instructor.class_eval do def give_advice "Bonnie will take care of it!" end end superclass class methods: Object ... superclass class methods: Object ... superclass class methods: Class ... superclass class methods: Class’ + start_pomo + end_pomo instance_eval and class_eval superclass class methods: Instructor + give_lecture + give_advice Tuesday, July 24, 12
  61. class Instructor < Person def self.start_pomodoro puts "Ready, set, go"

    end def self.end_pomodoro puts "RINNGGGGG" end def give_lecture "Pomodoro, pomodoro, pomodoro." end end Instructor.instance_eval do def restart_pomodoro end_pomodoro start_pomodoro end end superclass class methods: Object ... superclass class methods: Instructor + to_s superclass class methods: Object ... superclass class methods: Class ... superclass class methods: Class’ + start_pomo + end_pomo + restart_pomo instance_eval and class_eval Tuesday, July 24, 12
  62. String interpolated class Shape attr_reader :dimensions def initialize(dimensions={}) @dimensions =

    dimensions end [:width, :height, :length].each do |dim| class_eval <<-MET def #{dim}=(val) @#{dim} = val end MET end end Tuesday, July 24, 12
  63. class Instructor < Person def self.start_pomodoro puts "Ready, set, go"

    end def self.end_pomodoro puts "RINNGGGGG" end def give_lecture "Pomodoro, pomodoro, pomodoro." end end Instructor.class_eval <<-MET, __FILE__, __LINE__ def advise_student "Bonnie will take care of it!" end MET superclass class methods: Object ... superclass class methods: Object ... superclass class methods: Class ... superclass class methods: Class’ + start_pomo + end_pomo instance_eval and class_eval superclass class methods: Instructor + give_lecture + give_advice Tuesday, July 24, 12
  64. Class “macro” methods module AccessibleAttributes def accessible_attribute(name) define_method(name) do instance_variable_get("@#{name}")

    end define_method("#{name}=") do |value| instance_variable_set("@#{name}", value) end end end Creating a module that generates methods allows it to be extended into an existing class to provide macro abilities. class Feed extend AccessibleAttributes end Tuesday, July 24, 12
  65. Testing “macro” methods describe AccessibleAttributes do let(:klass) { Class.new }

    before(:each) do klass.extend AccessibleAttributes klass.accessible_attribute :foo end describe ".accessible_attribute" do it "allows writing and reading" do k = klass.new.tap{|k| k.foo = "bar" } k.foo.should == "bar" end end end Extending into a new class for each spec prevents specs from interacting with one another. Tuesday, July 24, 12
  66. Rewrite this class to use a single call to define_method

    instead of the normal defs. Rewrite it again using a form of eval and a string with interpolation. For either version, add the corresponding reader methods. class Player def name=(name) @name = name end def score=(score) @score = score end def paddle=(paddle) @paddle = paddle end end Task: Define methods dynamically Tuesday, July 24, 12
  67. Hash.class_eval do def method_missing(name, *args, &block) if has_key?(name) fetch(name) else

    super end end def respond_to?(name) has_key?(name) || super end end respond_to? Tuesday, July 24, 12
  68. class Instructor < Person def give_lecture "Pomodoro, pomodoro, pomodoro." end

    end jeff = Instructor.new jeff.hand_out_contracts Method Missing name: “Jeff” age: 31 jeff superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s Tuesday, July 24, 12
  69. class Instructor < Person def give_lecture "Pomodoro, pomodoro, pomodoro." end

    end jeff = Instructor.new jeff.hand_out_contracts Method Missing name: “Jeff” age: 31 jeff superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s Nope! #hand_out_contracts Tuesday, July 24, 12
  70. class Instructor < Person def give_lecture "Pomodoro, pomodoro, pomodoro." end

    end jeff = Instructor.new jeff.hand_out_contracts Method Missing name: “Jeff” age: 31 jeff superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s Nope! #hand_out_contracts Tuesday, July 24, 12
  71. class Instructor < Person def give_lecture "Pomodoro, pomodoro, pomodoro." end

    end jeff = Instructor.new jeff.hand_out_contracts Method Missing name: “Jeff” age: 31 jeff superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s Nope! #hand_out_contracts Tuesday, July 24, 12
  72. class Instructor < Person def give_lecture "Pomodoro, pomodoro, pomodoro." end

    end jeff = Instructor.new jeff.hand_out_contracts Method Missing name: “Jeff” age: 31 jeff superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s Nope! #method_missing Tuesday, July 24, 12
  73. class Instructor < Person def give_lecture "Pomodoro, pomodoro, pomodoro." end

    end jeff = Instructor.new jeff.hand_out_contracts Method Missing name: “Jeff” age: 31 jeff superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s Nope! #method_missing Tuesday, July 24, 12
  74. class Instructor < Person def give_lecture "Pomodoro, pomodoro, pomodoro." end

    end jeff = Instructor.new jeff.hand_out_contracts Method Missing name: “Jeff” age: 31 jeff superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s #method_missing Yep! Tuesday, July 24, 12
  75. class Instructor < Person def give_lecture "Pomodoro, pomodoro, pomodoro." end

    end jeff = Instructor.new jeff.hand_out_contracts #NoMethodError: undefined method `hand_out_contracts' for Instructor Jeff Method Missing name: “Jeff” age: 31 jeff superclass class methods: Person + say_name superclass class methods: Object ... superclass class methods: Instructor + to_s #method_missing Yep! Tuesday, July 24, 12
  76. included is invoked by the Ruby runtime whenever a module

    is included into another class. It can be used to implement a pattern that gives the appearance that inclusion also imports “class” methods. module AccessibleAttributes def self.included(base) base.extend(ClassMethods) end module ClassMethods def accessible_attribute(name) define_method(name) do instance_variable_get("@#{name}") end define_method("#{name}=") do |value| instance_variable_set("@#{name}", value) end end end end Hooks Tuesday, July 24, 12
  77. included is invoked by the Ruby runtime whenever a module

    is included into another class. It can be used to implement a pattern that gives the appearance that inclusion also imports “class” methods. module AccessibleAttributes def self.included(base) base.extend(ClassMethods) end module ClassMethods def accessible_attribute(name) define_method(name) do instance_variable_get("@#{name}") end define_method("#{name}=") do |value| instance_variable_set("@#{name}", value) end end end end Hooks Other hooks methods include: • extended • method_defined • method_undefined • inherited Tuesday, July 24, 12
  78. Define a module Settings that provides a macro method called

    .setting that takes a name, “foo” and creates #foo_on, #foo_off, and also #foo_on?, #foo_off? instance methods. Next, let .setting take a list of setting names. class Configurator include Settings setting :power end c = Configurator.new c.power_on? #=> false c.power_off? #=> true c.power_on c.power_off? #=> false Task: Define a macro method Tuesday, July 24, 12
  79. Define a let macro that takes a name and a

    block. It creates an instance method of that name, the body of which is the block, but that block should be dynamically evaluated in the context of the instance. Super duper bonus: define a let! that memoizes the result of the block after the first call. Bonus: Macro with instance scope class Computor let(:quotient) { dividend / divisor } def dividend; 1; end def divisor; 0; end end c = Computor.new c.quotient # ZeroDivisionError: divided by 0 Tuesday, July 24, 12
  80. • class Foo < class_expr() • closure currying • caller

    • binding • yield (self) • set_trace_function • defined? / const_defined? Misc. Tuesday, July 24, 12