Module: Observable (Ruby 2.7.2)
Description: Module : Observable - Ruby 2.7.2

### Home Classes Methods

### In Files

- observer.rb

### Methods

- #add_observer
- #changed
- #changed?
- #count_observers
- #delete_observer
- #delete_observers
- #notify_observers

### Class/Module Index

Quicksearch

- Observable
- Observer

No matching classes.

# Observable

The Observer pattern (also known as publish/subscribe) provides a simple mechanism for one object to inform a set of interested third-party objects when its state changes.

## Mechanism¶ ↑

The notifying class mixes in the `Observable` module, which provides the methods for managing the associated observer objects.

The observable object must:

- assert that it has `#changed`

- call `#notify_observers`

An observer subscribes to updates using #add_observer, which also specifies the method called via notify_observers. The default method for notify_observers is update.

### Example¶ ↑

The following example demonstrates this nicely. A `Ticker`, when run, continually receives the stock `Price` for its `@symbol`. A `Warner` is a general observer of the price, and two warners are demonstrated, a `WarnLow` and a `WarnHigh`, which print a warning if the price is below or above their set limits, respectively.

The `update` callback allows the warners to run without being explicitly called. The system is set up with the `Ticker` and several observers, and the observers do their duty without the top-level code having to interfere.

Note that the contract between publisher and subscriber (observable and observer) is not declared or enforced. The `Ticker` publishes a time and a price, and the warners receive that. But if you don’t ensure that your contracts are correct, nothing else can warn you.

    require "observer"

    class Ticker          ### Periodically fetch a stock price.
      include Observable

      def initialize(symbol)
        @symbol = symbol
      end

      def run
        last_price = nil
        loop do
          price = Price.fetch(@symbol)
          print "Current price: #{price}\n"
          if price != last_price
            changed                 # notify observers
            last_price = price
            notify_observers(Time.now, price)
          end
          sleep 1
        end
      end
    end

    class Price           ### A mock class to fetch a stock price (60 - 140).
      def self.fetch(symbol)
        60 + rand(80)
      end
    end

    class Warner          ### An abstract observer of Ticker objects.
      def initialize(ticker, limit)
        @limit = limit
        ticker.add_observer(self)
      end
    end

    class WarnLow < Warner
      def update(time, price)       # callback for observer
        if price < @limit
          print "--- #{time.to_s}: Price below #@limit: #{price}\n"
        end
      end
    end

    class WarnHigh < Warner
      def update(time, price)       # callback for observer
        if price > @limit
          print "+++ #{time.to_s}: Price above #@limit: #{price}\n"
        end
      end
    end

    ticker = Ticker.new("MSFT")
    WarnLow.new(ticker, 80)
    WarnHigh.new(ticker, 120)
    ticker.run

Produces:

    Current price: 83
    Current price: 75
    --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 75
    Current price: 90
    Current price: 134
    +++ Sun Jun 09 00:10:25 CDT 2002: Price above 120: 134
    Current price: 134
    Current price: 112
    Current price: 79
    --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 79

### Public Instance Methods

add_observer(observer, func=:update) click to toggle source

Add `observer` as an observer on this object. So that it will receive notifications.

`observer`

  the object that will be notified of changes.

`func`

  Symbol naming the method that will be called when this Observable has changes.

  This method must return true for `observer.respond_to?` and will receive `*arg` when notify_observers is called, where `*arg` is the value passed to notify_observers by this Observable

                   # File observer.rb, line 128
    def add_observer(observer, func=:update)
      @observer_peers = {} unless defined? @observer_peers
      unless observer.respond_to? func
        raise NoMethodError, "observer does not respond to `#{func}'"
      end
      @observer_peers[observer] = func
    end

changed(state=true) click to toggle source

Set the changed state of this object. Notifications will be sent only if the changed `state` is `true`.

`state`

  Boolean indicating the changed state of this Observable.

                   # File observer.rb, line 169
    def changed(state=true)
      @observer_state = state
    end

changed?() click to toggle source

Returns true if this object’s state has been changed since the last notify_observers call.

                   # File observer.rb, line 177
    def changed?
      if defined? @observer_state and @observer_state
        true
      else
        false
      end
    end

count_observers() click to toggle source

Return the number of observers associated with this object.

                   # File observer.rb, line 155
    def count_observers
      if defined? @observer_peers
        @observer_peers.size
      else
        0
      end
    end

delete_observer(observer) click to toggle source

Remove `observer` as an observer on this object so that it will no longer receive notifications.

`observer`

  An observer of this Observable

                   # File observer.rb, line 141
    def delete_observer(observer)
      @observer_peers.delete observer if defined? @observer_peers
    end

delete_observers() click to toggle source

Remove all observers associated with this object.

                   # File observer.rb, line 148
    def delete_observers
      @observer_peers.clear if defined? @observer_peers
    end

notify_observers(*arg) click to toggle source

Notify observers of a change in state if this object’s changed state is `true`.

This will invoke the method named in add_observer, passing `*arg`. The changed state is then set to `false`.

`*arg`

  Any arguments to pass to the observers.

                   # File observer.rb, line 193
    def notify_observers(*arg)
      if defined? @observer_state and @observer_state
        if defined? @observer_peers
          @observer_peers.each do |k, v|
            k.send v, *arg
          end
        end
        @observer_state = false
      end
    end