Amoeba
A ruby gem to allow the copying of ActiveRecord objects and their associated children, configurable with a DSL on the model
Install / Use
/learn @amoeba-rb/AmoebaREADME
Amoeba
Easy cloning of active_record objects including associations and several operations under associations and attributes.
Interested in contributing?
See here.
What?
The goal was to be able to easily and quickly reproduce ActiveRecord objects including their children, for example copying a blog post maintaining its associated tags or categories.
This gem is named "Amoeba" because amoebas are (small life forms that are) good at reproducing. Their children and grandchildren also reproduce themselves quickly and easily.
Technical Details
An ActiveRecord extension gem to allow the duplication of associated child record objects when duplicating an active record model.
Rails 5.2, 6.0, 6.1 compatible. For Rails 4.2 to 5.1 use version 3.x.
Features
- Supports the following association types
has_manyhas_one :throughhas_many :throughhas_and_belongs_to_many
- A simple DSL for configuration of which fields to copy. The DSL can be applied to your rails models or used on the fly.
- Supports STI (Single Table Inheritance) children inheriting their parent amoeba settings.
- Multiple configuration styles such as inclusive, exclusive and indiscriminate (aka copy everything).
- Supports cloning of the children of Many-to-Many records or merely maintaining original associations
- Supports automatic drill-down i.e. recursive copying of child and grandchild records.
- Supports preprocessing of fields to help indicate uniqueness and ensure the integrity of your data depending on your business logic needs, e.g. prepending "Copy of " or similar text.
- Supports preprocessing of fields with custom lambda blocks so you can do basically whatever you want if, for example, you need some custom logic while making copies.
- Amoeba can perform the following preprocessing operations on fields of copied records
- set
- prepend
- append
- nullify
- customize
- regex
Usage
Installation
is hopefully as you would expect:
gem install amoeba
or just add it to your Gemfile:
gem 'amoeba'
Configure your models with one of the styles below and then just run the amoeba_dup method on your model where you would run the dup method normally:
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 4
By default, when enabled, amoeba will copy any and all associated child records automatically and associate them with the new parent record.
You can configure the behavior to only include fields that you list or to only include fields that you don't exclude. Of the three, the most performant will be the indiscriminate style, followed by the inclusive style, and the exclusive style will be the slowest because of the need for an extra explicit check on each field. This performance difference is likely negligible enough that you can choose the style to use based on which is easiest to read and write, however, if your data tree is large enough and you need control over what fields get copied, inclusive style is probably a better choice than exclusive style.
Configuration
Please note that these examples are only loose approximations of real world scenarios and may not be particularly realistic, they are only for the purpose of demonstrating feature usage.
Indiscriminate Style
This is the most basic usage case and will simply enable the copying of any known associations.
If you have some models for a blog about like this:
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
simply add the amoeba configuration block to your model and call the enable method to enable the copying of child records, like this:
class Post < ActiveRecord::Base
has_many :comments
amoeba do
enable
end
end
class Comment < ActiveRecord::Base
belongs_to :post
end
Child records will be automatically copied when you run the amoeba_dup method.
Inclusive Style
If you only want some of the associations copied but not others, you may use the inclusive style:
class Post < ActiveRecord::Base
has_many :comments
has_many :tags
has_many :authors
amoeba do
enable
include_association :tags
include_association :authors
end
end
class Comment < ActiveRecord::Base
belongs_to :post
end
Using the inclusive style within the amoeba block actually implies that you wish to enable amoeba, so there is no need to run the enable method, though it won't hurt either:
class Post < ActiveRecord::Base
has_many :comments
has_many :tags
has_many :authors
amoeba do
include_association :tags
include_association :authors
end
end
class Comment < ActiveRecord::Base
belongs_to :post
end
You may also specify fields to be copied by passing an array. If you call the include_association with a single value, it will be appended to the list of already included fields. If you pass an array, your array will overwrite the original values.
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
end
These examples will copy the post's tags and authors but not its comments.
The inclusive style, when used, will automatically disable any other style that was previously selected.
Exclusive Style
If you have more fields to include than to exclude, you may wish to shorten the amount of typing and reading you need to do by using the exclusive style. All fields that are not explicitly excluded will be copied:
class Post < ActiveRecord::Base
has_many :comments
has_many :tags
has_many :authors
amoeba do
exclude_association :comments
end
end
class Comment < ActiveRecord::Base
belongs_to :post
end
This example does the same thing as the inclusive style example, it will copy the post's tags and authors but not its comments. As with inclusive style, there is no need to explicitly enable amoeba when specifying fields to exclude.
The exclusive style, when used, will automatically disable any other style that was previously selected, so if you selected include fields, and then you choose some exclude fields, the exclude_association method will disable the previously selected inclusive style and wipe out any corresponding include fields.
Conditions
Also if you need to path extra condition for include or exclude relationship you can path method name to :if option.
class Post < ActiveRecord::Base
has_many :comments
has_many :tags
amoeba do
include_association :comments, if: :popular?
end
def popular?
likes > 15
end
end
After call Post.first.amoeba_dup if likes is larger 15 than all comments will be duplicated too, but in another situation - no relations will be cloned. Same behavior will be for exclude_association.
Be aware! If you wrote:
class Post < ActiveRecord::Base
has_many :comments
has_many :tags
amoeba do
exclude_association :tags
include_association :comments, if: :popular?
end
def popular?
likes > 15
end
end
inclusion strategy will be chosen regardless of the result of popular? method call (the same for reverse situation).
Cloning
If you are using a Many-to-Many relationship, you may tell amoeba to actually make duplicates of the original related records rather than merely maintaining association with the original records. Cloning is easy, merely tell amoeba which fields to clone in the same way you tell it which fields to include or exclude.
class Post < ActiveRecord::Base
has_and_belongs_to_many :warnings
has_many :post_widgets
has_many :widgets, :through => :post_widgets
amoeba do
enable
clone [:widgets, :warnings]
end
end
class Warning < ActiveRecord::Base
has_and_belongs_to_many :posts
end
class PostWidget < ActiveRecord::Base
belongs_to :widget
belongs_to :post
end
class Widget < ActiveRecord::Base
has_many :post_widgets
has_many :posts, :through => :post_widgets
end
This example will actually duplicate the warnings and widgets in the database. If there were originally 3 warnings in the database then, upon duplicating a post, you will end up with 6 warnings in the database. This is in contrast to the default behavior where your new post would merely be re-associated with any previously existing warnings and those warnings themselves would not be duplicated.
Limiting Association Types
By default, amoeba recognizes and attempts to copy any children of the following association types:
- has one
- has many
- has and belongs to many
You may control which association types amoeba applies itself to by using the recognize method within the amoeba configuration block.
class Post < ActiveRecord::Base
has_one :config
has_many :comments
has_and_belongs_to_many :tags
amoeba do
recognize [:has_one, :has_and_belongs_to_many]
end
end
class Comment < ActiveRecord::Base
belongs_to :post
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
This example will copy the post's configuration data and keep tags associated with the new post, but will not copy the post's comments because amoeba will only recognize and
