学习deep_cloneable的使用方法

145 阅读2分钟

学习deep_cloneable的使用方法

我们总是做的基本事情之一是复制,由于我们使用Rails(因此也是ActiveRecord),复制对象显然是必须的。我记得我当时负责实现复制,早在那个时候,我就决定使用反射来复制元素,虽然它很有效,但它却逐渐失去了控制。

在项目的下一次大迭代中,我们的一个工程师决定使用deep_cloneable。这在开始时效果很好,直到我们的模型开始变得更加复杂,需要包括它们的关联(以及它们的关联的关联)。添加这些关联变得非常痛苦,特别令人困惑。

最后,经过更多的研究,我们的另一位工程师提出了一个新的宝石,叫做阿米巴,它工作得非常好,因为定义要克隆的东西和如何克隆它都很清楚。但是有一个注意事项:继承,特别是单表继承

根据README,STI是被支持的......某种程度上,事实是在测试了不同的选项和阅读了源代码后,很明显,继承没有被正确支持,而我们需要它。

所以,在想了一下之后,我决定写一个小的关注点,以更好地处理这个问题。这个关注点的目标很明确。

  1. 定义duplicate 方法
  2. 定义一个使用阿米巴DSL的块
  3. 允许继承的块被子类正确继承。
  4. 盈利

解决方案如下。

# If you use amoeba (https://github.com/amoeba-rb/amoeba) for cloning your
# ActiveRecord objects this concern should be useful, it allows you properfly inherit
# associations if you use (Single Table) Inheritance.
#
# Just do something like:
# 
# class SomeTable < ActiveRecord::Base
#   has_many :somethings
#
#   duplicate_this do
#     include_association :somethings
#   end
# end

module Duplicable
  extend ActiveSupport::Concern

  included do
    class_attribute :amoeba_blocks
  end

  module ClassMethods
    def inherited(subclass)
      super
      subclass.duplicate_this
    end

    def duplicate_this(&block)
      self.amoeba_blocks ||= begin
        blocks = [ Proc.new { enable } ]

        superclass = nil
        loop do
          superclass = self.superclass
          break if superclass == ActiveRecord::Base

          blocks.unshift(*superclass.amoeba_blocks)
        end

        blocks << block if block_given?

        blocks
      end

      blocks = self.amoeba_blocks

      self.amoeba do |config|
        blocks.each { |db| config.instance_eval(&db) }
      end
    end
  end

  def duplicate
    blocks = self.class.amoeba_blocks || []

    self.class.amoeba do |config|
      blocks.each { |db| config.instance_eval(&db) }
    end

    self.amoeba_dup
  end
end

在上面的要点中,需要注意的是两件事。

  1. inherited 方法和
  2. 的方法duplicateduplicate_this

首先是继承的方法。这个方法是回调,每当创建当前类的子类时就立即执行,换句话说:如果A类包括Duplicable模块,然后B类A类进行子类化,就会调用inherited 。这在子类不明确调用duplicate_this 方法但仍需要支持duplicate的情况下特别重要......这也是子类创建Duplicable的全部意义。

第二,duplicateduplicate_this 方法。如果你注意到这个问题背后的奥妙,基本上是把所有的块(包括阿米巴的DSL)保存在一个叫做amoeba_blocksclass_attribute中,然后用instance_eval 来调用它们,所以最后阿米巴是执行所有块的人,因此正确地调用了它们的方法。

如果你问我,这是最完美的元编程。