{"record":{"id":"b22341f81a75c789","repo":"ruby-concurrency/concurrent-ruby","slug":"cannot-provide-both-an-observer-and-a-block-b22341","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_write_observer_set.rb","lineNumber":23,"sourceCode":"\n    # A thread safe observer set implemented using copy-on-write approach:\n    # every time an observer is added or removed the whole internal data structure is\n    # duplicated and replaced with a new one.\n    #\n    # @api private\n    class CopyOnWriteObserverSet < 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          new_observers = @observers.dup\n          new_observers[observer] = func\n          @observers = new_observers\n          observer\n        end\n      end\n\n      # @!macro observable_delete_observer\n      def delete_observer(observer)\n        synchronize do","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb#L5-L41","documentation":"CopyOnWriteObserverSet is the second observer-registry implementation behind Concurrent::Observable (it swaps the whole observers hash atomically on change instead of notifying over a copy). Its add_observer has the same contract as the other sets: register an observer object (optional callback method, default :update) or a block, never both — the combination is ambiguous and raises ArgumentError.","triggerScenarios":"`subject.add_observer(listener) { |v| handle(v) }`; `set.add_observer(obj, :on_event) { ... }` — any call supplying both the positional observer and a block.","commonSituations":"Switching subjects between observer-set implementations and carrying over a double registration; helpers that always attach a block while callers also pass an object.","solutions":["Register only the object: `subject.add_observer(listener)` (callback defaults to :update) or `subject.add_observer(listener, :on_event)`.","Or register only the block — it is stored as the observer and called via #call.","If both behaviors are wanted, register them as two separate observers."],"exampleFix":"// before\nsubject.add_observer(listener) { |e| on(e) }\n\n// after — object form\nsubject.add_observer(listener, :on)\n\n// or block form\nsubject.add_observer { |e| on(e) }","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\nend","tryCatchPattern":"begin\n  subject.add_observer(listener) { |e| on(e) }\nrescue ArgumentError => e\n  raise unless e.message.include?('observer and a block')\n  subject.add_observer(listener, :on)\nend","preventionTips":["Register exactly one callback form; if both behaviors are needed, register two observers.","Check wrappers for stray blocks appended to add_observer calls.","Object observers use the func method (default :update); block observers use #call."],"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-21T23:17:16.201Z"}