Skip to main content

Boot and extension

Overview​

For: gem maintainers wiring Decidim::RestFull::Extension.register — read this after Recipe when routes or scopes do not appear.

Boot sequence​

sequenceDiagram
participant Gem as FeatureEngine
participant Ext as Extension
participant Registry as RouteRegistry
participant Core as CoreEngine
participant Reloader as RoutesReloader
participant DK as Doorkeeper

Gem->>Ext: Extension.register (initializer before draw_routes)
Ext->>Registry: draw_api_routes (route blocks)
Ext->>Ext: oauth_scopes → doorkeeper_optional_scopes
Core->>Core: rest_full.draw_routes → Routes.mount! (RouteSet#append)
Reloader->>Reloader: clear! + load routes.rb + finalize!
Note over Reloader,Registry: append blocks run inside finalize! (Devise configures Warden once)
Core->>DK: DoorkeeperConfig.merge_optional_scopes! on late Extension.register
Note over Gem,DK: Late Extension.register → append_pending! + merge! if app initialized

Initializer anchors​

Register your engine initializer before core mounts routes and merges scopes:

initializer "rest_full.widgets.extension", before: "rest_full.draw_routes" do
Decidim::RestFull::Extension.register(:widgets) do |ext|
ext.oauth_scopes :widgets
ext.permissions(:widgets, "widgets.read", group: :widgets)
ext.routes do
Decidim::RestFull::Routing.read_resources(self, :widgets, controller: "widgets/widgets", only: [:index, :show])
end
ext.rswag_specs File.join(Widgets::ENGINE_ROOT, "spec/requests/decidim/api/rest_full/widgets/**/*_spec.rb")
ext.open_api_definitions File.join(Widgets::ENGINE_ROOT, "lib/decidim/rest_full/widgets/test_definitions.rb")
end
end
AnchorPurpose
before: "rest_full.draw_routes"Route blocks collected before Routes.mount! queues the append
before: "rest_full.scopes"Belt-and-suspenders for OAuth scope collection

Extension.register​

Public DSL: decidim-restfull-core/lib/decidim/rest_full/extension.rb.

MethodRegisters
oauth_scopesDoorkeeper optional scopes (merged at after_initialize)
permissionsSystem-admin API permission checkboxes
routesRoute block → RouteRegistry
api_jobAsync mutation handler
rswag_specsOpenAPI request spec globs
open_api_definitionsSchema barrel for OpenAPI
webhooksActiveSupport::Notifications patterns

When Extension.register runs after routes are already drawn, Routes.append_pending! adds new blocks without a full redraw.

Registering the same route block twice raises Decidim::RestFull::Core::DuplicateRouteBlockError (fail-fast; no duplicate Rails routes).

OAuth scopes​

Core optional scopes live on DoorkeeperConfig::CORE_OPTIONAL_SCOPES.

Gems add new scopes only:

ext.oauth_scopes :widgets   # skip if already in core list
ext.permissions(:widgets, "widgets.read", group: :widgets)

Merge runs at boot in the rest_full.scopes initializer. Late Extension.register calls DoorkeeperConfig.merge_optional_scopes! when Rails.application.initialized?.

See Scopes and permissions.

Host app extensions​

Host apps may register from config/initializers/ inside Rails.application.config.after_initialize:

Rails.application.config.after_initialize do
require "decidim/rest_full"

Decidim::RestFull::Extension.register(:my_feature) do |ext|
ext.routes { get "my_resource/:id", to: "/decidim/my_app/my_resources#show" }
end
end

Extension.register appends routes and re-merges scopes when the app is already initialized. See Host app extensions.

warning

Host controllers must not live under Decidim::Api::RestFull::* (Zeitwerk conflict with decidim-api). Use an app-specific namespace.

RouteRegistry​

RouteRegistry collects route blocks from feature gems and draws them under /api/rest_full/v<major.minor>/ on Decidim::Core::Engine.routes.

Boot entry: Decidim::RestFull::Routes.mount! from initializer rest_full.draw_routes — same pattern as Decidim Admin/System (RouteSet#append). Rails' RoutesReloader runs a single finalize!; Devise configures Warden there. We never call finalize! or patch Warden.

Late Extension.register uses Routes.append_pending! (draw without finalize). Isolated specs may call RouteRegistry.apply! on a throwaway RouteSet.

CasePath
Route registrydecidim-restfull-core/spec/lib/decidim/rest_full/core/route_registry_spec.rb
Routing DSLdecidim-restfull-core/spec/lib/decidim/rest_full/routing_spec.rb
Routes bootdecidim-restfull-core/spec/requests/decidim/api/rest_full/routes_boot_spec.rb

See also​