{"record":{"id":"08c3a086a813ceb0","repo":"ruby-concurrency/concurrent-ruby","slug":"should-pass-observer-as-a-first-argument-or-block","errorCode":null,"errorMessage":"should pass observer as a first argument or block","messagePattern":"should pass observer as a first argument or block","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb","lineNumber":22,"sourceCode":"  module Collection\n\n    # 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","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb#L4-L40","documentation":"`CopyOnNotifyObserverSet#add_observer(observer = nil, func = :update, &block)` requires either an observer object (on which `func`, default `:update`, will be called) or a block (invoked via `:call`). Called with neither — bare `add_observer` or `add_observer(nil)` — it raises ArgumentError before touching the set. This set is one of the two observer strategies behind Concurrent::Observable; the sibling error 'cannot provide both an observer and a block' guards the opposite misuse.","triggerScenarios":"`observable.add_observer` with no arguments; `add_observer(nil)`; forwarding a variable that was never assigned: `add_observer(@observer)` when config omitted the key. `add_observer(nil, &blk)` is fine (block path).","commonSituations":"Optional-callback APIs where the observer comes from config or params and can be absent; builder DSLs that defer attaching and accidentally attach nil; refactor that moved observer construction after the attach call.","solutions":["Pass the observer object: `add_observer(MyObserver.new)`.","Or pass a block: `add_observer { |*args| handle(*args) }`.","Guard optionality at the call site: `add_observer(obs) if obs`, or default the observer (`obs ||= DefaultObserver.new`).","In wrapper APIs, raise your own descriptive error when both observer and block are missing."],"exampleFix":"// before\nadd_observer(@listener) # @listener is nil from missing config\n\n// after\nadd_observer(@listener) if @listener\n# or always attach a handler:\nadd_observer { |*args| @listener&.call(*args) }","handlingStrategy":"validation","validationCode":"def attach(set, observer = nil, func = :update, &block)\n  return set.add_observer(observer, func, &block) if observer || block\n  raise ArgumentError, 'observer object or block required'\nend\n\n# usage\nattach(set, obs) if obs\nattach(set) { |*a| handle(*a) } if obs.nil?","typeGuard":"def observer_supplied?(observer = nil, &block)\n  !observer.nil? || !block.nil?\nend","tryCatchPattern":"begin\n  set.add_observer(obs)\nrescue ArgumentError => e\n  raise unless e.message.include?('observer as a first argument')\n  set.add_observer { |*args| fallback.call(*args) }\nend","preventionTips":["Treat the observer as required: default it (obs ||= NOOP_OBSERVER) before calling.","Never forward possibly-nil callback variables into add_observer.","Remember the mirrored rule: observer AND block together is also rejected."],"tags":["concurrent-ruby","observer","observable","copy-on-notify","argumenterror"],"backgroundTag":"missing-required-argument","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}