0

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?

3 Answers 3

3

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.

Sign up to request clarification or add additional context in comments.

2 Comments

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.
This is one of the many reasons why I always recommend using the block form of class_eval.
2

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.

Comments

1

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"

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.