Class: CurrentScope::ScopedRoleAssignment

Inherits:
ApplicationRecord show all
Includes:
StorableKeys
Defined in:
app/models/current_scope/scoped_role_assignment.rb

Overview

A role held on ONE specific record: "Editor of Project #7" grants nothing on Project #8. Never touches the subject's org-wide role — the two are independent axes.

Rows survive host resource destruction by design (polymorphic, no dependent:). Since #65 those orphan grants open nothing (empty list = 403) but still rendered like live access until labeled (#90).

Class Method Summary collapse

Instance Method Summary collapse

Methods included from StorableKeys

#current_scope_resolved_record

Class Method Details

.preload_resolvable_resources!(assignments) ⇒ Object

Batch-load polymorphic resources for resolvable types only. A global includes(:resource) NameErrors when any resource_type is stale; this constantizes per type and skips unresolvable ones so they stay lazy and orphaned_resource? labels them inert (#90 / PR #104 review).



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'app/models/current_scope/scoped_role_assignment.rb', line 26

def self.preload_resolvable_resources!(assignments)
  list = Array(assignments)
  return list if list.empty?

  list.group_by(&:resource_type).each do |type, rows|
    next if type.blank?

    # Same canonical resolver the key guard uses: a namespaced, shortened or
    # custom polymorphic token must not read as missing here while passing
    # validation there.
    klass = CurrentScope.polymorphic_class(type, owner: self)
    next if klass.nil?
    next unless klass.respond_to?(:where)

    # Look up by the DECLARED primary key, and index on the string form of it.
    # resource_id is a string column (#151), so a record keyed on an integer
    # yields 1 while the grant holds "1"; matching them raw silently misses and
    # labels a live grant inert. `where` casts the strings back to the column's
    # own type, so the query is still correct on every adapter.
    key = klass.primary_key
    unless key.is_a?(String)
      mark_resources_loaded(rows, {})
      next
    end

    # Only ids that are legal keys for THIS model reach the query. A legacy
    # collapsed value ("7", left by the pre-#151 integer column) sent at a
    # PostgreSQL uuid column does not come back empty — it RAISES
    # `invalid input syntax for type uuid`, and the console page an
    # administrator opens to find these very grants 500s instead of listing
    # them as inert. Same rule the resolver applies (CurrentScope
    # .canonical_key?); a dropped id simply stays unloaded, which is exactly
    # what orphaned_resource? then labels.
    ids = rows.map(&:resource_id).uniq.select { |id| CurrentScope.canonical_key?(klass, id) }

    # When NOTHING in the group is a legal key, skip the query but still mark
    # every row loaded-as-nil below. Returning early instead would leave the
    # associations lazy, so the first orphaned_resource? call would go and do
    # per-row exactly the load this method exists to do safely and in bulk.
    records = ids.empty? ? {} : klass.where(key => ids).index_by { |r| r[key].to_s }
    mark_resources_loaded(rows, records)
  end

  list
end

Instance Method Details

#orphaned_resource?Boolean

True when the pointed-at resource is gone (deleted row or unresolvable type). The grant is inert for authorization (#65) but still a console row. Memoized: views call this plus the label helper once each; a reset-every- call would re-query the resource twice per row (PR #104 cubic follow-up).

Returns:

  • (Boolean)


76
77
78
79
80
81
82
83
84
85
86
87
# File 'app/models/current_scope/scoped_role_assignment.rb', line 76

def orphaned_resource?
  return @orphaned_resource if defined?(@orphaned_resource)

  @orphaned_resource =
    if resource_id.blank?
      false
    else
      current_scope_resolved_record("resource").nil?
    end
rescue NameError, ActiveRecord::RecordNotFound
  @orphaned_resource = true
end