{"record":{"id":"be2d9c0b3ccbbf04","repo":"opf/openproject","slug":"missing-update-handler-for-model","errorCode":null,"errorMessage":"Missing update handler for #{@model}","messagePattern":"Missing update handler for #(.+?)","errorType":"exception","errorClass":"ArgumentError","httpStatus":404,"severity":"error","filePath":"app/controllers/inplace_edit_fields_controller.rb","lineNumber":76,"sourceCode":"    replace_via_turbo_stream(component:)\n    respond_with_turbo_streams\n  end\n\n  def dialog\n    respond_with_dialog(\n      OpenProject::Common::InplaceEditFieldDialogComponent.new(\n        model: @model,\n        attribute: @attribute,\n        system_arguments: system_arguments.to_h.symbolize_keys\n      )\n    )\n  end\n\n  private\n\n  def invoke_update_handler\n    handler = update_registry.fetch_handler(@model)\n    raise ArgumentError, \"Missing update handler for #{@model}\" if handler.blank?\n\n    handler.call(model: @model, params: permitted_params, user: current_user)\n  end\n\n  def handle_update_success\n    render_success_flash_message_via_turbo_stream(\n      message: I18n.t(:notice_successful_update)\n    )\n    close_dialog_via_turbo_stream(dialog_id) if dialog_id\n    refresh_calculated_dependents\n  end\n\n  def replace_field_component(success)\n    if !success && dialog_id\n      replace_via_turbo_stream(\n        component: dialog_field_component,\n        status: :unprocessable_entity\n      )","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/opf/openproject/blob/d9742c43f3424c34b63550f8c03f201fe5c3040c/app/controllers/inplace_edit_fields_controller.rb#L58-L94","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"# before\n# MyModel dialogs render, but update raises\n\n# after (initializer)\nOpenProject::InplaceEdit::UpdateRegistry.register(\n  MyModel,\n  handler: ->(model:, params:, user:) { MyUpdateService.call(model:, params:, user:) },\n  contract: MyModelContract\n)","handlingStrategy":"validation","validationCode":"klass = @model.class\nraise ArgumentError, 'no inplace update handler' unless OpenProject::InplaceEdit::UpdateRegistry.registered?(klass)","typeGuard":"def inplace_editable?(record)\n  OpenProject::InplaceEdit::UpdateRegistry.registered?(record.class) &&\n    !OpenProject::InplaceEdit::UpdateRegistry.fetch_handler(record.class).nil?\nend","tryCatchPattern":"begin\n  invoke_update_handler\nrescue ArgumentError => e\n  head :not_found if e.message.start_with?('Missing update handler')\nend","preventionTips":["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."],"tags":["inplace-edit","registry","stimulus","plugin","controller"],"backgroundTag":"missing-handler-registration","analyzedSha":"d9742c43f3424c34b63550f8c03f201fe5c3040c","analyzedAt":"2026-08-21T14:40:06.829Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}