lib/amoeba/cloner.rb
# frozen_string_literal: true
require 'forwardable'
module Amoeba
class Cloner
extend Forwardable
attr_reader :new_object, :old_object, :object_klass
def_delegators :old_object, :_parent_amoeba, :_amoeba_settings,
:_parent_amoeba_settings
def_delegators :object_klass, :amoeba, :fresh_amoeba, :reset_amoeba
def initialize(object, options = {})
@old_object = object
@options = options
@object_klass = @old_object.class
inherit_parent_settings
@new_object = object.__send__(amoeba.dup_method)
end
def run
process_overrides
apply if amoeba.enabled
after_apply if amoeba.do_preproc
@new_object
end
private
def parenting_style
amoeba.upbringing || _parent_amoeba.parenting
end
def inherit_strict_parent_settings
fresh_amoeba(&_parent_amoeba_settings)
end
def inherit_relaxed_parent_settings
amoeba(&_parent_amoeba_settings)
end
def inherit_submissive_parent_settings
reset_amoeba(&_amoeba_settings)
amoeba(&_parent_amoeba_settings)
amoeba(&_amoeba_settings)
end
def inherit_parent_settings
return unless _parent_amoeba.inherit
return unless %w[strict relaxed submissive].include?(parenting_style.to_s)
__send__("inherit_#{parenting_style}_parent_settings".to_sym)
end
def apply_clones
amoeba.clones.each do |clone_field|
exclude_clone_if_has_many_through(clone_field)
end
end
def exclude_clone_if_has_many_through(clone_field)
association = @object_klass.reflect_on_association(clone_field)
# if this is a has many through and we're gonna deep
# copy the child records, exclude the regular join
# table from copying so we don't end up with the new
# and old children on the copy
return unless association.macro == :has_many ||
association.is_a?(::ActiveRecord::Reflection::ThroughReflection)
amoeba.exclude_association(association.options[:through])
end
def follow_only_includes
amoeba.includes.each do |include, options|
next if options[:if] && !@old_object.send(options[:if])
follow_association(include, @object_klass.reflect_on_association(include))
end
end
def follow_all_except_excludes
@object_klass.reflections.each do |name, association|
exclude = amoeba.excludes[name.to_sym]
next if exclude && (exclude.blank? || @old_object.send(exclude[:if]))
follow_association(name, association)
end
end
def follow_all
@object_klass.reflections.each do |name, association|
follow_association(name, association)
end
end
def apply_associations
if amoeba.includes.present?
follow_only_includes
elsif amoeba.excludes.present?
follow_all_except_excludes
else
follow_all
end
end
def apply
apply_clones
apply_associations
end
def follow_association(relation_name, association)
return unless amoeba.known_macros.include?(association.macro.to_sym)
follow_klass = ::Amoeba::Macros.list[association.macro.to_sym]
follow_klass&.new(self)&.follow(relation_name, association)
end
def process_overrides
amoeba.overrides.each do |block|
block.call(@old_object, @new_object)
end
end
def process_null_fields
# nullify any fields the user has configured
amoeba.null_fields.each do |field_key|
@new_object[field_key] = nil
end
end
def process_coercions
# prepend any extra strings to indicate uniqueness of the new record(s)
amoeba.coercions.each do |field, coercion|
@new_object[field] = coercion.to_s
end
end
def process_prefixes
# prepend any extra strings to indicate uniqueness of the new record(s)
amoeba.prefixes.each do |field, prefix|
@new_object[field] = "#{prefix}#{@new_object[field]}"
end
end
def process_suffixes
# postpend any extra strings to indicate uniqueness of the new record(s)
amoeba.suffixes.each do |field, suffix|
@new_object[field] = "#{@new_object[field]}#{suffix}"
end
end
def process_regexes
# regex any fields that need changing
amoeba.regexes.each do |field, action|
@new_object[field].gsub!(action[:replace], action[:with])
end
end
def process_customizations
# prepend any extra strings to indicate uniqueness of the new record(s)
amoeba.customizations.each do |block|
block.call(@old_object, @new_object)
end
end
def after_apply
process_null_fields
process_coercions
process_prefixes
process_suffixes
process_regexes
process_customizations
end
end
end