Module: ClosureTree::HierarchyMaintenance

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

Instance Method Summary collapse

Instance Method Details

#_ct_after_saveObject



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 43

def _ct_after_save
  if saved_changes[_ct.parent_column_name] || @was_new_record
    rebuild!
  elsif _ct.order_is_numeric?
    if _ct.scope_changed?(self)
      # Scope changed without parent change - reorder old scope's siblings
      _ct_reorder_prior_siblings_if_parent_changed
      _ct_reorder_siblings
    elsif saved_changes[_ct.order_column_sym]
      min = saved_changes[_ct.order_column_sym].min
      _ct_reorder_siblings(min.negative? ? nil : min)
    end
  end
  if saved_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.
  true # don't cancel anything.
end

#_ct_before_destroyObject



66
67
68
69
70
71
72
73
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 66

def _ct_before_destroy
  _ct.with_advisory_lock(self) do
    _ct_adopt_children_to_grandparent if _ct.options[:dependent] == :adopt
    delete_hierarchy_references
    self.class.find(id).children.find_each(&:rebuild!) if _ct.options[:dependent] == :nullify
  end
  true # don't prevent destruction
end

#_ct_before_saveObject



38
39
40
41
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 38

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

#_ct_skip_cycle_detection!Object



16
17
18
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 16

def _ct_skip_cycle_detection!
  @_ct_skip_cycle_detection = true
end

#_ct_skip_sort_order_maintenance!Object



20
21
22
23
24
25
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 20

def _ct_skip_sort_order_maintenance!
  ActiveSupport::Deprecation.new.warn(
    '_ct_skip_sort_order_maintenance! is deprecated and will be removed in the next major version. ' \
    'Sort order maintenance is now handled automatically.'
  )
end

#_ct_validateObject



27
28
29
30
31
32
33
34
35
36
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 27

def _ct_validate
  if !(defined? @_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



114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/closure_tree/hierarchy_maintenance.rb', line 114

def delete_hierarchy_references
  _ct.with_advisory_lock(self) 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.

    hierarchy_table = hierarchy_class.arel_table
    delete_query = _ct.build_hierarchy_delete_query(hierarchy_table, id)
    _ct.connection.execute(_ct.to_sql_with_connection(delete_query))
  end
end

#rebuild!(called_by_rebuild = false) ⇒ Object



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

def rebuild!(called_by_rebuild = false)
  _ct.with_advisory_lock(self) do
    delete_hierarchy_references unless (defined? @was_new_record) && @was_new_record
    hierarchy_class.create!(ancestor: self, descendant: self, generations: 0)
    unless root?
      _ct.connection.execute <<-SQL.squish
        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_reorder_prior_siblings_if_parent_changed
      # Prevent double-reordering of siblings:
      _ct_reorder_siblings unless called_by_rebuild
    end

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

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