{"record":{"id":"d4bbc10db4289e73","repo":"collectiveidea/interactor","slug":"interactor-context-foo-baz","errorCode":null,"errorMessage":"#<Interactor::Context foo=\"baz\">","messagePattern":"#<Interactor::Context foo=\"baz\">","errorType":"exception","errorClass":"Interactor::Failure","httpStatus":null,"severity":"error","filePath":"lib/interactor/context.rb","lineNumber":130,"sourceCode":"    # context - A Hash whose key/value pairs are merged into the existing\n    #           Interactor::Context instance. (default: {})\n    #\n    # Examples\n    #\n    #   context = Interactor::Context.new\n    #   # => #<Interactor::Context>\n    #   context.fail!\n    #   # => Interactor::Failure: #<Interactor::Context>\n    #   context.fail! rescue false\n    #   # => false\n    #   context.fail!(foo: \"baz\")\n    #   # => Interactor::Failure: #<Interactor::Context foo=\"baz\">\n    #\n    # Raises Interactor::Failure initialized with the Interactor::Context.\n    def fail!(context = {})\n      context.each { |key, value| self[key.to_sym] = value }\n      @failure = true\n      raise Failure, self\n    end\n\n    # Internal: Track that an Interactor has been called. The \"called!\" method\n    # is used by the interactor being invoked with this context. After an\n    # interactor is successfully called, the interactor instance is tracked in\n    # the context for the purpose of potential future rollback.\n    #\n    # interactor - An Interactor instance that has been successfully called.\n    #\n    # Returns nothing.\n    def called!(interactor)\n      _called << interactor\n    end\n\n    # Public: Roll back the Interactor::Context. Any interactors to which this\n    # context has been passed and which have been successfully called are asked\n    # to roll themselves back by invoking their \"rollback\" instance methods.\n    #","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/collectiveidea/interactor/blob/c0e0079375e8d447eadcd062d7bb3b550fcb60bb/lib/interactor/context.rb#L112-L148","documentation":"Interactor::Failure is the control-flow signal of the interactor gem, not a crash. Interactor::Context#fail! (lib/interactor/context.rb:127-131) merges its hash argument into the current context, marks it failed by setting @failure = true, then raises Failure with the context attached (raise Failure, self). The message #<Interactor::Context foo=\"baz\"> is the inspect output of that failed context, i.e. the keys present when fail! was signaled, such as from context.fail!(foo: \"baz\"). The exception reaches your code only through the bang entry points: MyInteractor.call! / run! re-raise it (lib/interactor.rb:75-77, 144-152), while plain .call swallows the Failure for its own context and returns the failed context instead (lib/interactor.rb:114-120).","triggerScenarios":"Calling context.fail! (with or without a hash, e.g. context.fail!(foo: \"baz\")) inside an interactor #call and invoking it via MyInteractor.call!(...) or MyInteractor.new(...).run!. Using an organizer (organize A, B) whose nested interactor calls fail! while the organizer is invoked with .call!. Calling context.fail! directly on an Interactor::Context object. Edge case: a Failure raised with a context object different from the one owned by the receiving interactor is re-raised even under plain .call (object_id check in lib/interactor.rb:117-119).","commonSituations":"Switching an invocation from .call to .call! (wanting exceptions) without adding rescue Interactor::Failure, so expected business failures (validation, insufficient funds, record not found) surface as 500s in Rails controllers or crashes in Sidekiq jobs. Error reporters (Sentry, Honeybadger) or a bare rescue => e catching Interactor::Failure and filing false crash alerts. Teams migrating from other service-object gems assume call always raises on failure and are surprised that .call returns silently with failure? == true. Rescue blocks that manually undo work which context.rollback! already undid via the automatic rollback in run!.","solutions":["Rescue Interactor::Failure => e at the exact call site and read e.context (an Interactor::Context carrying the keys merged by fail!, plus failure?/success?); rollback already ran by the time you catch it.","If failure is an expected outcome, replace MyInteractor.call!(args) with MyInteractor.call(args) and branch on context.success?, because the non-bang variant returns the failed context instead of raising.","If the failure is unexpected, debug why the interactor reached fail!: the inspect string in the message shows exactly which keys were set at failure time, so check the guard conditions around each fail! call.","In organizers, trust the automatic rollback: run! rescues, calls context.rollback! (reverse order over _called), then re-raises; implement rollback in interactors that need undoing instead of rescuing mid-chain.","Add Interactor::Failure to the excluded_exceptions list of your error reporter (Sentry, Honeybadger) so this control-flow signal is not classified as a crash."],"exampleFix":"# before\nresult = PlaceOrder.call!(order: order)\n# => raises Interactor::Failure: #<Interactor::Context order=... error=\"out of stock\">\n\n# after (option 1: expected failure, use the non-bang call)\nresult = PlaceOrder.call(order: order)\nunless result.success?\n  redirect_to cart_path, alert: result.error\nend\n\n# after (option 2: keep call!, handle the signal)\nbegin\n  PlaceOrder.call!(order: order)\nrescue Interactor::Failure => e\n  Rails.logger.warn(\"order rejected: #{e.context[:error]}\")\nend","handlingStrategy":"try-catch","validationCode":"# Run the preconditions that guard the internal fail! before the bang call\norder_placeable = order.persisted? && order.items.any? && order.total_cents.positive?\nraise ArgumentError, \"order not placeable\" unless order_placeable\nresult = PlaceOrder.call!(order: order)","typeGuard":"def interactor_failure?(error)\n  error.is_a?(Interactor::Failure) && error.context.is_a?(Interactor::Context)\nend\n\n# narrow a returned context before treating it as success\ndef failed_context?(ctx)\n  ctx.respond_to?(:failure?) && ctx.failure?\nend","tryCatchPattern":"begin\n  result = PlaceOrder.call!(order: order)\nrescue Interactor::Failure => e\n  ctx = e.context          # Interactor::Context with the keys merged by fail!\n  report_rejection(ctx)    # ctx[:error] / ctx.failure? == true; rollback already ran\nrescue StandardError => e\n  raise                    # never let a broad rescue swallow real bugs\nend","preventionTips":["Use .call (not .call!) whenever failure is a normal business outcome, and check context.success? on the returned context.","Reserve call! for truly exceptional failures and always wrap that exact call site in rescue Interactor::Failure.","Always pass a descriptive payload to fail! (fail!(error: \"insufficient stock\", order_id: order.id)) so e.context is diagnosable in production.","Remember a rescued Failure means context.rollback! already undid prior interactors; do not manually repeat that undo in the rescue block.","Add Interactor::Failure to the excluded_exceptions list of Sentry or Honeybadger so control-flow signals do not page anyone.","Never wrap interactor invocations in a bare rescue or rescue StandardError without re-raising real errors."],"tags":["ruby","interactor","control-flow","service-object","rails"],"backgroundTag":"interactor-failure","analyzedSha":"c0e0079375e8d447eadcd062d7bb3b550fcb60bb","analyzedAt":"2026-08-23T10:49:08.816Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}