class DeepCloning::Clone
This is the main class responsible to duplicate an entire hierarchy
Constants
- VERSION
Public Class Methods
new(root, opts = { except: [], save_root: true })
click to toggle source
# File lib/deep_cloning.rb, line 11 def initialize(root, opts = { except: [], save_root: true }) @root = root @opts = opts @opts[:source] = [] @must_ignore = [] end
Public Instance Methods
leafs(cell)
click to toggle source
# File lib/deep_cloning.rb, line 107 def leafs(cell) node = cell.class arr = [] node.reflect_on_all_associations(:has_one).each do |c| if should_copy?(c.class_name) leaf = cell.send(c.name) arr << leaf if leaf&.persisted? and (@opts[:source].nil? or (not leaf.in? @opts[:source])) end end node.reflect_on_all_associations(:has_many).each do |c| if should_copy?(c.class_name) cell.send(c.name).find_in_batches.each do |leafs| leafs.each do |leaf| arr << leaf if leaf&.persisted? and (@opts[:source].nil? or (not leaf.in? @opts[:source])) end end end end arr end
parents(node)
click to toggle source
# File lib/deep_cloning.rb, line 128 def parents(node) parents = node.reflect_on_all_associations(:belongs_to) # name and class_name end
replicate() { |root, clone, :before_save| ... }
click to toggle source
# File lib/deep_cloning.rb, line 18 def replicate ActiveRecord::Base.transaction do # the block can be used to change the fields and fix eventual validation problems leafs_statuses = { "#{@root.class.name}_#{@root.id}" => true } if @opts[:save_root] clone = @root.dup yield(@root, clone, :before_save) if block_given? clone.save if clone.new_record? # avoid save again if saved on block yield(@root, clone, :after_save) if block_given? raise "#{clone.class} - #{clone.errors.full_messages.join(', ')}" if clone.errors.any? @opts[clone.class.name] = { @root.id => clone } end leafs(@root).each do |cell| @opts[:source] << cell if block_given? and not skip?(yield(cell, cell, :skip?)) end while @opts[:source].any? @cell = @opts[:source].detect do |node| node = yield(node, node, :prepare) if block_given? walk?(node) end unless @cell ap @opts[:source].map { |s| "#{s.id} - #{s.class.name}" } raise "Cannot duplicate the Hierarchy. You must ignore: #{@must_ignore.join(', ')}" end @opts[:source] -= [@cell] @opts[@cell.class.name] = {} unless @opts[@cell.class.name] next if @opts[@cell.class.name][@cell.id] # already cloned? if should_copy?(@cell.class.name) clone = @cell.dup parents(clone.class).each do |belongs_to| old_id = clone.send("#{belongs_to.name}_id") next unless old_id if belongs_to.options[:polymorphic] class_name = clone.send("#{belongs_to.name}").class.name if @opts[class_name] and @opts[class_name][old_id] clone.send("#{belongs_to.name}=", @opts[class_name][old_id]) end else if @opts[belongs_to.class_name] and @opts[belongs_to.class_name][old_id] clone.send("#{belongs_to.name}=", @opts[belongs_to.class_name][old_id]) end end end yield(@cell, clone, :before_save) if block_given? clone.save if clone.new_record? # avoid save again if saved on block yield(@cell, clone, :after_save) if block_given? raise "#{clone.class} - #{clone.errors.full_messages.join(', ')}" if clone.errors.any? @opts[clone.class.name][@cell.id] = clone end leafs(@cell).each do |cell| @opts[:source] << cell if block_given? and not skip?(yield(cell, cell, :skip?)) end leafs_statuses["#{@cell.class.name}_#{@cell.id}"] = true end end end
safe_child?(child, parent)
click to toggle source
# File lib/deep_cloning.rb, line 79 def safe_child?(child, parent) !child.respond_to?("#{parent.name}_id".to_sym) or child.send("#{parent.name}_id").nil? or @opts[parent.class_name][child.send("#{parent.name}_id")] or not should_copy?(parent.class_name) # replicated parent? end
should_copy?(klass)
click to toggle source
# File lib/deep_cloning.rb, line 132 def should_copy?(klass) return !(klass.in? @opts[:except]) if @opts[:including].nil? klass.in? @opts[:including] end
skip?(skip)
click to toggle source
# File lib/deep_cloning.rb, line 137 def skip?(skip) return skip if skip.in? [true, false] false # If the skip? moment is not passed, its set to false. end
walk?(cell)
click to toggle source
Need to check the relations instead the models only
# File lib/deep_cloning.rb, line 88 def walk?(cell) parents(cell.class).map do |p| if p.options[:polymorphic] if cell.respond_to?("#{p.name}_id".to_sym) and cell.send("#{p.name}_id") class_name = cell.send("#{p.name}").class.name @opts[class_name] = {} unless @opts[class_name] @opts[class_name][cell.send("#{p.name}_id")] or not should_copy?(class_name) # replicated parent? else true end else @opts[p.class_name] = {} unless @opts[p.class_name] safe_child = safe_child?(cell, p) @must_ignore << p.class_name unless safe_child safe_child end end.all?(&:present?) end