{"record":{"id":"88d698a50e8f2bf7","repo":"ruby-concurrency/concurrent-ruby","slug":"cannot-provide-both-an-observer-and-a-block","errorCode":null,"errorMessage":"cannot provide both an observer and a block","messagePattern":"cannot provide both an observer and a block","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb","lineNumber":24,"sourceCode":"    # A thread safe observer set implemented using copy-on-read approach:\n    # observers are added and removed from a thread safe collection; every time\n    # a notification is required the internal data structure is copied to\n    # prevent concurrency issues\n    #\n    # @api private\n    class CopyOnNotifyObserverSet < Synchronization::LockableObject\n\n      def initialize\n        super()\n        synchronize { ns_initialize }\n      end\n\n      # @!macro observable_add_observer\n      def add_observer(observer = nil, func = :update, &block)\n        if observer.nil? && block.nil?\n          raise ArgumentError, 'should pass observer as a first argument or block'\n        elsif observer && block\n          raise ArgumentError.new('cannot provide both an observer and a block')\n        end\n\n        if block\n          observer = block\n          func     = :call\n        end\n\n        synchronize do\n          @observers[observer] = func\n          observer\n        end\n      end\n\n      # @!macro observable_delete_observer\n      def delete_observer(observer)\n        synchronize do\n          @observers.delete(observer)\n          observer","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb#L6-L42","documentation":"CopyOnNotifyObserverSet is the observer registry used by Concurrent::Observable subjects. add_observer accepts either an observer object (with an optional callback method name, default :update) or a block, but not both — passing both is ambiguous about what should be notified, so it raises ArgumentError before anything is registered.","triggerScenarios":"`subject.add_observer(listener) { |value| handle(value) }`; `observer_set.add_observer(obj, :on_change) { ... }` — any call supplying both the positional observer and a block.","commonSituations":"Refactoring from block callbacks to object callbacks (or vice versa) and leaving the other form behind; adding a block 'for documentation' while also registering an object; wrapper methods that always append a block.","solutions":["Remove the block when registering an object: `subject.add_observer(listener)` — the callback defaults to listener.update.","Or name the object's callback method: `subject.add_observer(listener, :on_change)`.","Or drop the object and keep only the block — the block itself becomes the observer and is invoked via #call."],"exampleFix":"// before\nchat.add_observer(logger) { |msg| log(msg) }\n\n// after — object form\nchat.add_observer(logger, :log)\n\n// or block form\nchat.add_observer { |msg| log(msg) }","handlingStrategy":"validation","validationCode":"def register(subject, observer = nil, func = :update, &block)\n  raise ArgumentError, 'observer OR block, not both' if observer && block\n  subject.add_observer(observer, func, &block)\nend","typeGuard":"def valid_registration?(observer, block_given)\n  observer.nil? != block_given # exactly one form supplied\nend","tryCatchPattern":"begin\n  subject.add_observer(listener) { |m| log(m) }\nrescue ArgumentError => e\n  raise unless e.message.include?('observer and a block')\n  subject.add_observer(listener, :log)\nend","preventionTips":["Pick one callback style per registration: object (with optional method symbol) or block, never both.","Centralize registrations in a helper that enforces the either/or contract.","Remember block observers are invoked via #call; object observers via the func argument (default :update)."],"tags":["concurrent-ruby","observer","add-observer","argumenterror"],"backgroundTag":"observer-registration-conflict","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}