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
| Anchor | Purpose |
|---|---|
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.
| Method | Registers |
|---|---|
oauth_scopes | Doorkeeper optional scopes (merged at after_initialize) |
permissions | System-admin API permission checkboxes |
routes | Route block → RouteRegistry |
api_job | Async mutation handler |
rswag_specs | OpenAPI request spec globs |
open_api_definitions | Schema barrel for OpenAPI |
webhooks | ActiveSupport::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?.
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.
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.
Related specs
| Case | Path |
|---|---|
| Route registry | decidim-restfull-core/spec/lib/decidim/rest_full/core/route_registry_spec.rb |
| Routing DSL | decidim-restfull-core/spec/lib/decidim/rest_full/routing_spec.rb |
| Routes boot | decidim-restfull-core/spec/requests/decidim/api/rest_full/routes_boot_spec.rb |