3

I want to write a method which takes one parameter and creates another method, named with this parameter. Here is my code

class Class
  def createMethod(attr_name)
    attr_name = attr_name.to_s
    class_eval %Q{
        def #{attr_name}
            puts "bar"
        end
    }
  end
end

p Class.new.createMethod("foo").respond_to?(:foo)

Unfortunately, respond_to?(:foo) evaluates to false. What's wrong?

ciembor
  • 7,189
  • 13
  • 59
  • 100

1 Answers1

3

This is because class_eval is a class method and you're calling it in the context of an instance. You can do this instead:

class Class
  def createMethod(attr_name)
    attr_name = attr_name.to_s
    self.class.class_eval %Q{
        def #{attr_name}
            puts "bar"
        end
    }
    self # Return yourself if you want to allow chaining methods
  end
end

Here's the output from irb when doing this:

irb(main):001:0> class Class
irb(main):002:1>   def createMethod(attr_name)
irb(main):003:2>     attr_name = attr_name.to_s
irb(main):004:2>     self.class.class_eval %Q{
irb(main):005:2"         def #{attr_name}
irb(main):006:2"             puts "bar"
irb(main):007:2"         end
irb(main):008:2"     }
irb(main):009:2>   end
irb(main):010:1> end
=> nil
irb(main):011:0> clazz = Class.new
=> #<Class:0x007fd86495cd58>
irb(main):012:0> clazz.respond_to?(:foo)
=> false
irb(main):013:0> clazz.createMethod("foo")
=> nil
irb(main):014:0> clazz.respond_to?(:foo)
=> true
Marc Baumbach
  • 10,323
  • 2
  • 30
  • 45
  • 2
    The reason it's still false is that your `createMethod` call returns `nil`, not the instance, so you can't chain the `respond_to?` call. You could put `self` right before the `end` of `createMethod` and that would work. – Marc Baumbach Jan 20 '13 at 15:53
  • 1
    Do not post images of text, instead copy-and-paste the actual text directly into your post. Images of text are not easily parseable, searchable, or accessible. – Andrew Marshall Jan 20 '13 at 16:49
  • @AndrewMarshall Will do. Fixed. – Marc Baumbach Jan 20 '13 at 16:51