group.rb 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class Group < ApplicationModel
  3. include HasDefaultModelUserRelations
  4. include CanBeImported
  5. include HasActivityStreamLog
  6. include ChecksClientNotification
  7. include ChecksHtmlSanitized
  8. include HasHistory
  9. include HasObjectManagerAttributes
  10. include HasCollectionUpdate
  11. include HasSearchIndexBackend
  12. include Group::Assets
  13. scope :sorted, -> { order(:name) }
  14. belongs_to :email_address, optional: true
  15. belongs_to :signature, optional: true
  16. belongs_to :parent, optional: true, class_name: 'Group'
  17. # workflow checks should run after before_create and before_update callbacks
  18. include ChecksCoreWorkflow
  19. core_workflow_screens 'create', 'edit'
  20. core_workflow_admin_screens 'create', 'edit'
  21. before_validation :ensure_name_last_and_parent, :check_max_depth
  22. before_save :update_path
  23. after_save :update_path_children
  24. validates :name, uniqueness: { case_sensitive: false }
  25. validates :name_last, presence: true, format: { without: %r{::}, message: __('No double colons (::) allowed, reserved delimiter') }
  26. validates :note, length: { maximum: 250 }
  27. sanitized_html :note, no_images: true
  28. activity_stream_permission 'admin.group'
  29. @@max_depth = nil # rubocop:disable Style/ClassVars
  30. def guess_name_last_and_parent
  31. split = name.split('::')
  32. self.name_last = split[-1]
  33. return if parent_id
  34. return if split.size == 1
  35. check_parent = Group.find_by(name: split[..-2].join('::'))
  36. if check_parent.blank?
  37. errors.add(:name, 'contains invalid path')
  38. raise ActiveRecord::RecordInvalid, self
  39. end
  40. self.parent = check_parent
  41. end
  42. def ensure_name_last_and_parent
  43. if persisted?
  44. return if name_last_changed?
  45. return if !name_changed?
  46. else
  47. return if name_last.present?
  48. return if name.blank?
  49. end
  50. guess_name_last_and_parent
  51. end
  52. def check_max_depth
  53. old_depth = if persisted?
  54. self.class.find(id).depth
  55. else
  56. 0
  57. end
  58. new_depth = depth(force: true)
  59. return if new_depth < self.class.max_depth && all_children(force: true).all? { |child| new_depth + (child.depth - old_depth) < self.class.max_depth }
  60. raise Exceptions::UnprocessableEntity, __('This group or its children exceed the allowed nesting depth.')
  61. end
  62. def update_path
  63. self.name = path(force: true).join('::')
  64. end
  65. def update_path_children
  66. return if !saved_change_to_attribute?(:parent_id) && !saved_change_to_attribute?(:name_last)
  67. all_children.each do |child|
  68. child.update_path
  69. child.save!
  70. end
  71. end
  72. def all_parents(force: false)
  73. Rails.cache.fetch("Group/#{Group.latest_change}/all_parents/#{id}", force: force) do
  74. result = []
  75. check_next = self
  76. (self.class.max_depth * 2).times do
  77. break if check_next.parent.blank?
  78. result << check_next.parent
  79. check_next = check_next.parent
  80. end
  81. result
  82. end
  83. end
  84. def all_children(force: false)
  85. return [] if !persisted?
  86. Rails.cache.fetch("Group/#{Group.latest_change}/all_children/#{id}", force: force) do
  87. result = []
  88. check_next = [self]
  89. (self.class.max_depth * 2).times do
  90. break if check_next.blank?
  91. children = self.class.where(parent_id: check_next)
  92. result += children
  93. check_next = children
  94. end
  95. result
  96. end
  97. end
  98. def self.all_max_depth(force: false)
  99. Rails.cache.fetch("Group/#{Group.latest_change}/all_max_depth", force: force) do
  100. Group.select { |group| group.depth >= max_depth }
  101. end
  102. end
  103. def depth(force: false)
  104. all_parents(force: force).count
  105. end
  106. def fullname
  107. path.join(' › ')
  108. end
  109. def path(force: false)
  110. all_parents(force: force).map(&:name_last).reverse + [name_last]
  111. end
  112. def self.max_depth
  113. return @@max_depth if @@max_depth
  114. @@max_depth = ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] == 'mysql2' ? 6 : 10 # rubocop:disable Style/ClassVars
  115. end
  116. end