Dependency 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/dependency.rb ADDED
@@ -0,0 +1,141 @@
1
+ # Just a namespace.
2
+ module Dependency
3
+ # This is the module you need to include in your classes (or Object).
4
+ module Methods
5
+ # Dependents can be any type of object, but must
6
+ # have the <tt>update</tt> method defined as follows:
7
+ #
8
+ # def update(aspect)
9
+ # end
10
+ #
11
+ # or:
12
+ #
13
+ # def update(aspect, message)
14
+ # # message[:from] always defined
15
+ # # message[:with] usually defined
16
+ # end
17
+ #
18
+ # Adding a dependent:
19
+ #
20
+ # object.dependents.add dependent
21
+ # object.dependents << dependent
22
+ #
23
+ # Removing a dependent:
24
+ #
25
+ # object.dependents.remove dependent
26
+ #
27
+ def dependents
28
+ @dependents ||= Proxy.new
29
+ end
30
+ # Call this method to notify of a change, e.g.
31
+ #
32
+ # def name=(new_name)
33
+ # old_name = self.name
34
+ # self.name = new_name
35
+ # changed :name, :with => old_name
36
+ # end
37
+ #
38
+ def changed(aspect = nil, message = nil)
39
+ args = [:update, aspect]
40
+ unless message.nil?
41
+ message[:from] = self
42
+ args << message
43
+ end
44
+ flexi = FlexiCaller.new(*args)
45
+ dependents.each { |dependent| flexi.call_on dependent }
46
+ end
47
+ # Register a dependent that's only interested in a particular aspect.
48
+ #
49
+ # object.express_interest_in :name, :for => dependent
50
+ #
51
+ # Use the :send_back option to specify what message (method call) should
52
+ # be sent back (defaults to :update).
53
+ #
54
+ # object.express_interest_in :name, :for => dependent, :send_back => :name_changed
55
+ #
56
+ # Note: the method called on the dependents has a different (simpler!)
57
+ # signature to that when just using `dependents.add`:
58
+ #
59
+ # def update(old_name)
60
+ # def update(old_name, object)
61
+ #
62
+ def express_interest_in(aspect, options)
63
+ dependents.add Transformer.new(self, aspect, options[:for], options[:send_back] || :update)
64
+ end
65
+ # Remove a dependency:
66
+ #
67
+ # object.retract_interest_in :name, :for => dependent
68
+ #
69
+ def retract_interest_in(aspect, options)
70
+ dependents.delete_if do |dependent|
71
+ dependent.respond_to?(:for?) && dependent.for?(aspect, options[:for])
72
+ end
73
+ end
74
+ end
75
+
76
+ # Include this module in your classes (or Object),
77
+ # if you want to use the dependency_attr class method.
78
+ module ClassMethod
79
+ # Shorthand for the following pattern:
80
+ #
81
+ # def name=(new_name)
82
+ # old_name = self.name
83
+ # self.name = new_name
84
+ # changed :name, :with => old_name
85
+ # end
86
+ #
87
+ # Use it as you would `attr`, e.g.
88
+ #
89
+ # dependency_attr :name, :age
90
+ #
91
+ def dependency_attr(*names)
92
+ for attribute in names
93
+ attr_reader attribute
94
+ class_eval %{
95
+ def #{attribute}=(new_value)
96
+ old_value = self.#{attribute}
97
+ instance_variable_set '@#{attribute}', new_value
98
+ changed :#{attribute}, :with => old_value
99
+ end
100
+ }
101
+ end
102
+ end
103
+ end
104
+
105
+ private # keep oot!
106
+
107
+ class Proxy < Array # :nodoc:
108
+ def remove(dependent)
109
+ delete_if { |d| d === dependent }
110
+ end
111
+ alias :add :<<
112
+ end
113
+
114
+ class FlexiCaller # :nodoc:
115
+ def initialize(method_name, *args)
116
+ @method_name, @args = method_name, args
117
+ end
118
+ def call_on(dependent)
119
+ case arity = dependent.method(@method_name).arity
120
+ when 0
121
+ dependent.send @method_name
122
+ when -1
123
+ dependent.send @method_name, *@args
124
+ else
125
+ dependent.send @method_name, *@args[0, arity]
126
+ end
127
+ end
128
+ end
129
+
130
+ class Transformer # :nodoc:
131
+ def initialize(parent, aspect, dependent, method_name)
132
+ @parent, @aspect, @dependent, @method_name = parent, aspect, dependent, method_name
133
+ end
134
+ def update(aspect = nil, message = {})
135
+ FlexiCaller.new(@method_name, message[:with], @parent).call_on(@dependent) if @aspect == aspect
136
+ end
137
+ def for?(aspect, dependent)
138
+ @aspect == aspect && @dependent.equal?(dependent)
139
+ end
140
+ end
141
+ end
data/rakefile.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/packagetask'
5
+ require 'rake/gempackagetask'
6
+ require 'rcov/rcovtask'
7
+ require 'rake/rdoctask'
8
+
9
+ module Dependency
10
+ VERSION = '1.0.0'
11
+ PACK = ['readme.txt', 'rakefile.rb', '{lib,spec}/*.rb']
12
+ SPEC = ['spec/*_spec.rb']
13
+ RDOC = ['readme.txt', 'lib/*.rb']
14
+ end
15
+
16
+ gemspec = Gem::Specification.new do |s|
17
+ s.name = 'Dependency'
18
+ s.summary ='Smalltalk-inspired dependency mechanism.'
19
+ s.version = Dependency::VERSION
20
+ s.files = FileList[*Dependency::PACK]
21
+ s.require_path = 'lib'
22
+ s.autorequire = 'dependency'
23
+ s.has_rdoc = true
24
+ s.extra_rdoc_files << Dependency::RDOC.first
25
+ s.rdoc_options << '--main' << Dependency::RDOC.first
26
+ s.rubyforge_project = s.name.downcase
27
+ s.homepage = "http://#{s.rubyforge_project}.rubyforge.org/"
28
+ s.author = 'Tim Fletcher'
29
+ s.email = 'twoggle@gmail.com'
30
+ end
31
+
32
+ Rake::GemPackageTask.new(gemspec) { |t| t.package_dir = 'pkg' }
33
+
34
+ Rake::PackageTask.new(gemspec.name, Dependency::VERSION) do |p|
35
+ p.need_tar_gz = true
36
+ p.package_files.include *Dependency::PACK
37
+ end
38
+
39
+ Rake::TestTask.new :spec do |t|
40
+ t.test_files = Dependency::SPEC
41
+ end
42
+
43
+ Rcov::RcovTask.new do |t|
44
+ t.test_files = Dependency::SPEC
45
+ t.output_dir = 'coverage'
46
+ t.rcov_opts = []
47
+ end
48
+
49
+ Rake::RDocTask.new do |t|
50
+ t.main = Dependency::RDOC.first
51
+ t.rdoc_files.include *Dependency::RDOC
52
+ end
data/readme.txt ADDED
@@ -0,0 +1,39 @@
1
+ = Dependency
2
+
3
+ Smalltalk-inspired dependency mechanism.
4
+
5
+
6
+ == Basic HOWTO
7
+
8
+ In your classes (or Object):
9
+
10
+ include Dependency::Methods
11
+
12
+ Register dependencies:
13
+
14
+ object.dependents.add dependent
15
+
16
+ Notify of changes like so:
17
+
18
+ changed :attr_name, :with => old_value
19
+
20
+ The update method will then be called on all the object's dependents.
21
+
22
+ Remove dependencies:
23
+
24
+ object.dependents.remove dependent
25
+
26
+ For more insight, take a look at the source, the spec, or Chapter 19 of
27
+ Smalltalk by Example, upon which this code was based.
28
+
29
+ Smalltalk by Example: http://www.iam.unibe.ch/~ducasse/FreeBooks/ByExample
30
+
31
+
32
+ == License
33
+
34
+ Same as Ruby.
35
+
36
+
37
+ == Contact
38
+
39
+ Tim Fletcher <mailto:twoggle@gmail.com>
@@ -0,0 +1,48 @@
1
+ #
2
+ # Needs [test/spec](http://chneukirchen.org/repos/testspec)
3
+
4
+ $:.unshift File.dirname(__FILE__)
5
+
6
+ require 'test/spec'
7
+ require 'preamble'
8
+
9
+ $stock = Stock.new('DEPCO')
10
+ $simple_watcher = SimpleStockWatcher.new($stock)
11
+ $selective_watcher = SelectiveStockWatcher.new($stock)
12
+ $selective_watcher_with_send_back = SelectiveStockWatcherWithSendBack.new($stock)
13
+
14
+ $stock.traded 200, :price => 20
15
+ $stock.traded 100, :price => 30
16
+ $stock.name = 'NEWCO'
17
+ $stock.dependents.remove $simple_watcher
18
+ $stock.retract_interest_in :price, :for => $selective_watcher
19
+ $stock.retract_interest_in :price, :for => $selective_watcher_with_send_back
20
+ $stock.traded 150, :price => 25
21
+
22
+
23
+ context 'Simple Stock Watcher' do
24
+ specify 'should receive 3 calls to #update' do
25
+ $simple_watcher.method_calls.should.equal :update => 3
26
+ end
27
+ specify 'should receive correct price changes' do
28
+ $simple_watcher.price_changes.should.equal [[0, 20], [20, 30]]
29
+ end
30
+ end
31
+
32
+ context 'Selective Stock Watcher' do
33
+ specify 'should receive 2 calls to #update' do
34
+ $selective_watcher.method_calls.should.equal :update => 2
35
+ end
36
+ specify 'should receive correct price changes' do
37
+ $selective_watcher.price_changes.should.equal [[0, 20], [20, 30]]
38
+ end
39
+ end
40
+
41
+ context 'Selective Stock Watcher w/ Send Back' do
42
+ specify 'should receive 2 calls to #price_changed' do
43
+ $selective_watcher_with_send_back.method_calls.should.equal :price_changed => 2
44
+ end
45
+ specify 'should receive correct price changes' do
46
+ $selective_watcher.price_changes.should.equal [[0, 20], [20, 30]]
47
+ end
48
+ end
data/spec/preamble.rb ADDED
@@ -0,0 +1,53 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'dependency')
2
+
3
+ Object.send :include, Dependency::Methods
4
+ Class.send :include, Dependency::ClassMethod
5
+
6
+ class Stock
7
+ dependency_attr :price, :name
8
+ def initialize(name, price = 0)
9
+ @name, @price = name, price
10
+ end
11
+ def traded(count, args)
12
+ self.price = args[:price]
13
+ end
14
+ end
15
+
16
+ class StockWatcher
17
+ def method_calls
18
+ @method_calls ||= Hash.new { |calls, meth| calls[meth] = 0 }
19
+ end
20
+ def price_changes
21
+ @price_changes ||= []
22
+ end
23
+ end
24
+
25
+ class SimpleStockWatcher < StockWatcher
26
+ def initialize(stock)
27
+ stock.dependents.add self
28
+ end
29
+ def update(aspect, message)
30
+ method_calls[:update] += 1
31
+ price_changes << [message[:with], message[:from].price] if :price == aspect
32
+ end
33
+ end
34
+
35
+ class SelectiveStockWatcher < StockWatcher
36
+ def initialize(stock)
37
+ stock.express_interest_in :price, :for => self
38
+ end
39
+ def update(old_price, stock)
40
+ method_calls[:update] += 1
41
+ price_changes << [old_price, stock.price]
42
+ end
43
+ end
44
+
45
+ class SelectiveStockWatcherWithSendBack < StockWatcher
46
+ def initialize(stock)
47
+ stock.express_interest_in :price, :for => self, :send_back => :price_changed
48
+ end
49
+ def price_changed(old_price, stock)
50
+ method_calls[:price_changed] += 1
51
+ price_changes << [old_price, stock.price]
52
+ end
53
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: Dependency
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2006-10-20 00:00:00 +01:00
8
+ summary: Smalltalk-inspired dependency mechanism.
9
+ require_paths:
10
+ - lib
11
+ email: twoggle@gmail.com
12
+ homepage: http://dependency.rubyforge.org/
13
+ rubyforge_project: dependency
14
+ description:
15
+ autorequire: dependency
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Tim Fletcher
31
+ files:
32
+ - readme.txt
33
+ - rakefile.rb
34
+ - lib/dependency.rb
35
+ - spec/dependency_spec.rb
36
+ - spec/preamble.rb
37
+ test_files: []
38
+
39
+ rdoc_options:
40
+ - --main
41
+ - readme.txt
42
+ extra_rdoc_files:
43
+ - readme.txt
44
+ executables: []
45
+
46
+ extensions: []
47
+
48
+ requirements: []
49
+
50
+ dependencies: []
51
+