【问题标题】:What is the easiest way to duplicate an activerecord record?复制 activerecord 记录的最简单方法是什么?
【发布时间】:2010-09-08 18:29:36
【问题描述】:

我想复制一个 ActiveRecord 对象,在过程中更改单个字段(除了 id)。完成此任务的最简单方法是什么?

我意识到我可以创建一个新记录,然后遍历每个字段,逐个字段地复制数据 - 但我认为必须有更简单的方法来做到这一点。

大概是这样的:

 new_record = Record.copy(:id)

【问题讨论】:

    标签: ruby-on-rails ruby rails-activerecord


    【解决方案1】:

    要获取副本,请使用 dup(或

    #rails >= 3.1
    new_record = old_record.dup
    
    # rails < 3.1
    new_record = old_record.clone
    

    然后您可以更改您想要的任何字段。

    ActiveRecord overrides the built-in Object#clone 为您提供一个未分配 ID 的新记录(未保存到数据库中)。
    请注意,它不会复制关联,因此如果需要,您必须手动执行此操作。

    Rails 3.1 clone is a shallow copy, use dup instead...

    【讨论】:

    • 这在 Rails 3.1.0.beta 中仍然有效吗?当我做q = p.clone,然后p == q,我得到true。另一方面,如果我使用q = p.dup,在比较它们时我会得到false
    • 这个功能好像被dup替换了:gist.github.com/994614
    • 绝对不要使用克隆。正如其他海报所提到的,克隆方法现在委托给使用 Kernel#clone 来复制 id。从现在开始使用 ActiveRecord::Base#dup
    • 我不得不说,这真的很痛苦。如果您没有良好的规范覆盖范围,对预期功能进行这样的简单更改可能会削弱一些重要功能。
    • 如果您想更改特定属性,dupclone 的补充是使用 tap 例如clone = record.dup.tap { |new_clone| new_clone.name = "dup_#{new_clone.name}" }
    【解决方案2】:

    我通常只是复制属性,更改我需要更改的任何内容:

    new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
    

    【讨论】:

    • 执行此操作时,由于 has_many 关系而存在一列,因此我收到一列 unknown attribute 错误。有没有办法解决这个问题?
    • 使用rails4,它不会为记录创建唯一的id
    • 要使用 Rails 4 创建新记录,请执行 User.create(old_user.attributes.merge({ login: "newlogin", id: nil }))。这将保存一个具有正确唯一 ID 的新用户。
    • Rails 有Hash#exceptHash#slice,这可能使建议的方法最强大且不易出错。无需添加额外的库,易于扩展。
    【解决方案3】:

    根据您的需要和编程风格,您还可以使用类的新方法和合并的组合。由于缺少更好的简单示例,假设您有一个任务安排在某个日期,并且您想将它复制到另一个日期。任务的实际属性并不重要,所以:

    old_task = Task.find(task_id) new_task = Task.new(old_task.attributes.merge({:scheduled_on => some_new_date}))

    将创建一个具有:id =&gt; nil:scheduled_on =&gt; some_new_date 以及与原始任务相同的所有其他属性的新任务。使用 Task.new,您必须显式调用 save,因此如果您希望它自动保存,请将 Task.new 更改为 Task.create。

    和平。

    【讨论】:

    • 不太确定这是一个多么好的主意,你得到 WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_at 的返回
    • 当我执行此操作时,由于 has_many 关系存在一列,我得到一列的未知属性错误。有没有办法解决这个问题?
    • @RubenMartineJr。我知道这是一篇旧帖子,但是是的,您可以通过在属性哈希上使用“.except”来解决这个问题: new_task = Task.new(old_task.attributes.except(:attribute_you_dont_want, :another_aydw).merge({:scheduled_on => some_new_date}))
    • @PhillipKoebbe 谢谢 - 但如果我希望 id 不为空怎么办?我希望 rails 在我创建副本时自动分配一个新的 id - 这可能吗?
    • old_task.attribtes 不幸地也分配了 ID 字段。它不适合我
    【解决方案4】:

    如果您不想复制 id,请使用 ActiveRecord::Base#dup

    【讨论】:

    • @Thorin 根据上面接受的答案,看起来 Rails .clone
    【解决方案5】:

    如果您需要带有关联的深层副本,我推荐deep_cloneable gem。

    【讨论】:

    • 我也是。我试过这个 gem,第一次就成功了,非常好用。
    【解决方案6】:

    您可能还喜欢 ActiveRecord 3.2 的 Amoeba gem

    在您的情况下,您可能希望使用配置 DSL 中可用的 nullifyregexprefix 选项。

    它支持has_onehas_manyhas_and_belongs_to_many 关联的简单自动递归复制、字段预处理和高度灵活且功能强大的配置 DSL,可同时应用于模型和动态。

    请务必查看Amoeba Documentation,但使用起来非常简单...

    只是

    gem install amoeba
    

    或添加

    gem 'amoeba'
    

    到你的 Gemfile

    然后将变形虫块添加到您的模型中并照常运行dup 方法

    class Post < ActiveRecord::Base
      has_many :comments
      has_and_belongs_to_many :tags
    
      amoeba do
        enable
      end
    end
    
    class Comment < ActiveRecord::Base
      belongs_to :post
    end
    
    class Tag < ActiveRecord::Base
      has_and_belongs_to_many :posts
    end
    
    class PostsController < ActionController
      def some_method
        my_post = Post.find(params[:id])
        new_post = my_post.dup
        new_post.save
      end
    end
    

    您还可以通过多种方式控制复制哪些字段,但例如,如果您想防止 cmets 被复制但又想保持相同的标签,您可以执行以下操作:

    class Post < ActiveRecord::Base
      has_many :comments
      has_and_belongs_to_many :tags
    
      amoeba do
        exclude_field :comments
      end
    end
    

    您还可以预处理字段以帮助使用前缀和后缀以及正则表达式来指示唯一性。此外,还有许多选项,因此您可以根据自己的目的以最易读的风格编写:

    class Post < ActiveRecord::Base
      has_many :comments
      has_and_belongs_to_many :tags
    
      amoeba do
        include_field :tags
        prepend :title => "Copy of "
        append :contents => " (copied version)"
        regex :contents => {:replace => /dog/, :with => "cat"}
      end
    end
    

    关联的递归复制很容易,只需在子模型上启用变形虫

    class Post < ActiveRecord::Base
      has_many :comments
    
      amoeba do
        enable
      end
    end
    
    class Comment < ActiveRecord::Base
      belongs_to :post
      has_many :ratings
    
      amoeba do
        enable
      end
    end
    
    class Rating < ActiveRecord::Base
      belongs_to :comment
    end
    

    配置 DSL 有更多选项,因此请务必查看文档。

    享受吧! :)

    【讨论】:

    • 很好的答案。感谢您的详细信息!
    • 感谢它的工作!但是我有一个问题,如何在保存克隆对象之前添加新条目?
    • 这里只是一个修复。正确的方法是.amoeba_dup,而不仅仅是.dup。我试图执行这段代码,但它在这里不起作用。
    【解决方案7】:

    您也可以查看acts_as_inheritable gem。

    “Acts As Inheritable 是专为 Rails/ActiveRecord 模型编写的 Ruby Gem。它旨在与 Self-Referential Association 或具有共享可继承属性的父模型一起使用。这将让您继承任何来自父模型的属性或关系。”

    通过将acts_as_inheritable 添加到您的模型中,您将可以访问这些方法:

    继承属性

    class Person < ActiveRecord::Base
    
      acts_as_inheritable attributes: %w(favorite_color last_name soccer_team)
    
      # Associations
      belongs_to  :parent, class_name: 'Person'
      has_many    :children, class_name: 'Person', foreign_key: :parent_id
    end
    
    parent = Person.create(last_name: 'Arango', soccer_team: 'Verdolaga', favorite_color:'Green')
    
    son = Person.create(parent: parent)
    son.inherit_attributes
    son.last_name # => Arango
    son.soccer_team # => Verdolaga
    son.favorite_color # => Green
    

    inherit_relations

    class Person < ActiveRecord::Base
    
      acts_as_inheritable associations: %w(pet)
    
      # Associations
      has_one     :pet
    end
    
    parent = Person.create(last_name: 'Arango')
    parent_pet = Pet.create(person: parent, name: 'Mango', breed:'Golden Retriver')
    parent_pet.inspect #=> #<Pet id: 1, person_id: 1, name: "Mango", breed: "Golden Retriver">
    
    son = Person.create(parent: parent)
    son.inherit_relations
    son.pet.inspect # => #<Pet id: 2, person_id: 2, name: "Mango", breed: "Golden Retriver">
    

    希望对你有帮助。

    【讨论】:

      【解决方案8】:

      简单的方法是:

      #your rails >= 3.1 (i was done it with Rails 5.0.0.1)
        o = Model.find(id)
       # (Range).each do |item|
       (1..109).each do |item|
         new_record = o.dup
         new_record.save
       end
      

      或者

      # if your rails < 3.1
       o = Model.find(id)
       (1..109).each do |item|
         new_record = o.clone
         new_record.save
       end     
      

      【讨论】:

        【解决方案9】:

        由于可能存在更多逻辑,因此在复制模型时,我建议创建一个新类,您可以在其中处理所有需要的逻辑。 为了缓解这种情况,有一个可以提供帮助的 gem:clowne

        根据他们的文档示例,对于用户模型:

        class User < ActiveRecord::Base
          # create_table :users do |t|
          #  t.string :login
          #  t.string :email
          #  t.timestamps null: false
          # end
        
          has_one :profile
          has_many :posts
        end
        

        你创建你的克隆器类:

        class UserCloner < Clowne::Cloner
          adapter :active_record
        
          include_association :profile, clone_with: SpecialProfileCloner
          include_association :posts
        
          nullify :login
        
          # params here is an arbitrary Hash passed into cloner
          finalize do |_source, record, params|
            record.email = params[:email]
          end
        end
        
        class SpecialProfileCloner < Clowne::Cloner
          adapter :active_record
        
          nullify :name
        end
        

        然后使用它:

        user = User.last
        #=> <#User(login: 'clown', email: 'clown@circus.example.com')>
        
        cloned = UserCloner.call(user, email: 'fake@example.com')
        cloned.persisted?
        # => false
        
        cloned.save!
        cloned.login
        # => nil
        cloned.email
        # => "fake@example.com"
        
        # associations:
        cloned.posts.count == user.posts.count
        # => true
        cloned.profile.name
        # => nil
        

        从项目中复制的示例,但它将清楚地说明您可以实现的目标。

        为了快速简单的记录,我会选择:

        Model.new(Model.last.attributes.reject {|k,_v| k.to_s == 'id'}

        【讨论】:

          【解决方案10】:

          这是一个覆盖 ActiveRecord #dup 方法的示例,用于自定义实例复制并包括关系复制:

          class Offer < ApplicationRecord
            has_many :offer_items
          
            def dup
              super.tap do |new_offer|
          
                # change title of the new instance
                new_offer.title = "Copy of #{@offer.title}"
          
                # duplicate offer_items as well
                self.offer_items.each { |offer_item| new_offer.offer_items << offer_item.dup }
              end
            end
          end
          

          注意:此方法不需要任何外部 gem,但需要更新的 ActiveRecord 版本并实现 #dup 方法

          【讨论】:

            【解决方案11】:

            在 Rails 5 中,您可以像这样简单地创建重复的对象或记录。

            new_user = old_user.dup
            

            【讨论】:

              【解决方案12】:

              试试rails的dup方法:

              new_record = old_record.dup.save
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-10-06
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多