You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

project_nested_set.rb 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. # frozen_string_literal: true
  2. # Redmine - project management software
  3. # Copyright (C) 2006-2021 Jean-Philippe Lang
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. module Redmine
  19. module NestedSet
  20. module ProjectNestedSet
  21. def self.included(base)
  22. base.class_eval do
  23. belongs_to :parent, :class_name => self.name
  24. before_create :add_to_nested_set
  25. before_update(
  26. :move_in_nested_set,
  27. :if =>
  28. lambda {|project| project.parent_id_changed? || project.name_changed?}
  29. )
  30. before_destroy :destroy_children
  31. end
  32. base.extend ClassMethods
  33. base.send :include, Redmine::NestedSet::Traversing
  34. end
  35. private
  36. def target_lft
  37. siblings_rgt =
  38. self.class.where(:parent_id => parent_id).where("name < ?", name).maximum(:rgt)
  39. if siblings_rgt
  40. siblings_rgt + 1
  41. elsif parent_id
  42. parent_lft = self.class.where(:id => parent_id).pick(:lft)
  43. unless parent_lft
  44. raise "Project id=#{id} with parent_id=#{parent_id}: parent missing or without 'lft' value"
  45. end
  46. parent_lft + 1
  47. else
  48. 1
  49. end
  50. end
  51. def add_to_nested_set(lock=true)
  52. lock_nested_set if lock
  53. self.lft = target_lft
  54. self.rgt = lft + 1
  55. self.class.where("lft >= ? OR rgt >= ?", lft, lft).update_all(
  56. [
  57. "lft = CASE WHEN lft >= :lft THEN lft + 2 ELSE lft END, " \
  58. "rgt = CASE WHEN rgt >= :lft THEN rgt + 2 ELSE rgt END",
  59. {:lft => lft}
  60. ]
  61. )
  62. end
  63. def move_in_nested_set
  64. lock_nested_set
  65. reload_nested_set_values
  66. a = lft
  67. b = rgt
  68. c = target_lft
  69. unless c == a
  70. if c > a
  71. # Moving to the right
  72. d = c - (b - a + 1)
  73. scope =
  74. self.class.where(
  75. ["lft BETWEEN :a AND :c - 1 OR rgt BETWEEN :a AND :c - 1",
  76. {:a => a, :c => c}]
  77. )
  78. scope.update_all(
  79. [
  80. "lft = CASE WHEN lft BETWEEN :a AND :b THEN lft + (:d - :a) " \
  81. "WHEN lft BETWEEN :b + 1 AND :c - 1 THEN lft - (:b - :a + 1) ELSE lft END, " \
  82. "rgt = CASE WHEN rgt BETWEEN :a AND :b THEN rgt + (:d - :a) " \
  83. "WHEN rgt BETWEEN :b + 1 AND :c - 1 THEN rgt - (:b - :a + 1) ELSE rgt END",
  84. {:a => a, :b => b, :c => c, :d => d}
  85. ]
  86. )
  87. elsif c < a
  88. # Moving to the left
  89. scope =
  90. self.class.where(
  91. "lft BETWEEN :c AND :b OR rgt BETWEEN :c AND :b",
  92. {:a => a, :b => b, :c => c}
  93. )
  94. scope.update_all(
  95. [
  96. "lft = CASE WHEN lft BETWEEN :a AND :b THEN lft - (:a - :c) " \
  97. "WHEN lft BETWEEN :c AND :a - 1 THEN lft + (:b - :a + 1) ELSE lft END, " \
  98. "rgt = CASE WHEN rgt BETWEEN :a AND :b THEN rgt - (:a - :c) " \
  99. "WHEN rgt BETWEEN :c AND :a - 1 THEN rgt + (:b - :a + 1) ELSE rgt END",
  100. {:a => a, :b => b, :c => c, :d => d}
  101. ]
  102. )
  103. end
  104. reload_nested_set_values
  105. end
  106. end
  107. def destroy_children
  108. unless @without_nested_set_update
  109. lock_nested_set
  110. reload_nested_set_values
  111. end
  112. children.each {|c| c.send :destroy_without_nested_set_update}
  113. unless @without_nested_set_update
  114. self.class.where("lft > ? OR rgt > ?", lft, lft).update_all(
  115. [
  116. "lft = CASE WHEN lft > :lft THEN lft - :shift ELSE lft END, " \
  117. "rgt = CASE WHEN rgt > :lft THEN rgt - :shift ELSE rgt END",
  118. {:lft => lft, :shift => rgt - lft + 1}
  119. ]
  120. )
  121. end
  122. end
  123. def destroy_without_nested_set_update
  124. @without_nested_set_update = true
  125. destroy
  126. end
  127. def reload_nested_set_values
  128. self.lft, self.rgt = Project.where(:id => id).pick(:lft, :rgt)
  129. end
  130. def save_nested_set_values
  131. self.class.where(:id => id).update_all(:lft => lft, :rgt => rgt)
  132. end
  133. def move_possible?(project)
  134. new_record? || !is_or_is_ancestor_of?(project)
  135. end
  136. def lock_nested_set
  137. lock = true
  138. if /sqlserver/i.match?(self.class.connection.adapter_name)
  139. lock = "WITH (ROWLOCK HOLDLOCK UPDLOCK)"
  140. end
  141. self.class.order(:id).lock(lock).ids
  142. end
  143. def nested_set_scope
  144. self.class.order(:lft)
  145. end
  146. def same_nested_set_scope?(project)
  147. true
  148. end
  149. module ClassMethods
  150. def rebuild_tree!
  151. transaction do
  152. reorder(:id).lock.ids
  153. update_all(:lft => nil, :rgt => nil)
  154. rebuild_nodes
  155. end
  156. end
  157. private
  158. def rebuild_nodes(parent_id = nil)
  159. nodes = Project.where(:parent_id => parent_id).where(:rgt => nil, :lft => nil).reorder(:name)
  160. nodes.each do |node|
  161. node.send :add_to_nested_set, false
  162. node.send :save_nested_set_values
  163. rebuild_nodes node.id
  164. end
  165. end
  166. end
  167. end
  168. end
  169. end