views:

74

answers:

3

I have the following program.

module C
  def self.included(base)
    base.extend(ClassMethods)

  end

  module ClassMethods
    def test_for
      class_eval <<-DEFINECLASSMETHODS
        def self.my_method(param_a)
          puts "SELF is: #{self.inspect}"
          puts param_a
          puts "#{param_a}"
        end
      DEFINECLASSMETHODS
    end
  end
end

class A
  include C
end

class B < A
  test_for
end

when I run B.new.my_method("aaa"), I got this error

NameError: undefined local variable or method `param_a' for B:Class

I am quite confused.

I define param_a as a local variable in class method my_method,

puts param_a

runs good, and will output the "aaa".

however,

puts "#{param_a}"

output that error.

why?

Can anyone explain this?

A: 

Those are some majorly complex hoops you are jumping through, to achieve basically this:

module C
  def test_for
    define_singleton_method :my_method do |param_a|
      puts "SELF is: #{inspect}"
      p param_a
    end
  end
end

class A
  extend C
end

class B < A
  test_for
end

B.my_method 'foo'
# => SELF is: B
# => "foo"

EDIT: I just realized that the solution above is still much more complicated than it needs to be. In fact, we do not need any metaprogramming at all:

module C
  module D
    def my_method(param_a)
      puts "SELF is: #{inspect}"
      p param_a
    end
  end
  def test_for
    extend D
  end
end

class A
  extend C
end

class B < A
  test_for
end

B.my_method 'foo'
# => SELF is: B
# => "foo"
Jörg W Mittag
+3  A: 

You get that error because the #{} doesn't interpolate param_a into the string passed to puts - it interpolates it into the string passed to class_eval. It will work when you escape it, i.e.

puts "\#{param_a}"

You can also disable interpolation inside the heredoc by using <<-'DEFINECLASSMETHODS' instead of <<-DEFINECLASSMETHODS. This will also allow you to use other meta characters without having to escape them.

sepp2k
Damn! I totally missed that! +1 for eagle-eyes! Personally, I have the rule that I *always* use single-quoted strings, unless I am 100% sure that I absolutely, positively *want* string interpolation to occur. This is just one more example why that is a good idea.
Jörg W Mittag
This is one of the many reasons why I always recommend using the block form of `class_eval`.
Chuck
+1  A: 

Try using "class_eval do; end" instead, like this:

def test_for
  class_eval do
    def self.my_method(param_a)
      puts "SELF is: #{self.inspect}"
      puts param_a
      puts "#{param_a}"
    end 
  end 
end 

This way, no code escaping is necessary.

S. Christoffer Eliesen