Module: CurrentScope::ApplicationHelper

Defined in:
app/helpers/current_scope/application_helper.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.warn_subject_label_broken_once(error) ⇒ Object

The configured value could not even be classified — see the rescue in configured_subject_label. Keyed by the error's class, and says nothing about the label itself, because reading the label is what failed.



184
185
186
187
188
189
190
191
192
# File 'app/helpers/current_scope/application_helper.rb', line 184

def warn_subject_label_broken_once(error)
  return unless new_subject_label_warning?(:broken, safe_class(error))

  Rails.logger&.warn(
    "[CurrentScope] config.subject_label could not be read — #{safe_class(error)}: " \
    "#{safe_message(error)}. Every subject is falling back to the default label. Set it to " \
    "a Symbol (e.g. :email), a Proc taking the subject, or nil."
  )
end

.warn_subject_label_raised_once(label, error) ⇒ Object



170
171
172
173
174
175
176
177
178
179
# File 'app/helpers/current_scope/application_helper.rb', line 170

def warn_subject_label_raised_once(label, error)
  return unless new_subject_label_warning?(:raised, label)

  Rails.logger&.warn(
    "[CurrentScope] config.subject_label raised #{safe_class(error)}: #{safe_message(error)} " \
    "— that subject fell back to the default label rather than erroring the page. A " \
    "subject_label must be total: it runs for every subject, including ones with nil or " \
    "blank attributes."
  )
end

.warn_unknown_subject_label_once(label) ⇒ Object

Warn ONCE per (reason, label): a misconfigured subject_label is the same mistake on every row, so one line is a signal and one-per-subject is noise the admin will scroll past. Keyed by the label value, so changing the config in dev warns again for the new value.

Always-on rather than behind a warn_on_* flag (cf. warn_on_nil_sod_record, which defaults off because a nil record is often legitimate): a label the subject can't answer, or one that raises, is unambiguously a mistake. Mirrors Event.warn_missing_events_table_once.

Says "this subject" rather than "subjects": under STI some subclasses may answer and others not, and warn-once means the first non-responder would otherwise speak for all of them.



145
146
147
148
149
150
151
152
153
154
# File 'app/helpers/current_scope/application_helper.rb', line 145

def warn_unknown_subject_label_once(label)
  return unless new_subject_label_warning?(:unknown, label)

  Rails.logger&.warn(
    "[CurrentScope] config.subject_label is #{label.inspect}, but this subject does not " \
    "respond to it — it is falling back to the default label. If no subject responds to it, " \
    "this config does nothing. Set it to a method the subject responds to (e.g. :email), a " \
    "Proc, or nil."
  )
end

.warn_unusable_subject_label_once(label) ⇒ Object

Not a Symbol, String, Proc, or nil — so it can't name a method and can't be called. Reports the TYPE, not the value: this is the one warning whose subject is an arbitrary host object, and calling inspect on it could itself raise — inside the very path that exists to stop a raise.



160
161
162
163
164
165
166
167
168
# File 'app/helpers/current_scope/application_helper.rb', line 160

def warn_unusable_subject_label_once(label)
  return unless new_subject_label_warning?(:unusable, label.class)

  Rails.logger&.warn(
    "[CurrentScope] config.subject_label is a #{label.class} — it can't name a method and " \
    "can't be called, so every subject is falling back to the default label. Set it to a " \
    "Symbol (e.g. :email), a Proc taking the subject, or nil."
  )
end

Instance Method Details

#configured_subject_label(subject) ⇒ Object

The configured subject_label (Symbol or Proc) applied to a subject, or nil when unset, when it resolves to a blank value, or when it fails.

subject_label is arbitrary host code running once per row on the admin's main tool for granting and reviewing roles. One subject with incomplete data (a Proc doing u.email.upcase on a subject whose email is nil) must not take the whole page down — the same intent the holder-label helpers below already encode for stale polymorphic types. A failure here costs one label; a raise costs the page.



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
71
72
73
74
75
76
77
78
79
80
# File 'app/helpers/current_scope/application_helper.rb', line 45

