opf/openproject · error · ArgumentError
Missing update handler for #{@model}
Error message
Missing update handler for #{@model} What it means
InplaceEditFieldsController#invoke_update_handler looks up a handler in OpenProject::InplaceEdit::UpdateRegistry keyed by the model's exact class (fetch_handler uses model.class). The registry is only populated by explicit UpdateRegistry.register(model_class, handler:, contract:) calls, so when the instantiated model's class has no registered entry, ArgumentError('Missing update handler for <model>') is raised on the update submit.
Source
Thrown at app/controllers/inplace_edit_fields_controller.rb:76
replace_via_turbo_stream(component:)
respond_with_turbo_streams
end
def dialog
respond_with_dialog(
OpenProject::Common::InplaceEditFieldDialogComponent.new(
model: @model,
attribute: @attribute,
system_arguments: system_arguments.to_h.symbolize_keys
)
)
end
private
def invoke_update_handler
handler = update_registry.fetch_handler(@model)
raise ArgumentError, "Missing update handler for #{@model}" if handler.blank?
handler.call(model: @model, params: permitted_params, user: current_user)
end
def handle_update_success
render_success_flash_message_via_turbo_stream(
message: I18n.t(:notice_successful_update)
)
close_dialog_via_turbo_stream(dialog_id) if dialog_id
refresh_calculated_dependents
end
def replace_field_component(success)
if !success && dialog_id
replace_via_turbo_stream(
component: dialog_field_component,
status: :unprocessable_entity
)View on GitHub (pinned to d9742c43f3)
Solutions
- Register the model for updates in an initializer: OpenProject::InplaceEdit::UpdateRegistry.register(MyModel, handler: my_handler, contract: MyContract).
- Verify at boot in console: OpenProject::InplaceEdit::UpdateRegistry.registered?(MyModel) and fetch_handler(MyModel.new).
- If the record's class is an STI subclass, register that exact subclass (fetch_handler matches model.class, not ancestors).
Example fix
# before
# MyModel dialogs render, but update raises
# after (initializer)
OpenProject::InplaceEdit::UpdateRegistry.register(
MyModel,
handler: ->(model:, params:, user:) { MyUpdateService.call(model:, params:, user:) },
contract: MyModelContract
) Defensive patterns
Strategy: validation
Validate before calling
klass = @model.class raise ArgumentError, 'no inplace update handler' unless OpenProject::InplaceEdit::UpdateRegistry.registered?(klass)
Type guard
def inplace_editable?(record)
OpenProject::InplaceEdit::UpdateRegistry.registered?(record.class) &&
!OpenProject::InplaceEdit::UpdateRegistry.fetch_handler(record.class).nil?
end Try / catch
begin
invoke_update_handler
rescue ArgumentError => e
head :not_found if e.message.start_with?('Missing update handler')
end Prevention
- Register handler AND contract together at boot for every model exposed to inplace edit.
- Add a boot-time sanity check iterating registry keys asserting fetch_handler is present.
- Watch STI: register subclasses explicitly, fetch_handler matches model.class exactly.
When it happens
Trigger: Submitting the in-place-edit dialog for a model class that was never registered — e.g. an STI subclass used as the record's class while only the parent was registered, or a plugin adding an inplace-editable model without calling UpdateRegistry.register.
Common situations: Plugin/engine forgot or reordered its initializer so registration never ran; record loaded as a subclass (model.class differs from the class the client named in the model param); fork adds a model to the dialog whitelist but not to the update registry.
Related errors
AI-assisted analysis of opf/openproject@d9742c43f3 (2026-08-21).
Data as JSON: /api/errors/be2d9c0b3ccbbf04.
Report an issue: GitHub.