Amoeba: deep cloning ActiveRecord objects and their children in Rails
A ruby gem to allow the copying of ActiveRecord objects and their associated children, configurable with a DSL on the model
At a glance
- What is it?
- Amoeba is a Ruby gem that extends ActiveRecord so a duplicated record brings its children with it, configured through a per-model DSL with inclusive, exclusive or indiscriminate styles. It is small, narrowly scoped and still receiving releases, but its documented Rails support stops well short of current versions.
- Who is it for?
- Adopt Amoeba if your Rails application has a real copy feature, such as duplicating a post with its tags and comments, and you want that behaviour declared in the model rather than hand-written in a service object. Do not adopt it on a Rails version the README does not list, and treat the licence as unresolved until you have read LICENSE.md, since GitHub cannot classify it as a standard open source licence.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What ActiveRecord duplication leaves behind
Amoeba exists because of a specific gap in ActiveRecord. Calling dup on a record gives you a new object with the same attribute values and no identity, but the associated rows are left where they were: they still point at the original parent. Copy a blog post and you get a second post with no tags, no categories and no comments, and any child that referenced the old parent now belongs to the wrong record.
The project's stated goal is to reproduce an ActiveRecord object together with its children, and the README gives the case it was built for: copying a blog post while maintaining its associated tags or categories. The gem's name comes from the organism, since amoebas reproduce quickly and their children and grandchildren do the same, which is an accurate description of what the gem does to a record tree.
The scope is deliberately narrow. It is an ActiveRecord extension, not a Rails engine, and it works through a model-level DSL that declares which associations to copy and which fields to adjust on the copies. The audience is application developers who need a duplicate action in their own product, and the alternative they would otherwise write is a service object that walks associations by hand, saves each child, and re-points the foreign keys.
What amoeba_dup does to the database
The call itself replaces dup. The README's example creates a post with two comments, duplicates the post, saves the copy, and then checks the comment count:
p = Post.create(:title => "Hello World!", :content => "Lorum ipsum dolor")
p.comments.create(:content => "I love it!")
p.comments.create(:content => "This sucks!")
puts Comment.all.count # should be 2
my_copy = p.amoeba_dup
my_copy.save
puts Comment.all.count # should be 4Two things are worth noticing. The duplicate is not persisted when it is created; amoeba_dup builds the copy and its children in memory, and the save is what writes them. And the copies are real rows, not references, so the comment table grows from two rows to four. The new comments are attached to the copied post, while the original comments remain attached to the original.
That last point is what makes the library useful and what makes it something to think about twice. If an association is not copied, the copy simply has no children of that type. If it is copied, the database now holds two independent sets, and anything that later treats the copy as a peer of the original, a category counter, a search index or a cache, has to know that both exist.
Indiscriminate, inclusive and exclusive styles
The DSL offers three styles for deciding what travels with the copy, and the README is explicit about their relative cost.
The indiscriminate style is the basic case. You add the amoeba block, call enable, and every known association is copied. The inclusive style names what to copy, using include_association, and the exclusive style names what to leave behind, using exclude_association. Using include_association or exclude_association implies enabling the gem, so a separate enable call is unnecessary, although it does no harm. Whichever style you pick last wins: the README states that the inclusive style automatically disables any other style previously selected, and that selecting exclude fields disables previously selected include fields.
The performance ordering is documented. The indiscriminate style is the fastest, then the inclusive style, and the exclusive style is the slowest because it needs an extra explicit check on every field. The README's own advice is to pick the style that is easiest to read and write, since the difference is likely negligible, and to prefer the inclusive style over the exclusive one when the data tree is large and you need explicit control over what gets copied.
That ordering is a real design trade-off rather than a defect, but it means the most readable option for a small model is not the same as the fastest one for a large tree.
Preprocessing rules for the fields on copied rows
Copied rows often need to differ from their originals, otherwise uniqueness constraints or human expectations break. Amoeba handles this with preprocessing operations applied to the fields of copied records, and the list in the README covers six of them: set, prepend, append, nullify, customize and regex.
Set assigns a fixed value. Prepend and append add text to the start or the end of the existing value, which is the mechanism behind the README's example of marking a copy by prepending a label such as Copy of. Nullify empties the field. Customize takes a custom lambda block, and the README is blunt about its reach: with a lambda you can do basically whatever you want, for example when you need custom logic while making copies. Regex applies a regular expression rule, which is what the most recent release, v3.4.1 published on 2026-10-18, was a fix for.
The version history shows this area still evolving. v3.4.0 on 2026-09-15 added conditional nullify, meaning a field can be emptied under a condition rather than always, and v3.4.1 corrected the regex rule behaviour. A gem whose most recent two releases both touch the copy-time field rules is one to pin precisely if you depend on it.
Installing the gem and declaring the copy behaviour
Installation is a standard gem operation, either from the command line:
gem install amoebaor as a line in the Gemfile:
gem 'amoeba'After that, the model declaration is where the work happens. A model with a single association can use the indiscriminate form, where the amoeba block calls enable and every known association is copied:
class Post < ActiveRecord::Base
has_many :comments
amoeba do
enable
end
end
class Comment < ActiveRecord::Base
belongs_to :post
endIf only some associations should travel, the inclusive form lists them by name. The README notes that calling include_association with a single value appends to the existing list, while passing an array overwrites the list, which is an easy place to introduce a subtle bug when editing configuration:
class Post < ActiveRecord::Base
has_many :comments
has_many :tags
has_many :authors
amoeba do
include_association [:tags, :authors]
end
end
class Comment < ActiveRecord::Base
belongs_to :post
endThis copies the post's tags and authors but not its comments. The exclusive form reaches the same result from the other side by naming what to leave out, which is shorter when there is more to copy than to skip.
Association types, drill-down and STI
The gem supports four association shapes: has_many, has_one :through, has_many :through and has_and_belongs_to_many. The through and many-to-many cases carry a choice that the README spells out: the children of many-to-many records can either be cloned as well or merely maintained, meaning the copy keeps pointing at the original tag rows rather than duplicating them. For a system where a tag row carries a counter or a curated description, keeping the original is usually the behaviour you want, and duplicating it is usually a mistake.
Drill-down is the recursive case. Amoeba supports automatic drill-down, that is recursive copying of child and grandchild records, so a tree of records reproduces as a tree rather than as a single level.
Single Table Inheritance is handled as well: children inheriting from a parent pick up the parent's amoeba settings, which saves the business of repeating a DSL block in every subclass. The release history adds lazy load hooks in v3.3.0, published on 2023-09-30, which suggests the gem also hooks into the point at which associations are loaded so that copying does not force unnecessary queries.
Where Amoeba is the wrong tool
The first limitation is version support. The README states that the gem is compatible with Rails 5.2, 6.0 and 6.1, and that for Rails 4.2 through 5.1 you should use version 3.x. It says nothing about later Rails releases, so on a current application the documentation gives you no guidance about whether the gem works, and the honest reading is that compatibility is unstated rather than confirmed. The repository does contain an Appraisals file and a gemfiles directory, which is the standard way to test a gem against several dependency sets, so the project has the machinery to verify more combinations than the README names.
The second limitation is that the copy is shallow by default in the ways you choose not to configure. A DSL that copies associations cannot know that a particular field makes a row non-duplicable, or that a child record should be shared rather than copied, or that the copy should be attributed to a different owner. Those are the preprocessing rules, and they are opt-in. A team that enables the indiscriminate style on a model with a uniqueness constraint on a copied field will hit the database, not the gem.
The third is transaction behaviour. The README does not document whether the save of a copy and its children happens inside a transaction, nor what happens if a child fails to save halfway through a tree. If your copy operation must be atomic, that is something to establish yourself in the calling code rather than assume.
The alternative is to write the copy logic yourself in a service object, where you control exactly which associations are traversed, which fields are rewritten and how failures are handled. The difference in approach is that Amoeba derives the behaviour from a declaration in the model and applies it uniformly, while a service object makes every decision explicit at the call site. For one model with two associations, the service object is often clearer; across twenty models, the declaration wins.
Maintenance, test tooling and licence
The project is not archived and the last push was on 2026-10-18, the same day as the v3.4.1 release. Before that came v3.4.0 on 2026-09-15, and before that a long gap back to v3.3.0 on 2023-09-30, so the recent work is a burst rather than a steady stream.
The repository layout shows a conventionally tested gem. There is an Rakefile, a .rspec file for RSpec configuration, a .rubocop.yml with a companion .rubocop_todo.yml for existing offences, a defaults.reek file for the code smell detector, a .cane file, a CHANGELOG.md, a Gemfile, the amoeba.gemspec and separate spec and docs directories. The Appraisals file and the gemfiles directory point at a matrix of dependency versions being exercised in CI, which is where a Rails compatibility claim would be enforced.
Licensing is the loose end. The repository records a custom licence that GitHub cannot classify, with LICENSE.md at the root. Since the gem is distributed through RubyGems and pulled into applications as a dependency, the terms matter for anything beyond internal use, and the honest position is that they have to be read in the file rather than assumed from the gem's popularity. That is a description of the licensing situation and not legal advice.
Editorial conclusion
Adopt Amoeba if your Rails application has a real copy feature, such as duplicating a post with its tags and comments, and you want that behaviour declared in the model rather than hand-written in a service object. Do not adopt it on a Rails version the README does not list, and treat the licence as unresolved until you have read LICENSE.md, since GitHub cannot classify it as a standard open source licence. Verify first that the association types you need are supported, that your copy operation runs inside a transaction you control, and that the fields you copy are covered by a preprocessing rule such as prepend or nullify so duplicated rows stay distinguishable.
Frequently asked questions
How do I install the Amoeba gem?
Run gem install amoeba, or add the line gem 'amoeba' to your Gemfile. You then call the amoeba_dup method on a model instead of dup and save the result.
Which Rails versions does Amoeba support?
The README states compatibility with Rails 5.2, 6.0 and 6.1, and directs users of Rails 4.2 through 5.1 to version 3.x of the gem. Later Rails versions are not mentioned in the README.
What is the difference between the inclusive and exclusive styles in Amoeba?
The inclusive style names the associations to copy with include_association, while the exclusive style names the ones to leave behind with exclude_association and copies everything else. The README notes that the exclusive style is the slowest of the three because it checks every field explicitly.
Can Amoeba copy the children of many-to-many associations?
Yes, but it can also maintain the original associations instead. The README describes the choice between cloning the children of many-to-many records and merely keeping the original tag rows attached to the copy.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/amoeba-rb-amoeba)Was this useful?