def configured_subject_label(subject)
  label = CurrentScope.config.subject_label
  return if label.nil?
  return resolve_subject_label(label) { label.call(subject) } if label.respond_to?(:call)

  # Only a Symbol or String names a method. Checked BEFORE respond_to?,
  # which raises TypeError on anything else ("false is not a symbol nor a
  # string") — and that raise would land outside the rescue below and 500
  # the page, which is the bug this method exists to prevent, reached
  # through a different door.
  unless label.is_a?(Symbol) || label.is_a?(String)
    CurrentScope::ApplicationHelper.warn_unusable_subject_label_once(label)
    return
  end

  if subject.respond_to?(label)
    resolve_subject_label(label) { subject.public_send(label) }
  else
    # Not a mistake we can render around: this config does nothing for EVERY
    # subject, silently, which is why it needs saying out loud once.
    CurrentScope::ApplicationHelper.warn_unknown_subject_label_once(label)
    nil
  end
rescue StandardError => e
  # Last resort, and the reason it exists: every branch above calls a method
  # ON the host's config value to classify it — `nil?`, `respond_to?`,
  # `is_a?`. A pathological value breaks the classification itself, before
  # any specific guard can classify it, so the guards cannot be where this
  # is caught. A BasicObject has no #nil?; a delegator can raise from
  # respond_to?.
  #
  # Deliberately does NOT touch `label`: it is the thing that just proved it
  # cannot be touched. The warning is keyed off the error alone.
  CurrentScope::ApplicationHelper.warn_subject_label_broken_once(e)
  nil
end

#current_scope_gid_label(gid) ⇒ Object

Best-effort label for a stored GID string (event actor/subject). Falls back to the raw GID when the record is gone — the ledger outlives the identities it names.



100
101
102
103
104
105
106
107
# File 'app/helpers/current_scope/application_helper.rb', line 100

def current_scope_gid_label(gid)
  record = GlobalID::Locator.locate(gid)
  record ? current_scope_label(record) : gid
rescue ActiveRecord::RecordNotFound, NameError
  # locate RAISES for a deleted record or renamed class (nil is only for
  # unparseable strings) — same contract the holder helpers rescue above.
  gid
end

#current_scope_holder_resource_label(scoped_assignment) ⇒ Object



91
92
93
94
95
# File 'app/helpers/current_scope/application_helper.rb', line 91

def current_scope_holder_resource_label(scoped_assignment)
  current_scope_label(scoped_assignment.resource)
rescue NameError, ActiveRecord::RecordNotFound
  "#{scoped_assignment.resource_type} ##{scoped_assignment.resource_id}"
end

#current_scope_holder_subject_label(assignment) ⇒ Object

Members-list labels that survive a stale/renamed polymorphic type — a removed subject or resource class must not 500 the page; fall back to "Type #id" the way the audit ledger does.



85
86
87
88
89
# File 'app/helpers/current_scope/application_helper.rb', line 85

def current_scope_holder_subject_label(assignment)
  current_scope_subject_label(assignment.subject)
rescue NameError, ActiveRecord::RecordNotFound
  "#{assignment.subject_type} ##{assignment.subject_id}"
end

#current_scope_label(record) ⇒ Object

Best-effort human label for any host subject/resource. Delegates to the one shared chain (CurrentScope.label_for) — the same definition the audit ledger denormalizes (see Event.label_for), so the picker, the chips, and the ledger agree by construction, not by parallel maintenance.



7
8
9
10
11
# File 'app/helpers/current_scope/application_helper.rb', line 7

def current_scope_label(record)
  return "(none)" if record.nil?

  CurrentScope.label_for(record)
end

#current_scope_subject_label(subject) ⇒ Object

Human label for a subject (user/account), honouring config.subject_label so a host on UUID keys can show email or a full name instead of an opaque id. Falls back to the best-effort current_scope_label.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'app/helpers/current_scope/application_helper.rb', line 16

def current_scope_subject_label(subject)
  return "(none)" if subject.nil?

  # A configured label that resolves to nil/blank (e.g. :email on a subject
  # with no email yet) must not render as an empty row — fall through to the
  # default human-identifier chain rather than showing "".
  configured = configured_subject_label(subject)
  return configured if configured

  # Default: a subject is a person, so prefer human identifiers over a
  # generic "Class #id" — including when the model includes Scopeable, whose
  # current_scope_label is id-based. Covers `email` and Rails 8 auth's
  # `email_address`. Config overrides this entirely.
  full_name = [ subject.try(:first_name), subject.try(:last_name) ].compact.join(" ").presence
  # .presence on each identifier: a stored empty/whitespace email ("") is
  # truthy in Ruby and would otherwise short-circuit to a blank label.
  subject.try(:email).presence || subject.try(:email_address).presence ||
    subject.try(:name).presence || full_name || current_scope_label(subject)
end