activerecordレコードを複製する最も簡単な方法は何ですか?


412

idに加えて)プロセス内の単一のフィールドを変更して、activerecordレコードのコピーを作成します。これを達成する最も簡単な方法は何ですか?

新しいレコードを作成し、フィールドごとにデータをコピーして各フィールドを反復処理できることに気付きましたが、これを行う簡単な方法があるはずだと思いました...

といった:

 @newrecord=Record.copy(:id)  *perhaps?*

回答:


622

コピーを取得するには、クローン(またはRails 3.1以降の場合はdup)メソッドを使用します。

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

次に、必要なフィールドを変更できます。

ActiveRecordは組み込みのObject#cloneオーバーライドして、 IDが割り当てられていない新しい(DBに保存されていない)レコードを提供します。
関連付けはコピーされないため、必要に応じて手動でコピーする必要があることに注意してください。

Rails 3.1クローンは浅いコピーです。代わりにdupを使用してください...


6
これはまだRails 3.1.0.betaで動作しますか?私がするときq = p.clone、それからp == q私はtrue戻る。一方、を使用するとq = p.dupfalseそれらを比較すると戻ります。
Autumnsault、2011

1
クローン上のRails 3.1のドキュメントは、それはまだ動作しますと言うが、私はRailsのに3.1.0.rc4を使用していますし、でもnew?この方法が機能していません。
Turadg

12
この機能はdupに置き換えられたようです:gist.github.com/994614
skattyadz

74
クローンは絶対に使用しないでください。他の投稿者が言及したように、クローンメソッドは現在、IDをコピーするKernel#cloneの使用を委任しています。今からActiveRecord :: Base#dupを使用してください
bradgonesurfing '05

5
これは本当の痛みでした。仕様を十分にカバーしていない場合、このような単純な変更を目的の機能に変更すると、いくつかの重要な機能が損なわれる可能性があります。
マット・スミス

74

ニーズとプログラミングスタイルに応じて、クラスの新しいメソッドとマージの組み合わせを使用することもできます。より簡単な例がないので、特定の日付にスケジュールされたタスクがあり、それを別の日付に複製したいとします。タスクの実際の属性は重要ではないため、次のようにします。

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

新しいタスクが作成されます:id => nil:scheduled_on => some_new_date元のタスクと同じ、および他のすべての属性を。Task.newを使用する場合は、明示的にsaveを呼び出す必要があるため、自動的に保存する場合は、Task.newをTask.createに変更します。

平和。


5
ないかなり確実なアイデアの良い、これはあなたが取得B / CがどのようにWARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_at返さ
bcackerman

これを行うと、has_many関係が原因で存在する列があるため、1つの列で不明な属性エラーが発生します。これを回避する方法はありますか?
Ruben Martinez Jr.

2
@RubenMartineJr。私はこれが古い投稿であることを知っていますが、そうです、属性ハッシュで '.except'を使用することでこれを回避できます:new_task = Task.new(old_task.attributes.except(:attribute_you_dont_want、:another_aydw).merge({:scheduled_on =>
Ninigi

@PhillipKoebbeありがとう-しかし、IDをnullにしない場合はどうなりますか?複製を作成するときに、レールに自動的に新しいIDを割り当てたい-これは可能ですか?
BKSpurgeon

1
old_task.attribtesも、残念ながらIDフィールドを割り当てます。それは私のために機能していません
BKSpurgeon

32

また、ActiveRecord 3.2用のAmoeba gemもお勧めです。

あなたのケースでは、おそらく、設定DSLで利用可能なnullifyregexまたはprefixオプションを利用したいでしょう。

それは簡単で、自動再帰の重複をサポートhas_onehas_manyおよびhas_and_belongs_to_many団体、フィールドの前処理やモデルにその場での両方に適用することができ、非常に柔軟で強力な設定DSLを。

Amoebaのドキュメントを必ずチェックしてください。ただし、使い方は簡単です...

ただ

gem install amoeba

または追加

gem 'amoeba'

あなたのGemfileに

次に、amoebaブロックをモデルに追加し、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

コピーするフィールドをさまざまな方法で制御することもできますが、たとえば、コメントが重複しないようにしたいが同じタグを維持したい場合は、次のようにすることができます。

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にはさらに多くのオプションがあるため、ドキュメントを確認してください。

楽しい!:)


すばらしい答えです。詳細ありがとうございます!
Derek Prior

うまくいきました!! しかし、1つの質問があります。クローンされたオブジェクトを保存する前に、クローンに新しいエントリを追加するにはどうすればよいですか?
Mohd Anas 2013年

1
ここで修正するだけです。正しい方法は.amoeba_dupだけではなくです.dup。このコードを実行しようとしましたが、ここでは機能していませんでした。
ビクター


24

私は通常、属性をコピーして、変更が必要なものを変更します。

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

これを行うunknown attributeと、has_many関係が原因で存在する列があるため、1つの列でエラーが発生します。これを回避する方法はありますか?
Ruben Martinez Jr.

rails4では、レコードの一意のIDは作成されません
Ben

4
Rails 4で新しいレコードを作成するには、を実行しますUser.create(old_user.attributes.merge({ login: "newlogin", id: nil }))。これにより、新しいユーザーが正しい一意のIDで保存されます。
RajeshM 2015年

RailsにはHash#exceptHash#sliceがあり、提案されたメソッドが最も強力でエラーが発生しにくくなります。追加のライブラリを追加する必要はなく、簡単に拡張できます。
kucaahbe

10

アソシエーションを含むディープコピーが必要な場合は、deep_cloneable gem をお勧めします。


私も。私はこの宝石を試してみましたが、それは初めて動作し、非常に使いやすかったです。
Rob


2

簡単な方法は次のとおりです。

#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     

2

以下は、#dupインスタンスの複製をカスタマイズし、リレーションの複製も含めるためにActiveRecord メソッドをオーバーライドするサンプルです。

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を必要としませんが、#dupメソッドが実装された新しいActiveRecordバージョンが必要です


0

act_as_inheritable gem も確認できます。

「Acts As Inheritableは、Rails / ActiveRecordモデル用に特別に作成されたRuby Gemです。これは、自己参照アソシエーション、または継承可能な属性を共有する親を持つモデルで使用することを目的としています。これにより、任意の属性または親モデルとの関係。」

acts_as_inheritableモデルに追加することにより、これらのメソッドにアクセスできます。

inherit_attributes

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">

これがお役に立てば幸いです。


0

ロジックが増える可能性があるため、モデルを複製するときは、必要なすべてのロジックを処理する新しいクラスを作成することをお勧めします。それを容易にするために役立つ宝石があります: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'}

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.