Module: SuperAuth::Nestable
Defined Under Namespace
Modules: ClassMethods
Class Method Summary collapse
Instance Method Summary collapse
-
#before_destroy ⇒ Object
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.
-
#validate ⇒ Object
A node may not be its own parent, nor sit under one of its own descendants: either closes a parent_id cycle.
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_destroy ⇒ Object
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 |
#validate ⇒ Object
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 |