Class: DeepCloning::Clone

Inherits:
Object
  • Object
show all
Defined in:
lib/deep_cloning.rb

Overview

This is the main class responsible to duplicate an entire hierarchy

Constant Summary collapse

VERSION =
'0.2.2'.freeze

Instance Method Summary collapse

Constructor Details

#initialize(root, opts = { except: [], save_root: true }) ⇒ Clone

Returns a new instance of Clone.



11
12
13
14
15
16
# File 'lib/deep_cloning.rb', line 11

def initialize(root, opts = { except: [], save_root: true })
  @root = root
  @opts = opts
  @opts[:source] = []
  @must_ignore = []
end

Instance Method Details

#leafs(cell) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# 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) ⇒ Object



128
129
130
# File 'lib/deep_cloning.rb', line 128

def parents(node)
  parents = node.reflect_on_all_associations(:belongs_to) # name and class_name
end

#replicateObject



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 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) ⇒ Boolean

Returns:

  • (Boolean)


79
80
81
82
83
84
85
# 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) ⇒ Boolean

Returns:

  • (Boolean)


132
133
134
135
# 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) ⇒ Boolean

Returns:

  • (Boolean)


137
138
139
140
# 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) ⇒ Boolean

Need to check the relations instead the models only

Returns:

  • (Boolean)


88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# 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