Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
D

deep_cloneable

> 编程语言
Open source

This gem gives every ActiveRecord::Base object the possibility to do a deep clone that includes user specified associations.

811 stars0 likes2 views
WebsiteGitHub

About

This gem gives every ActiveRecord::Base object the possibility to do a deep clone that includes user specified associations.

deep_cloneable

This gem gives every ActiveRecord::Base object the possibility to do a deep clone that includes user specified associations. It is a rails 3+ upgrade of the deep_cloning plugin.

Requirements

  • Ruby >= 2 (3.3.5, 3.4.4 tested)
  • Activerecord >= 3.1.0 (7.1, 8.0 tested)

Installation

  • Add deep_cloneable to your Gemfile:
ruby
gem 'deep_cloneable', '~> 3.2.2'

Upgrade details

Upgrading from v2

There are two breaking changes that you might need to pay attention to:

  • When using an optional block (see below), the block used to be evaluated before deep_cloneable had performed its changes (inclusions, exclusions, includes). In v3, the block is evaluated after all processing has been done, just before the copy is about to be returned.
  • When a defined association is not found, deep_cloneable raises an exception. The exception class has changed namespace: the class definition used to be ActiveRecord::Base::DeepCloneable::AssociationNotFoundException and this has changed to DeepCloneable::AssociationNotFoundException.

Upgrading from v1

The dup method with arguments has been replaced in deep_cloneable 2 by the method deep_clone. Please update your sources accordingly.

Usage

The deep_clone method supports a couple options that can be specified by passing an options hash. Without options, the behaviour is the same as ActiveRecord's dup method.

Association inclusion

Associations to be included in the dup can be specified with the include option:

…

The Dictionary (Object Reusage)

The dictionary ensures that models are not duped multiple times when it is associated to nested models. It does this by storing a mapping of the original object to its duped object. It can be used as follows:

ruby
# Enables the dictionary (empty on initialization)
pirate.deep_clone include: [ :mateys, { treasures:  [ :matey, :gold_pieces ] } ], use_dictionary: true

# Deep clones with a prefilled dictionary
dictionary = { mateys: {} }
pirate.mateys.each{|m| dict[:mateys][m] = m.deep_clone }
pirate.deep_clone include: [ :mateys, { treasures: [ :matey, :gold_pieces ] } ], dictionary: dictionary

Attribute Exceptions & Inclusions

The deep_clone method supports both except and only for specifying which attributes should be duped:

Exceptions

ruby
# Single exception
pirate.deep_clone except: :name

# Multiple exceptions
pirate.deep_clone except: [ :name, :nick_name ]

# Nested exceptions
pirate.deep_clone include: :parrot, except: [ :name, { parrot: [ :name ] } ]

Inclusions

ruby
# Single attribute inclusion
pirate.deep_clone only: :name

# Multiple attribute inclusions
pirate.deep_clone only: [ :name, :nick_name ]

# Nested attribute inclusions
pirate.deep_clone include: :parrot, only: [ :name, { parrot: [ :name ] } ]

Pre- and postprocessor

You can specify a pre- and/or a postprocessor to modify a duped object after duplication:

ruby
pirate.deep_clone(include: :parrot, preprocessor: ->(original, kopy) { kopy.cloned_from_id = original.id if kopy.respond_to?(:cloned_from_id) })
pirate.deep_clone(include: :parrot, postprocessor: ->(original, kopy) { kopy.cloned_from_id = original.id if kopy.respond_to?(:cloned_from_id) })

Note: Specifying a postprocessor is essentially the same as specifying an optional block (see below).

Note: Using deep_clone with a processors will pass all associated objects that are being cloned to the processor, so be sure to check whether the object actually responds to your method of choice.

Optional Block

Pass a block to deep_clone to modify a duped object after duplication:

ruby
pirate.deep_clone include: :parrot do |original, kopy|
  kopy.cloned_from_id = original.id if kopy.respond_to?(:cloned_from_id)
end

Note: Using deep_clone with a block will also pass the associated objects that are being cloned to the block, so be sure to check whether the object actually responds to your method of choice.

Cloning models with files

Carrierwave

If you are cloning models that have associated files through Carrierwave these will not get transferred automatically. To overcome the issue you need to explicitly set the file attribute.

Easiest solution is to add the code in a clone block as described above.

ruby
pirate.deep_clone include: :parrot do |original, kopy|
  kopy.thumbnail = original.thumbnail
end

Shrine

Shrine is similar to Carrierwave, but you have to clear the image_data hash attribute from the copy, otherwise Shrine will think there's an old attachment to clean up and will delete the original image.

ruby
pirate.deep_clone include: [:photos, :parrot] do |original, kopy|
  if kopy.is_a?(Photo)
    kopy.image_data = nil
    kopy.image = original.image
  end
end

Note you're not assigning image_data directly, otherwise this would result in both records sharing the same asset in your storage.

Directly assigning kopy.image to original.image ensures you use your usual uploading mechanism, such as background jobs.

ActiveStorage

For ActiveStorage, you have two options: you can either make a full copy, or share data blobs between two records.

Full copy example
…
Shallow copy example
ruby
pirate.deep_clone include: :parrot do |original, kopy|
  kopy.avatar.attach(original.avatar.blob) if kopy.is_a?(Pirate) && original.avatar.attached?
end

Skipping missing associations

By default, deep_cloneable will throw a DeepCloneable::AssociationNotFoundException error when an association cannot be found. You can also skip missing associations by specifying skip_missing_associations if needed, for example when you have associations on some (but not all) subclasses of an STI model:

ruby
pirate.deep_clone include: [:parrot, :rum], skip_missing_associations: true

Note on Patches/Pull Requests

  • Fork the project.
  • Make your feature addition or bug fix.
  • Add tests for it. This is important so I don't break it in a future version unintentionally.
  • Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
  • Send me a pull request. Bonus points for topic branches.

Copyright

Copyright © 2026 Reinier de Lange. See LICENSE for details.

Issues· 9 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Ruby

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言