前任留下了一个祖传代码,看着挺好的,通过一个 build 方法,就可以动态创建一个新的类。等到自己去实现第二个类的时候,出了问题。say 方法被覆盖了。第一感觉就是,类被重新打开,然后被覆写了。

class Base
  def self.build (&block)
    Class.new(self) do
      yield
    end
  end

  def base_method
    puts 'base_method'
  end
end

T = Base.build do
  public
  def say
    puts 'T'
  end
end

M = Base.build do
  public
  def say
    puts 'M'
  end
end

T.new.base_method
M.new.base_method
T.new.say
M.new.say

后来,在尝试打印 self 的时候,发现 block 是在顶级作用域 main 里面执行的。导致的结果就是,第二次会覆盖第一次的方法定义,而且所有地方都有这个方法,无论是类方法,还是实例方法。

T = Base.build do
  p self
  def say
    puts 'T'
  end
end

问题到这里,就是切换 context 的问题了。用 class_eval 可以切换 context 到新建的类执行代码块,进而定义实例方法。

class Base
  def self.build (&block)
    Class.new(self) do
      class_eval @block
    end
  end

  def base_method
    puts 'base_method'
  end
end

参考 https://stackoverflow.com/questions/19319138/dynamically-create-a-class-inherited-from-activerecord