学习deep_cloneable的使用方法
我们总是做的基本事情之一是复制,由于我们使用Rails(因此也是ActiveRecord),复制对象显然是必须的。我记得我当时负责实现复制,早在那个时候,我就决定使用反射来复制元素,虽然它很有效,但它却逐渐失去了控制。
在项目的下一次大迭代中,我们的一个工程师决定使用deep_cloneable。这在开始时效果很好,直到我们的模型开始变得更加复杂,需要包括它们的关联(以及它们的关联的关联)。添加这些关联变得非常痛苦,特别令人困惑。
最后,经过更多的研究,我们的另一位工程师提出了一个新的宝石,叫做阿米巴,它工作得非常好,因为定义要克隆的东西和如何克隆它都很清楚。但是有一个注意事项:继承,特别是单表继承。
根据README,STI是被支持的......某种程度上,事实是在测试了不同的选项和阅读了源代码后,很明显,继承没有被正确支持,而我们需要它。
所以,在想了一下之后,我决定写一个小的关注点,以更好地处理这个问题。这个关注点的目标很明确。
- 定义
duplicate方法 - 定义一个使用阿米巴DSL的块
- 允许继承的块被子类正确继承。
- 盈利
解决方案如下。
# 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
在上面的要点中,需要注意的是两件事。
inherited方法和- 的方法
duplicate和duplicate_this
首先是继承的方法。这个方法是回调,每当创建当前类的子类时就立即执行,换句话说:如果A类包括Duplicable模块,然后B类对A类进行子类化,就会调用inherited 。这在子类不明确调用duplicate_this 方法但仍需要支持duplicate的情况下特别重要......这也是子类创建Duplicable的全部意义。
第二,duplicate 和duplicate_this 方法。如果你注意到这个问题背后的奥妙,基本上是把所有的块(包括阿米巴的DSL)保存在一个叫做amoeba_blocks的class_attribute中,然后用instance_eval 来调用它们,所以最后阿米巴是执行所有块的人,因此正确地调用了它们的方法。
如果你问我,这是最完美的元编程。