overrides awesome_nested_set methods for issues to allow for fixing the

root_id

The root_id provides the scope for a nested_set tree. The implicit
constraint of the Issue implementation is that there is but one tree per
scope. Or in other words, the root_id is part of the nested_set
attributes for an issue.

This is not the case for Project, which is also using nested_set but
does not set a scope. Project's are ordered that way.

The root_id should always point to the root of an issue tree.
awesome_nested_set only looks for nodes without a parent in the provided
scope to determine root nodes. For example, if the root_id of an issue
should point to the node itself, this issue is considered to be a root
node. This is regardless of the value of the parent_id. For such a node,
nested_set methods will provide wrong issues when queried for root,
ancestors and similar methods.

Fixing a missconfigured root_id attribute is achived by traversing up
the parent chain (parent_id) till an issue without a parent is found.
The parent_id attribute is considered to be correct.

The root_id is not really necessary for awesome_nested_set to work. But
employing the field should be beneficial performancewise. Updating an
issue tree requires less nodes' left/right attribute to be altered when
the forrest is scoped.
pull/5/head
Jens Ulferts 12 years ago
parent 47a89b8bfa
commit 3adca91704
  1. 73
      app/models/issue.rb

@ -91,6 +91,26 @@ class Issue < ActiveRecord::Base
}
}
# find all issues
# * having set a parent_id where the root_id
# 1) points to self
# 2) points to an issue with a parent
# 3) points to an issue having a different root_id
# * having not set a parent_id but a root_id
# This unfortunately does not find the issue with the id 3 in the following example
# | id | parent_id | root_id |
# | 1 | | 1 |
# | 2 | 1 | 2 |
# | 3 | 2 | 2 |
# This would only be possible using recursive statements
named_scope :invalid_root_ids, { :conditions => "(issues.parent_id IS NOT NULL AND " +
"(issues.root_id = issues.id OR " +
"(issues.root_id = parent_issues.id AND parent_issues.parent_id IS NOT NULL) OR " +
"(issues.root_id != parent_issues.root_id))" +
") OR " +
"(issues.parent_id IS NULL AND issues.root_id != issues.id)",
:joins => "LEFT OUTER JOIN issues parent_issues ON parent_issues.id = issues.parent_id" }
before_create :default_assign
before_save :close_duplicates, :update_done_ratio_from_issue_status
after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes
@ -718,6 +738,59 @@ class Issue < ActiveRecord::Base
projects
end
# method from acts_as_nested_set
def self.valid?
super && invalid_root_ids.empty?
end
def self.all_invalid
(super + invalid_root_ids).uniq
end
def self.rebuild_silently!(roots = nil)
invalid_root_ids_to_fix = if roots.is_a? Array
roots
elsif roots.present?
[roots]
else
[]
end
known_issue_parents = Hash.new do |hash, ancestor_id|
hash[ancestor_id] = Issue.find_by_id(ancestor_id)
end
fix_known_invalid_root_ids = lambda do
issues = invalid_root_ids
issues_roots = []
issues.each do |issue|
# At this point we can not trust nested set methods as the root_id is invalid.
# Therefore we trust the parent_issue_id to fetch all ancestors until we find the root
ancestor = issue
while ancestor.parent_issue_id do
ancestor = known_issue_parents[ancestor.parent_issue_id]
end
issues_roots << ancestor
if invalid_root_ids_to_fix.empty? || invalid_root_ids_to_fix.map(&:id).include?(ancestor.id)
Issue.update_all({ :root_id => ancestor.id },
{ :id => issue.id })
end
end
fix_known_invalid_root_ids.call unless (issues_roots.map(&:id) & invalid_root_ids_to_fix.map(&:id)).empty?
end
fix_known_invalid_root_ids.call
super
end
private
def update_nested_set_attributes

Loading…
Cancel
Save