Module: ClosureTree::HierarchyMaintenance

Extended by:
ActiveSupport::Concern
Defined in:
lib/closure_tree/hierarchy_maintenance.rb

Defined Under Namespace

Modules: ClassMethods

Instance Method Summary collapse

Instance Method Details

#_ct_after_saveObject



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 37

def _ct_after_save
  if changes[_ct.parent_column_name] || @was_new_record
    rebuild!
  end
  if changes[_ct.parent_column_name] && !@was_new_record
    # Resetting the ancestral collections addresses
    # https://github.com/mceachen/closure_tree/issues/68
    ancestor_hierarchies.reload
    self_and_ancestors.reload
  end
  @was_new_record = false # we aren't new anymore.
  @_ct_skip_sort_order_maintenance = false # only skip once.
  true # don't cancel anything.
end

#_ct_before_destroyObject



52
53
54
55
56
57
58
59
60
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 52

def _ct_before_destroy
  _ct.with_advisory_lock do
    delete_hierarchy_references
    if _ct.options[:dependent] == :nullify
      self.class.find(self.id).children.each { |c| c.rebuild! }
    end
  end
  true # don't prevent destruction
end

#_ct_before_saveObject



32
33
34
35
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 32

def _ct_before_save
  @was_new_record = new_record?
  true # don't cancel the save
end

#_ct_skip_cycle_detection!Object



14
15
16
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 14

def _ct_skip_cycle_detection!
  @_ct_skip_cycle_detection = true
end

#_ct_skip_sort_order_maintenance!Object



18
19
20
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 18

def _ct_skip_sort_order_maintenance!
  @_ct_skip_sort_order_maintenance = true
end

#_ct_validateObject



22
23
24
25
26
27
28
29
30
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 22

def _ct_validate
  if !@_ct_skip_cycle_detection &&
    !new_record? && # don't validate for cycles if we're a new record
    changes[_ct.parent_column_name] && # don't validate for cycles if we didn't change our parent
    parent.present? && # don't validate if we're root
    parent.self_and_ancestors.include?(self) # < this is expensive :\
    errors.add(_ct.parent_column_sym, I18n.t('closure_tree.loop_error', default: 'You cannot add an ancestor as a descendant'))
  end
end

#delete_hierarchy_referencesObject



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 88

def delete_hierarchy_references
  _ct.with_advisory_lock do
    # The crazy double-wrapped sub-subselect works around MySQL's limitation of subselects on the same table that is being mutated.
    # It shouldn't affect performance of postgresql.
    # See http://dev.mysql.com/doc/refman/5.0/en/subquery-errors.html
    # Also: PostgreSQL doesn't support INNER JOIN on DELETE, so we can't use that.
    _ct.connection.execute <<-SQL.strip_heredoc
      DELETE FROM #{_ct.quoted_hierarchy_table_name}
      WHERE descendant_id IN (
        SELECT DISTINCT descendant_id
        FROM (SELECT descendant_id
          FROM #{_ct.quoted_hierarchy_table_name}
          WHERE ancestor_id = #{_ct.quote(id)}
             OR descendant_id = #{_ct.quote(id)}
        ) AS x )
    SQL
  end
end

#rebuild!(called_by_rebuild = false) ⇒ Object



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
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 62

def rebuild!(called_by_rebuild = false)
  _ct.with_advisory_lock do
    delete_hierarchy_references unless @was_new_record
    hierarchy_class.create!(:ancestor => self, :descendant => self, :generations => 0)
    unless root?
      _ct.connection.execute <<-SQL.strip_heredoc
        INSERT INTO #{_ct.quoted_hierarchy_table_name}
          (ancestor_id, descendant_id, generations)
        SELECT x.ancestor_id, #{_ct.quote(_ct_id)}, x.generations + 1
        FROM #{_ct.quoted_hierarchy_table_name} x
        WHERE x.descendant_id = #{_ct.quote(_ct_parent_id)}
      SQL
    end

    if _ct.order_is_numeric? && !@_ct_skip_sort_order_maintenance
      _ct_reorder_prior_siblings_if_parent_changed
      # Prevent double-reordering of siblings:
      _ct_reorder_siblings if !called_by_rebuild
    end

    children.each { |c| c.rebuild!(true) }

    _ct_reorder_children if _ct.order_is_numeric? && children.present?
  end
end