Ruby class_eval, __FILE__ and __LINE__ arguments

Here is an example of class_eval method call that we will discuss:

def my_attr_reader(sym)
  class_eval <<-READER, __FILE__ , __LINE__ + 1
    def #{sym}
      @#{sym}
    end
  READER
end

What class_eval method does and what is it used for? Why do we send file name and line number values to it? These are the questions that we will answer here.

class_eval evaluates a string or block in the context of the receiver self which can be a class or module. In the following example we are using class_eval to define an instance method in the class identified by self (User):

module MyAttrReader
  def my_attr_reader(sym)
    class_eval <<-READER, __FILE__ , __LINE__ + 1
      def #{sym}
        @#{sym}
      end
    READER
  end
end

class User
  extend MyAttrReader

  def initialize(name)
    @name = name
  end

  my_attr_reader :name
end

u = User.new('dalibor')
p u.name # calls the method 'name' defined with class_eval

Let's now explain the pseudo variables that are used:

__FILE__ # the current source file name.
__LINE__ # the current line number in the source file.

We can simplify the class_eval call by removing the here document:

def my_attr_reader(sym)
  class_eval("def #{sym}; undefined_method; @#{sym}; end;", __FILE__ , __LINE__ )
end

Now when the syntax is clear, if we check the documentation for class_eval we can see that second and third arguments are used to set the text for error message which will help us locate where the error has occurred.

Look at the errors produced by the following 2 code snippets. Notice that we added undefined_method in the string evaluated by class_eval so that it produces an error and we removed the file and line pseudo variables from the first snippet:

module MyAttrReader
  def my_attr_reader(sym)
    class_eval "def #{sym}; undefined_method; @#{sym}; end;"
  end
end

class User
  extend MyAttrReader

  def initialize(name)
    @name = name
  end

  my_attr_reader :name
end

u = User.new('dalibor')
p u.name

# (eval):1:in `name': undefined local variable or method `undefined_method' for # (NameError)
module MyAttrReader
  def my_attr_reader(sym)
    class_eval "def #{sym}; undefined_method; @#{sym}; end;", __FILE__ , __LINE__
  end
end

class User
  extend MyAttrReader

  def initialize(name)
    @name = name
  end

  my_attr_reader :name
end

u = User.new('dalibor')
p u.name

# t.rb:3:in `name': undefined local variable or method `undefined_method' for # (NameError)

Error messages are displayed at the bottom of the code snippets.

You can notice that the second error message is clear, telling us that the error is on file line 3, but the first error message doesn't tell us the file line where the error has occurred.