Module: CurrentScope
- Defined in:
- lib/current_scope.rb,
lib/current_scope/guard.rb,
lib/current_scope/engine.rb,
lib/current_scope/context.rb,
lib/current_scope/version.rb,
lib/current_scope/resolver.rb,
lib/current_scope/scopeable.rb,
app/models/current_scope/role.rb,
lib/current_scope/permissions.rb,
app/models/current_scope/event.rb,
lib/current_scope/test_helpers.rb,
lib/current_scope/configuration.rb,
app/models/current_scope/current.rb,
lib/current_scope/mutation_guard.rb,
lib/current_scope/gating_tripwire.rb,
lib/current_scope/permission_grid.rb,
lib/current_scope/gating_reflection.rb,
lib/current_scope/permission_catalog.rb,
app/models/current_scope/role_assignment.rb,
app/models/current_scope/role_permission.rb,
app/models/current_scope/application_record.rb,
app/helpers/current_scope/application_helper.rb,
app/controllers/current_scope/roles_controller.rb,
app/controllers/current_scope/events_controller.rb,
app/models/current_scope/scoped_role_assignment.rb,
app/controllers/current_scope/subjects_controller.rb,
app/controllers/current_scope/application_controller.rb,
lib/generators/current_scope/install/install_generator.rb,
app/controllers/current_scope/role_assignments_controller.rb,
app/controllers/current_scope/scoped_role_assignments_controller.rb
Defined Under Namespace
Modules: ApplicationHelper, Context, GatingTripwire, Generators, Guard, MutationGuard, Permissions, Scopeable, TestHelpers Classes: AccessDenied, ApplicationController, ApplicationRecord, Configuration, ConfigurationError, Current, Engine, Event, EventsController, GatingReflection, PermissionCatalog, PermissionGrid, Resolver, Role, RoleAssignment, RoleAssignmentsController, RolePermission, RolesController, ScopedRoleAssignment, ScopedRoleAssignmentsController, SubjectsController
Constant Summary collapse
- VERSION =
"0.3.0"
Class Method Summary collapse
-
.allowed?(action, subject:, record: nil, controller_path: nil, actor: nil, model: nil) ⇒ Boolean
The single entry point behind every allowed_to? call.
- .catalog ⇒ Object
- .config ⇒ Object
- .configure {|config| ... } ⇒ Object
-
.grant!(subject, role: nil) ⇒ Object
Bootstrap the first admin: assign a role (default: the full_access Owner) to
subjectas its one org-wide role. -
.label_for(record) ⇒ Object
THE human-label fallback chain, shared by the UI helpers (ApplicationHelper#current_scope_label) and the audit ledger (Event.label_for) — one definition, so a record can never render as "Apollo" on screen while being frozen into the ledger as "Project #7".
- .permission_key(action, record: nil, controller_path: nil) ⇒ Object
-
.record_impersonation_started!(subject) ⇒ Object
Impersonation boundary events.
- .record_impersonation_stopped!(subject) ⇒ Object
- .register_scopeable(model_name) ⇒ Object
- .reset_catalog! ⇒ Object
-
.reset_cross_controller_warnings! ⇒ Object
The cross-controller nudge warns once per site (see below).
- .reset_scopeable_registry! ⇒ Object
- .resolver ⇒ Object
-
.scope_for(subject:, model:, permission:) ⇒ Object
The list-side companion to allowed?.
-
.scopeable_registry ⇒ Object
Models that opted into the scoped-role picker via CurrentScope::Scopeable.
- .scopeable_resources ⇒ Object
-
.seed_defaults! ⇒ Object
Creates the two baseline roles every install needs: an Owner with full_access (present and future permissions) and a Member baseline.
Class Method Details
.allowed?(action, subject:, record: nil, controller_path: nil, actor: nil, model: nil) ⇒ Boolean
The single entry point behind every allowed_to? call.
action is either a full permission key ("admin/reports#approve") or a
bare action name resolved against record's route key, falling back to
controller_path.
103 104 105 106 107 108 109 110 111 |
# File 'lib/current_scope.rb', line 103 def allowed?(action, subject:, record: nil, controller_path: nil, actor: nil, model: nil) resolver.allow?( subject: subject, permission: (action, record: record, controller_path: controller_path), record: record, actor: actor, model: model ) end |
.catalog ⇒ Object
63 64 65 |
# File 'lib/current_scope.rb', line 63 def catalog @catalog ||= PermissionCatalog.new end |
.config ⇒ Object
51 52 53 |
# File 'lib/current_scope.rb', line 51 def config @config ||= Configuration.new end |
.configure {|config| ... } ⇒ Object
55 56 57 |
# File 'lib/current_scope.rb', line 55 def configure yield config end |
.grant!(subject, role: nil) ⇒ Object
Bootstrap the first admin: assign a role (default: the full_access Owner)
to subject as its one org-wide role. Idempotent — re-running sets the
same subject's org role to role rather than creating a duplicate (which
the one-role-per-subject uniqueness would reject anyway). Backs the
current_scope:grant rake task, so a fresh install doesn't need a console.
Seeds the default Owner/Member roles ONLY on the default path — the name promises "assign a role", so a caller granting an explicit role must not get a full-access Owner row created in their roles table as a side effect.
193 194 195 196 197 198 199 |
# File 'lib/current_scope.rb', line 193 def grant!(subject, role: nil) role ||= begin seed_defaults! Role.find_by!(name: "Owner") end RoleAssignment.find_or_initialize_by(subject: subject).tap { |a| a.update!(role: role) } end |
.label_for(record) ⇒ Object
THE human-label fallback chain, shared by the UI helpers (ApplicationHelper#current_scope_label) and the audit ledger (Event.label_for) — one definition, so a record can never render as "Apollo" on screen while being frozen into the ledger as "Project #7". Chain: the record's own current_scope_label (Scopeable provides one) → human identifiers (name/email/title) → "Model #id" → to_s. Returns nil for nil; callers choose their own nil presentation ("(none)" in views).
128 129 130 131 132 133 134 135 136 137 |
# File 'lib/current_scope.rb', line 128 def label_for(record) return if record.nil? return record.current_scope_label if record.respond_to?(:current_scope_label) name = record.try(:name).presence || record.try(:email).presence || record.try(:title).presence return name if name return "#{record.model_name.human} ##{record.id}" if record.respond_to?(:model_name) record.to_s end |
.permission_key(action, record: nil, controller_path: nil) ⇒ Object
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/current_scope.rb', line 139 def (action, record: nil, controller_path: nil) action = action.to_s return action if action.include?("#") if record.respond_to?(:model_name) route_key = record.model_name.route_key # When the current controller handles this record type (possibly under # a namespace — admin/reports for a Report), its path is the key the # Guard enforces, so prefer it: the view must agree with the gate. return "#{controller_path}##{action}" if controller_path&.split("/")&.last == route_key warn_on_cross_controller_derivation(action, route_key, controller_path) return "#{route_key}##{action}" end return "#{controller_path}##{action}" if controller_path raise ArgumentError, "cannot derive a permission key for #{action.inspect} — pass a record, " \ "a full \"controller#action\" string, or call from a controller/view" end |
.record_impersonation_started!(subject) ⇒ Object
Impersonation boundary events. The impersonated identity is an EXPLICIT argument (not read from the ambient pair): at act-as START the ambient actor still equals the effective user — Current re-resolves next request — so an ambient-only recorder would lose who was impersonated. Call these from the host's start/stop-impersonation endpoints.
166 167 168 169 |
# File 'lib/current_scope.rb', line 166 def record_impersonation_started!(subject) require_actor_method! Event.record!(event: "impersonation.started", target: subject) end |
.record_impersonation_stopped!(subject) ⇒ Object
171 172 173 174 |
# File 'lib/current_scope.rb', line 171 def record_impersonation_stopped!(subject) require_actor_method! Event.record!(event: "impersonation.stopped", target: subject) end |
.register_scopeable(model_name) ⇒ Object
87 88 89 |
# File 'lib/current_scope.rb', line 87 def register_scopeable(model_name) scopeable_registry << model_name.to_s end |
.reset_catalog! ⇒ Object
67 68 69 |
# File 'lib/current_scope.rb', line 67 def reset_catalog! @catalog = nil end |
.reset_cross_controller_warnings! ⇒ Object
The cross-controller nudge warns once per site (see below). That latch is per-process, so it must be clearable: a leaked one silently disarms the warning for every later test and makes the suite order-dependent. Also cleared on engine to_prepare, since a reload can change what's routed.
75 76 77 |
# File 'lib/current_scope.rb', line 75 def reset_cross_controller_warnings! @cross_controller_warned = nil end |
.reset_scopeable_registry! ⇒ Object
95 96 97 |
# File 'lib/current_scope.rb', line 95 def reset_scopeable_registry! @scopeable_registry = Set.new end |
.resolver ⇒ Object
59 60 61 |
# File 'lib/current_scope.rb', line 59 def resolver @resolver ||= Resolver.new end |
.scope_for(subject:, model:, permission:) ⇒ Object
The list-side companion to allowed?. Returns a chainable relation of the
records of model the subject may act on under permission — same
grants, same fail-closed rules as the per-record gate. permission is a
resolved key ("projects#index"); the mixin derives the default.
117 118 119 |
# File 'lib/current_scope.rb', line 117 def scope_for(subject:, model:, permission:) resolver.scope_for(subject: subject, model: model, permission: ) end |
.scopeable_registry ⇒ Object
Models that opted into the scoped-role picker via CurrentScope::Scopeable. Stored as class-name strings and resolved lazily so dev-mode reloading never pins a stale constant. Rebuilt from scratch on every engine to_prepare (see reset_scopeable_registry!).
83 84 85 |
# File 'lib/current_scope.rb', line 83 def scopeable_registry @scopeable_registry ||= Set.new end |
.scopeable_resources ⇒ Object
91 92 93 |
# File 'lib/current_scope.rb', line 91 def scopeable_resources scopeable_registry.map(&:constantize).sort_by(&:name) end |
.seed_defaults! ⇒ Object
Creates the two baseline roles every install needs: an Owner with full_access (present and future permissions) and a Member baseline. Call from db/seeds.rb.
179 180 181 182 |
# File 'lib/current_scope.rb', line 179 def seed_defaults! Role.find_or_create_by!(name: "Owner") { |r| r.full_access = true } Role.find_or_create_by!(name: "Member") end |