Class: ObservableCollection
- Inherits:
-
Object
- Object
- ObservableCollection
- Includes:
- Observable
- Defined in:
- lib/observable_collection.rb
Overview
This class facilitates the modification of collections in a distributed fashion. It adds the Observable functionality to collections types like Hash and Array. Initialize with the underlying collection you want to wrap.
It supports nested collections. Notifications of changes in lower levels in a data structure are bubbled upward by chained-together ObservableCollections, where each chain link is an observable-observer relationship.
The object wrapped by an ObservableCollection can be accessed explicitly via #subject, but the point of this class is that you can treat an ObservableCollection as you would a regular Array or Hash.
Instance Attribute Summary collapse
-
#subject ⇒ Object
Returns the value of attribute subject.
Class Method Summary collapse
-
.create(subject, observer = nil, opts = {}) ⇒ Object
Factory method - takes in an Array or a Hash, and the observer.
Instance Method Summary collapse
-
#initialize(subject, opts = {}) ⇒ ObservableCollection
constructor
Options: lock_file - path to a file used for locking always_update_after - always call update after the collection is accessed, regardless of whether the methods are destructive (update is always called before the collection is accessed).
-
#lock ⇒ Object
Gain an exclusive lock on access to this data structure.
-
#method_missing(meth, *args, &block) ⇒ Object
Users will treat ObservableCollection like a regular collection, so send method calls to the underlying collection.
-
#update(_downstream_object, kind) ⇒ Object
We ignore the arguments because we don’t care what the change was downstream–we just need to propagate upward the message that something changed.
Constructor Details
#initialize(subject, opts = {}) ⇒ ObservableCollection
Options:
lock_file - path to a file used for locking
always_update_after - always call update after the collection is accessed,
regardless of whether the methods are destructive
(update is always called *before* the collection
is accessed)
30 31 32 33 34 |
# File 'lib/observable_collection.rb', line 30 def initialize(subject, opts = {}) @subject = subject @lock_file = opts[:lock_file] @always_update_after = opts[:always_update_after] end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(meth, *args, &block) ⇒ Object
Users will treat ObservableCollection like a regular collection, so send method calls to the underlying collection. Extra things we do:
-catch the creation/retrieval of child collections and make them
observable too, so that updates to them bubble up.
-let *our* observers know about this method call, both before and after
we call the desired method.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/observable_collection.rb', line 79 def method_missing(meth, *args, &block) # Let the our observers know someone is calling a method on us. If we are # reporting directly to a user-land observer, its callback will be # invoked. If we are reporting to another ObservableCollection, it will # just propagate the notification upward. unless @locked changed notify_observers(@subject, :before) end # Execute the method on the subject result = @subject.send(meth, *args, &block) # If the return value is another ObservableCollection, add myself as an # observer. If it's an ordinary collection, make it an ObservableCollection # and add myself as an observer. The exception is when result == @subject, # in which case we just want to return the subject unadorned. This is to # avoid, e.g., puts being unable to convert an observable array to a regular # array the way it expects (this exception facilitates, e.g., `puts hash.values`) if result.is_a? ObservableCollection result.add_observer self elsif result != @subject result = ObservableCollection.create(result, self, lock_file: @lock_file, always_update_after: @always_update_after) end if (DESTRUCTIVE.include? meth) || @always_update_after changed notify_observers(@subject, :after) end result end |
Instance Attribute Details
#subject ⇒ Object
Returns the value of attribute subject.
22 23 24 |
# File 'lib/observable_collection.rb', line 22 def subject @subject end |
Class Method Details
.create(subject, observer = nil, opts = {}) ⇒ Object
Factory method - takes in an Array or a Hash, and the observer. Options are passed to constructor (see above). An additional option is :func, which specifies the name of the observer’s update callback (defaults to :update as in the Observable module).
40 41 42 43 44 45 46 47 48 49 |
# File 'lib/observable_collection.rb', line 40 def self.create(subject, observer = nil, opts = {}) observable = subject if [Array, Hash].include? observable.class observable = ObservableCollection.new(subject, opts) args = [observer] args << opts[:func] if opts[:func] observable.add_observer(*args) if observer end observable end |
Instance Method Details
#lock ⇒ Object
Gain an exclusive lock on access to this data structure. Accepts a block to execute while the lock is owned. It is best to do this whenever, e.g., writing to disk upon changes to the collection.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/observable_collection.rb', line 54 def lock _lock # (TL;DR: locking solves more problems than concurrency) # Only read from disk once while the lock is kept. Normal behavior is # to read every time a method is called at any level of the data # structure, which can cause problems when e.g. reading twice in one # line, such as `obs_hash[a][b] << obs_hash[c][d].count`. Note that the # problem being solved here is not related to concurrency--it's just # a convenient way to solve it. changed notify_observers(self, :before) yield _unlock end |
#update(_downstream_object, kind) ⇒ Object
We ignore the arguments because we don’t care what the change was downstream–we just need to propagate upward the message that something changed.
118 119 120 121 122 123 |
# File 'lib/observable_collection.rb', line 118 def update(_downstream_object, kind) if kind == :after changed notify_observers(@subject, :after) end end |