Skip to main content

Async writes and jobs

Overview

Mutating actions return 202 Accepted, persist an ApiJob, enqueue ExecuteApiJobJob, and expose job_id + poll_url. Poll GET /api/rest_full/v…/jobs/:uuid (no Bearer; UUID is the secret).

Inline work uses /sync or *_sync actions (200/201/204).

When to use

  • Any new create, update, destroy, publish, vote on the public API.
  • You register a new command in ext.api_job and ApiJobCommandRunner.

Example

1. Implement an operations class

decidim-restfull-widgets/app/services/decidim/rest_full/widgets/widget_operations.rb

module Decidim::RestFull::Widgets
class WidgetOperations
def initialize(ctx, params)
@ctx = ctx
@params = params
end

def create!
# validate, mutate Decidim models, return JSON-serializable Hash
end
end
end
warning

Do not register a raw Decidim::Command in ApiJobCommandRunner.

2. Register ext.api_job in the engine

decidim-restfull-widgets/lib/decidim/rest_full/widgets/engine.rb

ext.api_job "widgets#create", ->(ctx, p) {
Widgets::WidgetOperations.new(ctx, p).create!
}

3. Enqueue from the controller

decidim-restfull-widgets/app/controllers/decidim/api/rest_full/widgets/widgets_controller.rb

class WidgetsController < Decidim::Api::RestFull::Core::ResourcesController
include Decidim::Api::RestFull::AsyncApiJobEnqueuing

def create
enqueue_rest_full_api_job!("widgets#create") # => 202 + job_id
end
end

4. Add a /sync route and *_sync action

decidim-restfull-proposals/app/controllers/decidim/api/rest_full/draft_proposals/draft_proposals_controller.rb

def create_sync
render json: Decidim::RestFull::SyncRunner.call {
Proposals::DraftProposalsOperations.new(api_execution_context, params).create!
}
end

Route: post "/sync", action: :create_sync — see Routing.

5. Document async and sync in RSwag

decidim-restfull-forms/spec/requests/decidim/api/rest_full/forms/questions_controller_spec.rb

# POST /questions — async
response "202", "Job accepted" do
run_test! do |response|
expect(response).to have_http_status(:accepted)
expect(JSON.parse(response.body)).to include("job_id")
end
end

# POST /questions/sync — inline (separate path block in the same file)

6. Run Sidekiq on queue default

Set DECIDIM_REST_QUEUE_NAME in the host app. Workers must be running for jobs to finish.

Rules

RuleDetail
Defaultcreate / update / destroy / publish / vote → async unless *_sync.
command_keySame string in ext.api_job, enqueue_rest_full_api_job!, and payload.
PayloadStored as JSONB on decidim_rest_full_api_jobs; job row id only in Sidekiq args.
Size capDecidim::RestFull.config.max_async_api_job_payload_bytes
Forms answersAsync POST /answers; poll /submission_requests/:id or /jobs/:id.
Forms authoringAsync default; inline via /sync — table below.
CIRuboCop Decidim/RestFull/AsyncApiMutation on controllers (excludes submission_requests, controller_helpers).
ExceptionPOST /magic_links returns 201 with token inline (no job).

Forms authoring (default async)

HTTPActioncommand_key
PUT /questionnaires/:idupdateforms/questionnaires#update
POST /questionscreateforms/questions#create
PUT /questions/:idupdateforms/questions#update
DELETE /questions/:iddestroyforms/questions#destroy
POST /answer_optionscreateforms/answer_options#create
PUT /answer_options/:idupdateforms/answer_options#update
DELETE /answer_options/:iddestroyforms/answer_options#destroy
DELETE /questionnaire_responses/:iddestroyforms/questionnaire_responses#destroy

Inline: PUT …/sync, POST …/sync, DELETE …/sync*_sync actions. Implementation: Decidim::RestFull::Forms::AuthoringOperations.

CasePath
Jobs polldecidim-restfull-core/spec/requests/.../jobs/api_jobs_async_and_conditional_get_spec.rb
Forms answer syncdecidim-restfull-forms/spec/requests/.../answers_controller_sync_spec.rb
Draft asyncdecidim-restfull-proposals/spec/requests/.../draft_proposals_controller_create_spec.rb

See also