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

  1. Register the model for updates in an initializer: OpenProject::InplaceEdit::UpdateRegistry.register(MyModel, handler: my_handler, contract: MyContract).
  2. Verify at boot in console: OpenProject::InplaceEdit::UpdateRegistry.registered?(MyModel) and fetch_handler(MyModel.new).
  3. 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

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.