Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do we copy singleton methods between different Ruby classes?

I am trying to define a class with methods, and a class lacking those methods, and then allowing an object of the latter class to 'learn' the methods from an instance of the former class.

This is my attempt (Ruby 1.9.2) - it breaks (at the line commented "BREAKS!")when I try to change the value of 'self' in the lambda binding.

If you can work out how to solve this - I'd be fascinated to find out.

class Skill

  attr_accessor :name
  attr_accessor :technique

  def initialize(name, &technique_proc)
    @name = name
    @technique = lambda(&proc)
  end

end

class Person

  attr_accessor :name

  def initialize(name)
    @name = name
  end

  def method_missing(m, *args)
    "#{@name} the #{self.class}: I don't know how to #{m}"
  end

  def learn_skill(skill)
    puts "#{@name} the #{self.class} is learning skill: #{skill.name}"
    actual_self = self
    eval "self = #{actual_self}", skill.technique.binding ####### BREAKS!
    define_singleton_method skill.name.to_sym, skill.technique
  end

  def teach_skill(skill_name)
    skill = nil
    if self.respond_to?(skill_name) 
      puts "#{@name} the #{self.class} is teaching skill: #{skill_name}"
      skill_method = self.method(skill_name.to_sym)
      skill_proc = skill_method.to_proc
      skill_lambda = lambda(&skill_proc)
      skill = Skill.new(skill_name, &skill_lambda)
    end
    skill
  end

end

class Teacher < Person

  def speak(sentence)
    "#{@name} the #{self.class} is now saying \"#{sentence}\"!"
  end

  def jump(number_of_feet)
    "#{name} the #{self.class} is now jumping #{number_of_feet} high!"
  end

end

miss_mollyflop = Teacher.new("Miss Mollyflop")
little_billey = Person.new("Little Billy")

puts miss_mollyflop.speak("Good morning, children!")
puts little_billey.speak("Good morning, Miss Mollyflop!")

speak_skill = miss_mollyflop.teach_skill("speak")
little_billey.learn_skill(speak_skill)

puts little_billey.speak("Good morning, Miss Mollyflop!")

The output of this is:

Miss Mollyflop the Teacher is now saying "Good morning, children!"!
Little Billy the Person: I don't know how to speak
Miss Mollyflop the Teacher is teaching skill: speak
Little Billy the Person is learning skill: speak
test.rb:27:in `eval': (eval):1: Can't change the value of self (SyntaxError)
self = #<Person:0x1482270>
      ^
(eval):1: syntax error, unexpected $end
self = #<Person:0x1482270>
                          ^
        from test.rb:27:in `learn_skill'
        from test.rb:64:in `<main>'
like image 468
Anthony Avatar asked Dec 08 '10 17:12

Anthony


1 Answers

If you want to copy across methods from one class to another it's possible, but if the method modifies state it will modify state on the original object not on the object the method is subsequently bound to (this is because the method isn't really copied across instead a Proc wrapper of the method is bound to the new object as a method):

a = Object.new
def a.hello
  puts "hello world from a"
end

b = Object.new
b.define_singleton_method(:hello, &a.method(:hello))
b.hello #=> "hello world from a"
like image 51
horseyguy Avatar answered Sep 20 '22 22:09

horseyguy