Module: SuperAuth::Nestable

Included in:
Group, Resource, Role
Defined in:
lib/super_auth/nestable.rb

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/super_auth/nestable.rb', line 3

def self.included(base)
  base.extend ClassMethods

  base.plugin :rcte_tree, {
    cte_name: base.cte_name(base),
    ancestors: {
      dataset: -> { base.cte(self.id, :asc) }
    },
    descendants: {
      dataset: -> { base.cte(self.parent_id, :desc) }
    }
  }

  base.dataset_module do
    def roots
      self.where(parent_id: nil)
    end

    def trees
      model.cte(nil, :desc)
    end
  end
end

Instance Method Details

#before_destroyObject

A deleted node takes its compiled rows and its edges with it, in the transaction that deletes the row: runtime reads only the compiled table, and a row naming a node that no longer exists would keep granting until the next compile. Children are not touched. The foreign key refuses to orphan them, and whether they are re-rooted or deleted is the caller's decision (the editor re-roots them, deliberately). Rows compiled through this node for its descendants stay until the next compile, as after any other revocation.



58
59
60
61
62
63
# File 'lib/super_auth/nestable.rb', line 58

def before_destroy
  super
  column = :"#{model.singularize}_id"
  SuperAuth::Authorization.where(column => id).delete
  SuperAuth::Edge.where(column => id).delete
end

#validateObject

A node may not be its own parent, nor sit under one of its own descendants: either closes a parent_id cycle. The pair CTEs terminate on one (UNION), so a cycle does not hang a compile; it does something quieter and worse. Every node in a cycle is an ancestor of every other, so a grant on any of them reaches all of their subtrees — a container pointed at one of its own children turned a single per-record read into the container's whole membership, with nothing raised anywhere. Checked only when parent_id changes, by walking UP from the new parent: that is one row per level however large the subtree, and unlike the descendant walk it does not stop at a type-level node (SuperAuth::Resource.descend_from), so a cycle through one is caught too. assert_acyclic! covers writes that bypass the model.



39
40
41
42
43
44
45
46
47
48
# File 'lib/super_auth/nestable.rb', line 39

def validate
  super
  return if parent_id.nil? || !changed_columns.include?(:parent_id)

  if parent_id == id
    errors.add(:parent_id, "cannot be the node itself")
  elsif !new? && model.ancestor_pairs(of: [parent_id]).where(ancestor_id: id).count > 0
    errors.add(:parent_id, "is inside the node's own subtree, which would close a cycle")
  end
end