Noir::RailsCallbackChain
The before_action chain a Rails action runs. A controller's own class
body is only half of it: before_action :authenticate_user! usually sits
in ApplicationController, and rails generate authentication (Rails 8)
installs before_action :require_authentication through an
Authentication concern ApplicationController includes. Shared by the
Rails analyzer (header/cookie reads in callbacks) and the auth tagger
(whether a callback guards the action).
Constants
before_action -> { doorkeeper_authorize! :read } (also ->(c) {,
lambda {, proc {, a bare { ... } block): the block body. Only
LineReader text holds one; Reader writes a block callback as the
one method it runs (see Reader#block_callback).
Class-body lines that change the callback chain a subclass inherits.
CanCanCan's class macros, which register a before_action that runs
authorize!(action, resource).
Gem controllers an app subclasses, which are not in the scan: the
callback lines their class bodies register. Doorkeeper's
authenticate_resource_owner! / authenticate_admin! run the app's
resource_owner_authenticator / admin_authenticator blocks.
The roots outside app/controllers that Rails autoloads the modules a
controller includes from (lib/, app/helpers/, ...).
The if: / unless: a skip carries (skip_before_action :x, if: -> { preview? }, or :if =>), through to the end of the statement.
The if cond / unless cond modifier Reader keeps on a macro call.
The lifted text of a callback whose except: splats something this
reader cannot expand: the guard stays, with a note.
only: [...] or the hash-rocket :only => [...] older apps write.
before_action :a, :b — the symbol callbacks it registers.
Constructors
A chain over the controllers files, plus the lib/ modules they (and
the modules those pull in) include: a controller's concern kept outside
app/controllers. others are the rest of the scan's Ruby files.
Class methods
An only: / except: naming its actions through a constant or a
method (only: PROTECTED) lists actions this reader cannot see: a
callback is taken to run for every action and a skip for none, which
keeps the guard.
The methods a before_action statement runs: its symbol callbacks, or
the one method its block calls. The left operand of && / || always
runs; a block under if / unless runs nothing unconditionally.
The action names an only: / except: option lists, in any of its
spellings: :show, [:show, :edit], %i[show edit], "show".
Matching ":#{action}" against the raw line missed the %i[ new create ]
the Rails generators write, and let :new match :new_session.
before_action :x, if: :cond? runs x only when the condition holds,
so a request can skip it: an opt-in such as
if: :limited_federation_mode? leaves the action public by default.
unless: is read as an exemption, the way except: is —
unless: :devise_controller? guards every app controller.
Whether a skip runs only on a condition, so the callback it names stays registered and that condition decides whether it runs.
An except: that splats a method this reader cannot expand
(*show_methods) may well list the action.
The statement starting at lines[idx], joined across the lines a
trailing comma or an unclosed [ / ( / { continues it onto:
before_action :x, # note with its only: [...] on the next line,
or only: [ closed lines later, applies to those actions only.
Trailing comments are dropped.
ponytail: a # inside a string on a callback line reads as a comment.
Instance methods
Indexes every class and module source declares, with its class-body
callback statements.
The controller class path declares.
ponytail: the first *Controller class in the file; a file holding
two controllers resolves both to the first.
The file's Facts, read once per source: off the AST, or by
LineReader when tree-sitter cannot place its classes.
Resolves Const.method to the literal list the app defines it as
(Discourse.filters); set by build over the files it was given.
Resolves Const.method to the literal list the app defines it as
(Discourse.filters); set by build over the files it was given.
Ruby resolves a bare constant from the innermost namespace outwards. Two
definitions of one name (reopened, or the same name in two apps of a
monorepo) are ambiguous and resolve to nothing. skip is the class
whose superclass is being looked up: module Admin; class ApplicationController < ApplicationController names the top-level one,
since the superclass is read before the new constant exists.
The paths Rails autoloads the modules indexed classes include
from, for each one no indexed file declares: include UploadHelpers in
Admin::UploadsController is lib/admin/upload_helpers.rb or
lib/upload_helpers.rb, nearest namespace first, as Zeitwerk looks.
What leaf's parent chain, its concerns and its own class body leave
active for action, honouring skip_before_action /
allow_unauthenticated_access and their only: / except: lists. The
leaf's own callbacks run last, so its skips remove what the parents
registered. A parent outside the scan (Devise::SessionsController)
ends the chain, after its GEM_CALLBACKS when it has any.