435
votes

I want to make a copy of an ActiveRecord object, changing a single field in the process (in addition to the id). What is the simplest way to accomplish this?

I realize I could create a new record, and then iterate over each of the fields copying the data field-by-field - but I figured there must be an easier way to do this.

Perhaps something like this:

 new_record = Record.copy(:id)
12

12 Answers

650
votes

To get a copy, use the clone (or dup for rails 3.1+) method:

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

Then you can change whichever fields you want.

ActiveRecord overrides the built-in Object#clone to give you a new (not saved to the DB) record with an unassigned ID.
Note that it does not copy associations, so you'll have to do this manually if you need to.

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

75
votes

Depending on your needs and programming style, you can also use a combination of the new method of the class and merge. For lack of a better simple example, suppose you have a task scheduled for a certain date and you want to duplicate it to another date. The actual attributes of the task aren't important, so:

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

will create a new task with :id => nil, :scheduled_on => some_new_date, and all other attributes the same as the original task. Using Task.new, you will have to explicitly call save, so if you want it saved automatically, change Task.new to Task.create.

Peace.

35
votes

You may also like the Amoeba gem for ActiveRecord 3.2.

In your case, you probably want to make use of the nullify, regex or prefix options available in the configuration DSL.

It supports easy and automatic recursive duplication of has_one, has_many and has_and_belongs_to_many associations, field preprocessing and a highly flexible and powerful configuration DSL that can be applied both to the model and on the fly.

be sure to check out the Amoeba Documentation but usage is pretty easy...

just

gem install amoeba

or add

gem 'amoeba'

to your Gemfile

then add the amoeba block to your model and run the dup method as usual

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

You can also control which fields get copied in numerous ways, but for example, if you wanted to prevent comments from being duplicated but you wanted to maintain the same tags, you could do something like this:

class Post < ActiveRecord::Base
  has_many :comments
  has_and_belongs_to_many :tags

  amoeba do
    exclude_field :comments
  end
end

You can also preprocess fields to help indicate uniqueness with both prefixes and suffixes as well as regexes. In addition, there are also numerous options so you can write in the most readable style for your purpose:

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

Recursive copying of associations is easy, just enable amoeba on child models as well

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

The configuration DSL has yet more options, so be sure to check out the documentation.

Enjoy! :)

31
votes
25
votes

I usually just copy the attributes, changing whatever I need changing:

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

If you need a deep copy with associations, I recommend the deep_cloneable gem.

6
votes

In Rails 5 you can simply create duplicate object or record like this.

new_user = old_user.dup
3
votes

Here is a sample of overriding ActiveRecord #dup method to customize instance duplication and include relation duplication as well:

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

Note: this method doesn't require any external gem but it requires newer ActiveRecord version with #dup method implemented

2
votes

The easily way is:

#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

Or

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

You can also check the acts_as_inheritable gem.

"Acts As Inheritable is a Ruby Gem specifically written for Rails/ActiveRecord models. It is meant to be used with the Self-Referential Association, or with a model having a parent that share the inheritable attributes. This will let you inherit any attribute or relation from the parent model."

By adding acts_as_inheritable to your models you will have access to these methods:

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

Hope this can help you.

0
votes

Since there could be more logic, when duplicating a model, I would suggest to create a new class, where you handle all the needed logic. To ease that, there's a gem that can help: clowne

As per their documentation examples, for a User model:

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

You create your cloner class:

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

and then use it:

user = User.last
#=> <#User(login: 'clown', email: '[email protected]')>

cloned = UserCloner.call(user, email: '[email protected]')
cloned.persisted?
# => false

cloned.save!
cloned.login
# => nil
cloned.email
# => "[email protected]"

# associations:
cloned.posts.count == user.posts.count
# => true
cloned.profile.name
# => nil

Example copied from the project, but it will give a clear vision of what you can achieve.

For a quick and simple record I would go with:

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

0
votes

Try rails's dup method:

new_record = old_record.dup.save