轻松克隆 active_record 对象,包括关联以及关联和属性下的多个操作。
请参阅此处。
目标是能够轻松快速地重现 ActiveRecord 对象(包括其子对象),例如复制博客文章并维护其关联的标签或类别。
这种宝石被命名为“变形虫”,因为变形虫(小型生命形式)善于繁殖。他们的子孙也能快速、轻松地繁衍后代。
ActiveRecord 扩展 gem,允许在复制活动记录模型时复制关联的子记录对象。
兼容 Rails 5.2、6.0、6.1。对于 Rails 4.2 到 5.1,请使用版本 3.x。
支持以下关联类型
has_many
has_one :through
has_many :through
has_and_belongs_to_many
一个简单的 DSL,用于配置要复制的字段。 DSL 可以应用于您的导轨模型或即时使用。
支持 STI(单表继承)子级继承其父级变形虫设置。
多种配置风格,例如包容性、排他性和无差别(又名复制一切)。
支持克隆多对多记录的子记录或仅保留原始关联
支持自动向下钻取,即递归复制子记录和孙记录。
支持字段预处理,以帮助指示唯一性并根据您的业务逻辑需求确保数据的完整性,例如预先添加“副本”或类似文本。
支持使用自定义 lambda 块对字段进行预处理,因此您基本上可以做任何您想做的事情,例如,如果您在制作副本时需要一些自定义逻辑。
Amoeba 可以对复制记录的字段进行以下预处理操作
放
前置
附加
无效化
定制
正则表达式
希望如您所期望的那样:
宝石安装阿米巴
或者只是将其添加到您的 Gemfile 中:
宝石“阿米巴”
使用以下样式之一配置您的模型,然后在您通常运行dup
方法的模型上运行amoeba_dup
方法:
p = Post.create(:title => "Hello World!", :content => "Lorum ipsum dolor")p.comments.create(:content => "我喜欢它!")p.comments.create(: content => "这太糟糕了!")puts Comment.all.count # 应该是 2my_copy = p.amoeba_dupmy_copy.saveputs Comment.all.count # 应该是 4
默认情况下,启用后,amoeba 将自动复制任何和所有关联的子记录并将它们与新的父记录关联。
您可以将行为配置为仅包含您列出的字段或仅包含您不排除的字段。在这三种风格中,性能最高的是不加区分的风格,其次是包容风格,而独占风格将是最慢的,因为需要对每个字段进行额外的显式检查。这种性能差异可能可以忽略不计,您可以根据最容易读写的方式选择要使用的样式,但是,如果您的数据树足够大并且您需要控制复制哪些字段,那么包含式样式可能是更好的选择选择而不是独特的风格。
请注意,这些示例只是现实世界场景的松散近似,可能不是特别现实,它们仅用于演示功能使用的目的。
这是最基本的用例,将简单地启用任何已知关联的复制。
如果您有一些类似这样的博客模型:
类 Post < ActiveRecord::Base has_many :commentsendclass 注释 < ActiveRecord::Base 属于:postend
只需将 amoeba 配置块添加到您的模型中并调用 enable 方法即可启用子记录的复制,如下所示:
类 Post < ActiveRecord::Base 有_很多:评论 阿米巴原虫可行 endendclass 注释 < ActiveRecord::Base 属于:postend
当您运行amoeba_dup
方法时,子记录将被自动复制。
如果您只想复制某些关联而不复制其他关联,则可以使用包含样式:
类 Post < ActiveRecord::Base 有_很多:评论 有_很多:标签 has_many :作者 阿米巴 doenableinclude_association :tagsinclude_association :authors endendclass 注释 < ActiveRecord::Base 属于:postend
在 amoeba 块中使用包容性样式实际上意味着您希望启用 amoeba,因此无需运行 enable 方法,尽管它也不会造成损害:
类 Post < ActiveRecord::Base 有_很多:评论 有_很多:标签 has_many :作者 阿米巴 doinclude_association :tagsinclude_association :作者 endendclass 注释 < ActiveRecord::Base 属于:postend
您还可以通过传递数组来指定要复制的字段。如果您使用单个值调用include_association
,它将被附加到已包含字段的列表中。如果您传递一个数组,您的数组将覆盖原始值。
类 Post < ActiveRecord::Base 有_很多:评论 有_很多:标签 has_many :作者 阿米巴 doinclude_association [:标签、:作者] endendclass 注释 < ActiveRecord::Base 属于:postend
这些示例将复制帖子的标签和作者,但不会复制其评论。
使用包容性样式时,将自动禁用先前选择的任何其他样式。
如果要包含的字段多于要排除的字段,您可能希望通过使用独占样式来缩短所需的键入和阅读量。所有未明确排除的字段都将被复制:
类 Post < ActiveRecord::Base 有_很多:评论 有_很多:标签 has_many :作者 阿米巴 doexclude_association :评论 endendclass 注释 < ActiveRecord::Base 属于:postend
此示例与包容性样式示例执行相同的操作,它将复制帖子的标签和作者,但不复制其评论。与包容性风格一样,在指定要排除的字段时无需显式启用 amoeba。
使用独占样式时,将自动禁用之前选择的任何其他样式,因此如果您选择了包含字段,然后选择了一些排除字段,则exclude_association
方法将禁用之前选择的包含样式并清除任何相应的包含字段。
此外,如果您需要为包含或排除关系指定额外条件,您可以将方法名称指定为:if
选项。
类 Post < ActiveRecord::Base 有_很多:评论 有_很多:标签 阿米巴 doinclude_association :评论,如果: :受欢迎? 结尾 def 受欢迎?喜欢 > 15 结束
调用Post.first.amoeba_dup
后,如果likes
大于 15,所有评论也会被复制,但在另一种情况下 - 不会克隆任何关系。 exclude_association
也有相同的行为。
请注意!如果你写道:
类 Post < ActiveRecord::Base 有_很多:评论 有_很多:标签 amoeba doexclude_association :tagsinclude_association :评论,如果: :受欢迎? 结尾 def 受欢迎?喜欢 > 15 结束
不管结果如何popular?
方法调用(反向情况相同)。
如果您使用多对多关系,您可以告诉阿米巴实际上复制原始相关记录,而不仅仅是维持与原始记录的关联。克隆很简单,只需告诉阿米巴要克隆哪些字段,就像告诉阿米巴要包含或排除哪些字段一样。
类 Post < ActiveRecord::Base has_and_belongs_to_many :警告 has_many :post_widgets has_many :widgets, :through => :post_widgets 阿米巴 doenableclone [:小部件, :警告] endendclass 警告 < ActiveRecord::Base has_and_belongs_to_many :postsendclass PostWidget < ActiveRecord::Base 属于:小部件 属于_to :postendclass Widget < ActiveRecord::Base has_many :post_widgets has_many :posts, :through => :post_widgetsend
该示例实际上将复制数据库中的警告和小部件。如果数据库中最初有 3 个警告,那么在复制帖子后,数据库中最终会出现 6 个警告。这与默认行为相反,在默认行为中,您的新帖子只会与任何先前存在的警告重新关联,并且这些警告本身不会重复。
默认情况下,amoeba 识别并尝试复制以下关联类型的任何子级:
有一个
有很多
拥有并属于许多
您可以使用 amoeba 配置块中的recognize
方法来控制 amoeba 应用到哪些关联类型。
类 Post < ActiveRecord::Base 有_一个:配置 有_很多:评论 has_and_belongs_to_many :标签 变形虫识别 [:has_one, :has_and_belongs_to_many] endendclass 注释 < ActiveRecord::Base 属于_to :postendclass 标签 < ActiveRecord::Base has_and_belongs_to_many :postsend
此示例将复制帖子的配置数据并保留与新帖子关联的标签,但不会复制帖子的评论,因为 amoeba 只会识别和复制has_one
和has_and_belongs_to_many
关联的子级,并且在此示例中,评论不是has_and_belongs_to_many
关联。
如果您希望防止常规(非基于has_*
关联)字段在复制时保留其值,您可以“清零”或“无效”该字段,如下所示:
类主题 < ActiveRecord::Base has_many :postsendclass Post < ActiveRecord::Base 属于:主题 有_很多:评论 阿米巴 doenablenullify :date_publishednullify :topic_id endendclass 注释 < ActiveRecord::Base 属于:postend
此示例将复制帖子的所有评论。它还将使发布日期无效,并将该帖子与其原始主题分离。
与包含和独占样式不同,指定空字段不会自动使amoeba 复制所有子记录。与任何活动记录对象一样,如果迁移中存在默认值,则将使用默认字段值而不是nil
。
如果您希望将所有重复对象上的字段设置为任意值,您可以使用set
指令。例如,如果您想要复制具有某种与之关联的审批流程的对象,您可能希望再次将新对象的状态设置为打开或“正在进行”。
类 Post < ActiveRecord::Base 阿米巴剂量组:state_tracker =>“open_for_editing” 结束
在此示例中,当重复帖子时,它的state_tracker
字段将始终被赋予open_for_editing
值以启动。
您可以在复制阶段将字符串添加到复制对象字段的开头:
类 Post < ActiveRecord::Base amoeba doenableprepend :title => "副本" 结束
您可以在复制阶段将字符串添加到复制对象字段的末尾:
类 Post < ActiveRecord::Base amoeba doenableappend :title => "副本" 结束
您可以在复制阶段对复制对象的字段运行搜索和替换查询:
类 Post < ActiveRecord::Base 阿米巴 doenableregex :contents => {:replace => /dog/, :with => 'cat'} 结束
您可以运行一个或多个自定义方法来执行基本上您喜欢的任何操作,只需将 lambda 块或 lambda 块数组传递给customize
指令即可。每个块必须具有相同的形式,这意味着每个块必须接受两个参数,原始对象和新复制的对象。然后你可以做任何你想做的事,就像这样:
类 Post < ActiveRecord::Base amoeba doprepend :title => "Hello world! "customize(lambda { |original_post,new_post| if original_post.foo == "bar"new_post.baz = "qux" end})append :comments => "...知道什么我说呢?” 结束
或者这样,使用数组:
类 Post < ActiveRecord::Base has_and_belongs_to_many :标签 amoeba doinclude_association :tagscustomize([ lambda do |orig_obj,copy_of_obj|# 好东西放在这里 end, lambda do |orig_obj,copy_of_obj|# 更多好东西放在这里 end]) 结束
默认情况下,在所有复制和字段预处理之后,Lambda 块会传递给自定义运行。如果您希望在任何自定义或字段预处理之前运行一个方法,您可以使用override
的customize
方法。用法与上面相同。
类 Post < ActiveRecord::Base amoeba doprepend :title => "Hello world!" override(lambda { |original_post,new_post| if original_post.foo == "bar"new_post.baz = "qux" end})append :comments => "...知道什么我说呢?” 结束
您可以一次将单个预处理器应用于多个字段。
类 Post < ActiveRecord::Base amoeba doenableprepend :title => "副本", :contents => "复制的内容:" 结束
您可以一次将多个预处理指令应用于单个模型。
类 Post < ActiveRecord::Base amoeba doprepend :title => "副本", :contents => "原始内容: "append :contents => " (复制版本)"regex :contents => {:replace => /dog/, :with => '猫'} 结束
这个例子应该产生如下结果:
发布 = Post.create( :title => "你好世界", :contents => "我喜欢狗,狗太棒了。")new_post = post.amoeba_dupnew_post.title # "Hello world 的副本"new_post.contents # "原创内容:我喜欢猫,猫太棒了。(复制版)"
与nullify
一样,预处理指令不会自动启用关联子记录的复制。如果仅使用预处理指令,并且您确实想要复制子记录,并且未提供include_association
或exclude_association
列表,则仍然必须通过从模型上的 amoeba 块内调用 enable 方法来显式启用子记录的复制。
您可以在每个模型的阿米巴块中使用配置方法的组合。已识别的关联类型优先于包含或排除列表。包容风格优先于排他风格,这两种显式风格优先于不加区别的风格。换句话说,如果您列出要复制的字段,amoeba 将仅复制您列出的字段,或者仅复制您不排除的字段(视情况而定)。此外,如果某个字段类型无法识别,则无论它是否出现在包含列表中,都不会复制该字段类型。如果您希望 amoeba 自动复制所有子记录,请不要使用include_association
或exclude_association
列出任何字段。
以下示例语法完全有效,并且将导致使用包含样式。在 amoeba 块中调用配置方法的顺序并不重要:
类主题 < ActiveRecord::Base has_many :postsendclass Post < ActiveRecord::Base 属于:主题 有_很多:评论 有_很多:标签 has_many :作者 amoeba doexclude_association :authorsinclude_association :tagsnullify :date_publishedprepend :title => "复制“append :contents => ”(复制版本)"正则表达式 :contents => {:replace => /dog/, :with => 'cat'}include_association :authorsenablenullify :topic_id endendclass 注释 < ActiveRecord::Base 属于:postend
此示例将复制帖子的所有标签和作者,但不复制其评论。它还将使发布日期无效,并将该帖子与其原始主题分离。它还将像前面的预处理示例一样预处理帖子的字段。
请注意,由于优先级的原因,使用包含样式并且从不查阅排除字段列表。此外, enable
方法是多余的,因为使用include_association
时会自动启用amoeba。
预处理指令在复制子记录后运行,并按此顺序运行。
空字段
前置
追加
搜索和替换
预处理指令不影响包含和排除列表。
您可以让变形虫继续沿着链复制,只要您愿意,只需将变形虫块添加到您希望复制其子代的每个模型中即可。 Amoeba 将自动递归到任何启用的孙子级并复制它们。
类 Post < ActiveRecord::Base 有_很多:评论 阿米巴原虫可行 endendclass 注释 < ActiveRecord::Base 属于:帖子 has_many :评级 阿米巴原虫可行 endendclass 评级 < ActiveRecord::Base 属于:评论结束
在此示例中,当复制帖子时,变形虫将复制帖子的所有评论,并且还将复制每个评论的评级。
使用has_one :through
关联很简单,只需确保在具有has_one
关联的每个模型上启用 amoeba,amoeba 就会自动递归地向下钻取,如下所示:
供应商类 < ActiveRecord::Base 有_一个:帐户 has_one :历史记录, :通过 => :帐户 阿米巴原虫可行 endendclass 帐户 < ActiveRecord::Base 所属:供应商 有_一个:历史 阿米巴原虫可行 endendclass 历史 < ActiveRecord::Base 属于:帐户结束
复制has_many :through
关联会自动进行。它们以与has_and_belongs_to_many
关联相同的方式执行复制,这意味着不会复制实际的子记录,而是简单地维护关联。如果您愿意,您可以向中间模型添加一些字段预处理器,但这并不是绝对必要的:
类 Assembly < ActiveRecord::Base has_many :清单 has_many :零件, :通过 => :清单 阿米巴原虫可行 endendclass 清单 < ActiveRecord::Base 属于:程序集 属于:部分 amoeba doprepend :notes => “副本” endendclass 部分 < ActiveRecord::Base has_many :清单 has_many :程序集, :through => :清单 阿米巴原虫可行 结束
您可以通过将配置块传递给模型的 amoeba 方法来控制 amoeba 如何动态复制对象。配置方法是静态的,但配置是基于每个实例应用的。
类 Post < ActiveRecord::Base 有_很多:评论 amoeba doenableprepend :title => "副本" endendclass 注释 < ActiveRecord::Base 属于_to :postendclass PostsController < ActionController defplicated_a_postold_post = Post.create( :title => "Hello world", :contents => "Lorum ipsum")old_post.class.amoeba do prepend :contents => "这是一份副本:"endnew_post = old_post.amoeba_dupnew_post.title #应该是“Hello world 的副本”new_post.contents # 应该是“这是一份副本:Lorum ipsum"new_post.save 结束
如果您使用ActiveRecord提供的单表继承,您可能会导致amoeba以与父类相同的方式自动处理子类。您需要做的就是在父类的amoeba 块中调用propagate
方法,所有子类都应该以类似的方式复制。
create_table :products, :force => true do |t| t.string :type # 这是 STI 列 # 这些属于所有产品 t.string :标题 t.小数:价格 # 这些仅适用于衬衫 t.decimal :sleeve_length t.decimal :collar_size # 这些仅适用于计算机 t.整数:ram_size t.integer :hard_drive_sizeendclass 产品 < ActiveRecord::Base 有_许多:图像 has_and_belongs_to_many :类别 阿米巴原虫可传播 endendclass 衬衫 < Productendclass 计算机 < Productendclass ProductsController def some_methodmy_shirt = Shirt.find(1)my_shirt.amoeba_dupmy_shirt.save# 这件衬衫现在应该:# - 拥有所有父图像的自己的副本# - 与父图像属于同一类别 结束
此示例应复制与这件衬衫相关的所有图像和部分,该衬衫是 Product 的子项
默认情况下,传播使用顺从父级,这意味着将应用父级上的配置设置,但任何子级设置(如果存在)将添加到或覆盖父级设置,具体取决于您调用 DSL 方法的方式。
您可以更改此行为,即所谓的“育儿风格”,以优先考虑父设置或忽略任何和所有子设置。
:relaxed
育儿风格会更喜欢父母的设置。
产品类 < ActiveRecord::Base 有_许多:图像 has_and_belongs_to_many :节 阿米巴 doexclude_association :imagespropagate :relaxed endendclass 衬衫 < 产品 include_association:图像 include_association :部分 前置:标题=>“副本”结束
在此示例中,子级上冲突的include_association
设置将被忽略,而父级的exclude_association
设置将被使用,