如何在纯Ruby中实现类似Rails的before_initialize / before_new之类的东西?

时间:2018-07-01 10:59:04

标签: ruby

在Rails中,我们可以定义一个类,例如:

class Test < ActiveRecord::Base
  before_initialize :method
end

,并且在调用Test.new时,将在实例上调用method()。我试图学习更多有关Ruby和类似的类方法的信息,但是我很难在普通的Ruby中实现它。

这是我到目前为止所拥有的:

class LameAR
  def self.before_initialize(*args, &block)
    # somehow store the symbols or block to be called on init
  end

  def new(*args)
    ## Call methods/blocks here
    super(*args)
  end
end

class Tester < LameAR
  before_initialize :do_stuff

  def do_stuff
    puts "DOING STUFF!!"
  end
end

我正在尝试找出将块存储在self.before_initialize中的位置。我最初尝试使用@before_init_methods之类的实例变量,但是那时该实例变量在内存中不存在,因此无法存储或检索它。我不确定在类定义期间如何/在哪里存储这些块/过程/符号,以便稍后在new内部调用。

我该如何实施? (无论是before_initialize都是一个符号块/程序/符号列表,我现在都不会介意,只是试图理解这个概念)

2 个答案:

答案 0 :(得分:3)

要获得全面的描述,您随时可以check the Rails source;毕竟,它本身是用“普通的Ruby”实现的。 (但是它可以处理很多边缘情况,因此对于快速浏览并不太好。)

快速版本是:

module MyCallbacks
  def self.included(klass)
    klass.extend(ClassMethods) # we don't have ActiveSupport::Concern either
  end

  module ClassMethods
    def initialize_callbacks
      @callbacks ||= []
    end

    def before_initialize(&block)
      initialize_callbacks << block
    end
  end

  def initialize(*)
    self.class.initialize_callbacks.each do |callback|
      instance_eval(&callback)
    end

    super
  end
end

class Tester
  include MyCallbacks
  before_initialize { puts "hello world" }
end

Tester.new

留给读者:

  • 参数
  • 按名称调用方法
  • 继承力
  • 回调中止呼叫并提供返回值
  • 包裹原始调用的“环绕”回调
  • 条件回调(:if / :unless
  • 子类有选择地覆盖/跳过回调
  • 在序列中的其他位置插入新的回调

...但是消除所有这些是[希望]使此实现更容易实现的方式。

答案 1 :(得分:2)

一种方法是覆盖Class#new

class LameAR
  def self.before_initialize(*symbols_or_callables, &block)
    @before_init_methods ||= []
    @before_init_methods.concat(symbols_or_callables)
    @before_init_methods << block if block
    nil
  end

  def self.new(*args, &block)
    obj = allocate

    @before_init_methods.each do |symbol_or_callable|
      if symbol_or_callable.is_a?(Symbol)
        obj.public_send(symbol_or_callable)
      else
        symbol_or_callable.(obj)
      end
    end

    obj.__send__(:initialize, *args, &block)
  end
end

class Tester < LameAR
  before_initialize :do_stuff

  def do_stuff
    puts "DOING STUFF!!"
  end
end