File: prunable.rb

package info (click to toggle)
ruby-awesome-nested-set 3.8.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 172 kB
  • sloc: ruby: 1,044; makefile: 2
file content (91 lines) | stat: -rw-r--r-- 3,501 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
module CollectiveIdea #:nodoc:
  module Acts #:nodoc:
    module NestedSet #:nodoc:
      module Model
        module Prunable

          # Prunes a branch off of the tree, shifting all of the elements on the right
          # back to the left so the counts still work.
          def destroy_descendants
            return if right.nil? || left.nil? || skip_before_destroy

            in_tenacious_transaction do
              # Rescue from +ActiveRecord::RecordNotFound+ error as there may be a case
              # that an +object+ has already been destroyed by its parent, but objects that are
              # in memory are not aware about this.
              begin
                reload_nested_set
              rescue ActiveRecord::RecordNotFound
                self.skip_before_destroy = true
                return true
              end
              # select the rows in the model that extend past the deletion point and apply a lock
              nested_set_scope.right_of(left).select(primary_id).lock(true)

              return false unless destroy_or_delete_descendants

              # update lefts and rights for remaining nodes
              update_siblings_for_remaining_nodes

              # Reload is needed because children may have updated their parent (self) during deletion.
              reload

              # Don't allow multiple calls to destroy to corrupt the set
              self.skip_before_destroy = true
            end
          end

          # Use reverse to delete from deepest child to parent in order to respect any possible foreign keys
          def decendants_to_destroy_in_order
            descendants.reverse
          end

          def destroy_or_delete_descendants
            if acts_as_nested_set_options[:dependent] == :destroy
              decendants_to_destroy_in_order.each do |model|
                model.skip_before_destroy = true
                model.destroy
              end
            elsif acts_as_nested_set_options[:dependent] == :restrict_with_exception
              raise ActiveRecord::DeleteRestrictionError.new(:children) unless leaf?
              return true
            elsif acts_as_nested_set_options[:dependent] == :restrict_with_error
              unless leaf?
                record = self.class.human_attribute_name(:children).downcase
                if Rails::VERSION::MAJOR < 5
                  errors.add(:base, :"restrict_dependent_destroy.many", record: record)
                  return false
                else
                  errors.add(:base, :"restrict_dependent_destroy.has_many", record: record)
                  throw :abort
                end
              end
              return true
             elsif acts_as_nested_set_options[:dependent] == :nullify
               descendants.update_all(parent_column_name => nil)
             else
              descendants.delete_all
            end
          end

          def update_siblings_for_remaining_nodes
            update_siblings(:left)
            update_siblings(:right)
          end

          def update_siblings(direction)
            full_column_name = send("quoted_#{direction}_column_full_name")
            column_name = send("quoted_#{direction}_column_name")

            nested_set_scope.where(["#{full_column_name} > ?", right]).
              update_all(["#{column_name} = (#{column_name} - ?)", diff])
          end

          def diff
            right - left + 1
          end
        end
      end
    end
  end
end