association_observers 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ if defined?(ActiveRecord)
2
+ ActiveRecord::Base.send(:extend, AssociationObservers::ClassMethods)
3
+ ActiveRecord::Base.send(:include, AssociationObservers::InstanceMethods)
4
+ end
@@ -0,0 +1,77 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Notifier
3
+ class Base
4
+ attr_reader :callback, :observers, :options
5
+ # @param [Symbol] callback callback key to which this observer will respond (:create, :update, :save, :destroy)
6
+ # @param [Array] observers list of observers asssociations in the symbolized-underscored form (SimpleAssociation => :simple_association)
7
+ # @param [Hash] options additional options for the notifier
8
+ # @option options [Class] :observer_class the class of the observer (important when the observer association is polymorphic)
9
+ def initialize(callback, observers, options = {})
10
+ @callback = callback
11
+ @observers = Array(observers)
12
+ @options = options
13
+ end
14
+
15
+
16
+ # this function will be triggered by the notify_observers call on the observable model. It is basically
17
+ # implemented as a filter where it is seen if the triggered callback corresponds to the callback this observer
18
+ # responds to
19
+ #
20
+ # @param [Symbol] callback key from the callback that has just been triggered
21
+ # @param [Object] observable the object which triggered the callback
22
+ def update(callback, observable)
23
+ return unless callback == @callback
24
+ observers = @options.has_key?(:observer_class) ? self.observers.select{|assoc| observable.association(assoc).klass == @options[:observer_class] } : self.observers
25
+ notify(observable, observers.map{|assoc| observable.send(assoc)}.compact)
26
+ end
27
+
28
+ # @return [Boolean] whether the action should be executed for the observer
29
+ def conditions(observable, observer) ; true ; end
30
+ # @return [Boolean] whether the action should be executed for the observers collection
31
+ def conditions_many(observable, observers) ; true ; end
32
+
33
+ # abstract action; has to be implemented by the subclass
34
+ def action(observable, observer, callback=@callback)
35
+ raise "this has to be implemented in your notifier"
36
+ end
37
+
38
+
39
+
40
+ private
41
+
42
+ # Notifies all observers; filters the observers into two groups: one-to-many and one-to-one collections
43
+ # @param [Object] observable the object which is notifying
44
+ # @param [Array] observers the associated observers which will be notified
45
+ def notify(observable, observers, &block)
46
+ many, ones = observers.partition{|obs| obs.respond_to?(:size) }
47
+ action = block_given? ? block : method(:action)
48
+ notify_many(observable, many, &action)
49
+ notify_ones(observable, ones, &action)
50
+ end
51
+
52
+ # TODO: make this notify private as soon as possible again
53
+ public :notify
54
+
55
+ # Abstract Method (can be re-defined by other notifiers); here it is defined the default implementation of
56
+ # handling of many-to-many observers: for each one it will notify its observers, in case these are observed
57
+ # @param [Object] observable the object which is notifying
58
+ # @param [Array[ActiveRecord::Relation]] many_observers the observers which will be notified; each element represents a one-to-many relation
59
+ def notify_many(observable, many_observers)
60
+ many_observers.each do |observers|
61
+ observers.find_each(:batch_size => 10) do |observer|
62
+ yield(observable, observer) if conditions(observable, observer)
63
+ end if conditions_many(observable, observers)
64
+ end
65
+ end
66
+
67
+ # Abstract Method (can be re-defined by other notifiers); here it is defined the default implementation of
68
+ # handling of one-to-one observers: for each one it will notify its observers, in case these are observed
69
+ # @param [Object] observable the object which is notifying
70
+ # @param [Array[Object]] observers the observers which will be notified; each element represents a one-to-one association
71
+ def notify_ones(observable, observers)
72
+ observers.each do |observer|
73
+ yield(observable, observer) if conditions(observable, observer)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,16 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # Default Observer which propagates notifications to the observers' observers
3
+ #
4
+ # @author Tiago Cardoso
5
+ class PropagationNotifier < Notifier::Base
6
+
7
+ def conditions(observable, observer) ; observer.observable? ; end
8
+ def conditions_many(observable, observers) ; observers.klass.observable? ; end
9
+
10
+ # propagates the message to the observer's observer if the
11
+ # observer is indeed observed by any entity
12
+ def action(observable, observer, callback=@callback)
13
+ (observer.new_record? or not observer.respond_to?(:delay)) ? observer.send(:notify_observers, callback) : observer.delay.notify_observers(callback)
14
+ end
15
+
16
+ end
@@ -0,0 +1,16 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module AssociationObservers
3
+ def self.initialize_railtie
4
+ ActiveSupport.on_load :active_record do
5
+ require 'association_observers/activerecord'
6
+ end
7
+ end
8
+ class Railtie < Rails::Railtie
9
+ initializer 'association_observers.insert_into_active_record' do
10
+ AssociationObservers.initialize_railtie
11
+ end
12
+ initializer 'association_observers.autoload', :before => :set_autoload_paths do |app|
13
+ app.config.autoload_paths += Rails.root.join("app", "notifiers")
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module AssociationObservers
3
+ def self.extended_to_s(val)
4
+ if val.is_a?(Hash)
5
+ "{#{val.map{|k, v| ":#{k}=>#{extended_to_s(v)}"}.join(",")}}"
6
+ elsif val.is_a?(Array)
7
+ "[#{val.map{|a|extended_to_s(a)}.join(",")}]"
8
+ else
9
+ val.is_a?(Symbol) ? ":#{val}" : val.is_a?(String) ? "\"#{val}\"" : val
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module AssociationObservers
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding : utf-8 -*-
2
+ class UpdateTimestampNotifier < Notifier
3
+ module ObserverMethods
4
+ def irrelevant_observer_method
5
+ puts "irrelevantimus"
6
+ end
7
+ end
8
+
9
+ module ObservableMethods
10
+ def irrelevant_observable_method
11
+ puts "up yours"
12
+ end
13
+ end
14
+
15
+ def action(observable, observer)
16
+ observer.touch :timestamp
17
+ end
18
+
19
+ private
20
+
21
+ def notify_many(observable, many_observers)
22
+ unless observable.new_record?
23
+ many_observers.each do |observers|
24
+ observers.update_all(observers.table[:timestamp].eq(Time.now).to_sql)
25
+ end
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,114 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # Logger
3
+ require 'logger'
4
+
5
+ LOGGER = Logger.new(STDOUT)
6
+
7
+ # Notifiers
8
+ class HaveSliceNotifier < Notifier::Base
9
+
10
+ def action(cake, kid)
11
+ cake.update_column(:slices, cake.slices - 1)
12
+ cake.destroy if cake.slices == 0
13
+ kid.increment(:slices).save
14
+ end
15
+
16
+ end
17
+
18
+ class BustKidsAssNotifier < Notifier::Base
19
+
20
+ module ObserverMethods
21
+ def bust_kids_ass!
22
+ LOGGER.info("Slam!!")
23
+ end
24
+ end
25
+
26
+ module ObservableMethods
27
+ def is_for_grandpa?
28
+ true # it is always for grandpa
29
+ end
30
+ end
31
+
32
+ def conditions(cake, mom)
33
+ cake.is_for_grandpa?
34
+ end
35
+
36
+ def action(cake, mom)
37
+ mom.bust_kids_ass!
38
+ end
39
+
40
+ end
41
+
42
+ class TellKidHesFatNotifier < Notifier::Base
43
+
44
+ module ObservableMethods
45
+ def cry!
46
+ LOGGER.info(":'(")
47
+ end
48
+
49
+ def throw_slices_away!
50
+ update_column(:slices, 0)
51
+ end
52
+ end
53
+
54
+ def conditions(kid, mom)
55
+ kid.slices > 20
56
+ end
57
+
58
+ def action(kid, mom)
59
+ LOGGER.info("Hey Fatty, BEEFCAKE!!!!!")
60
+ kid.cry!
61
+ kid.throw_slices_away!
62
+ end
63
+
64
+ end
65
+
66
+
67
+ # TABLES
68
+
69
+ ActiveRecord::Schema.define do
70
+ create_table :cakes, :force => true do |t|
71
+ t.integer :slices
72
+ t.integer :mom_id
73
+ end
74
+ create_table :kids, :force => true do |t|
75
+ t.integer :mom_id
76
+ t.integer :slices, :default => 0
77
+ end
78
+ create_table :moms, :force => true do |t|
79
+ end
80
+ end
81
+
82
+ # ENTITIES
83
+
84
+ class Cake < ActiveRecord::Base
85
+
86
+ def self.default_slices ; 8 ; end
87
+ belongs_to :mom
88
+ has_one :kid, :through => :mom
89
+ before_create do |record|
90
+ record.slices ||= record.class.default_slices
91
+ end
92
+ end
93
+
94
+ class Mom < ActiveRecord::Base
95
+ has_one :kid
96
+ has_many :cakes
97
+ end
98
+
99
+ class Kid < ActiveRecord::Base
100
+ belongs_to :mom
101
+ has_many :cakes, :through => :mom
102
+
103
+ observes :cakes, :notifier => :have_slice, :on => :create
104
+ end
105
+
106
+ class Mom < ActiveRecord::Base
107
+ observes :cakes, :on => :destroy, :notifier => :bust_kids_ass
108
+ observes :kid, :on => :update, :notifier => :tell_kid_hes_fat
109
+ end
110
+
111
+
112
+
113
+
114
+
@@ -0,0 +1,548 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require "./spec/spec_helper"
3
+
4
+ describe AssociationObservers do
5
+ class TestUpdateNotifier < Notifier::Base
6
+
7
+ def action(observable, observer)
8
+ observer.update_attributes(:updated => true)
9
+ end
10
+
11
+ def notify_many(observable, many_observers)
12
+ many_observers.each do |observers|
13
+ observers.update_all(:updated => true)
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ class TestDestroyNotifier < Notifier::Base
20
+
21
+ def action(observable, observer)
22
+ observer.update_attributes(:deleted => true)
23
+ end
24
+
25
+ def notify_many(observable, many_observers)
26
+ many_observers.each do |observers|
27
+ observers.each do |observer|
28
+ observer.update_all(:deleted => true)
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+
36
+ class ObservableAbstractTest < ActiveRecord::Base
37
+ self.table_name ='association_observable_tests'
38
+ attr_accessible :name
39
+ end
40
+
41
+ class BelongsToObservableTest < ObservableAbstractTest
42
+ attr_accessible :observer_test
43
+ has_one :observer_test
44
+ end
45
+ class CollectionObservableTest < ObservableAbstractTest
46
+ attr_accessible :observer_tests
47
+ has_many :observer_tests
48
+ end
49
+ class HasOneObservableTest < ObservableAbstractTest
50
+ attr_accessible :observer_test
51
+ belongs_to :observer_test
52
+ end
53
+ class HasManyObservableTest < ObservableAbstractTest
54
+ attr_accessible :observer_test, :has_many_through_observable_tests
55
+ belongs_to :observer_test
56
+ has_many :has_many_through_observable_tests
57
+ end
58
+ class HasManyThroughObservableTest < ObservableAbstractTest
59
+ attr_accessible :observer_test, :has_many_observable_test
60
+ belongs_to :has_many_observable_test
61
+ has_one :observer_test, :through => :has_many_observable_test
62
+ end
63
+
64
+ class PolymorphicHasManyObservableTest < ObservableAbstractTest
65
+ attr_accessible :observer
66
+ belongs_to :observer, :polymorphic => true
67
+ end
68
+ class HabtmObservableTest < ObservableAbstractTest
69
+ attr_accessible :observer_tests
70
+ has_and_belongs_to_many :observer_tests
71
+ end
72
+
73
+
74
+ class ObserverAbstractTest < ActiveRecord::Base
75
+ self.table_name = 'association_observer_tests'
76
+ attr_accessible :updated, :deleted
77
+ end
78
+
79
+ class ObserverTest < ObserverAbstractTest
80
+
81
+ belongs_to :belongs_to_observable_test
82
+ belongs_to :collection_observable_test
83
+ has_one :has_one_observable_test
84
+ has_many :has_many_observable_tests
85
+
86
+ has_many :has_many_through_observable_tests, :through => :has_many_observable_tests
87
+
88
+ has_many :polymorphic_has_many_observable_tests, :as => :observer
89
+
90
+ has_one :observer_observer_test
91
+
92
+ has_and_belongs_to_many :habtm_observable_tests
93
+
94
+ attr_accessible :belongs_to_observable_test,
95
+ :has_one_observable_test,
96
+ :has_many_observable_tests,
97
+ :has_many_through_observable_tests,
98
+ :polymorphic_has_many_observable_tests,
99
+ :collection_observable_test,
100
+ :observer_observer_test,
101
+ :habtm_observable_tests
102
+
103
+ observes :habtm_observable_tests, :notifiers => :test_update, :on => :create
104
+ observes :belongs_to_observable_test,
105
+ :has_one_observable_test,
106
+ :collection_observable_test,
107
+ :has_many_through_observable_tests,
108
+ :polymorphic_has_many_observable_tests,
109
+ :has_many_observable_tests,
110
+ :habtm_observable_tests, :notifiers => :test_update, :on => :update
111
+ observes :belongs_to_observable_test,
112
+ :has_one_observable_test,
113
+ :collection_observable_test,
114
+ :has_many_through_observable_tests,
115
+ :has_many_observable_tests,
116
+ :polymorphic_has_many_observable_tests,
117
+ :habtm_observable_tests, :notifiers => :test_destroy, :on => :destroy
118
+ end
119
+
120
+ class ObserverObserverTest < ObserverAbstractTest
121
+ belongs_to :observer_test
122
+ attr_accessible :observer_test
123
+
124
+ observes :observer_test, :notifiers => :test_update, :on => :update
125
+ end
126
+
127
+ ActiveRecord::Schema.define do
128
+ create_table :association_observer_tests, :force => true do |t|
129
+ t.column :type, :string # for polymorphic test only
130
+ t.column :observer_test_id, :integer
131
+ t.column :belongs_to_observable_test_id, :integer
132
+ t.column :collection_observable_test_id, :integer
133
+ t.column :updated, :boolean
134
+ t.column :deleted, :boolean
135
+ end
136
+ create_table :association_observable_tests, :force => true do |t|
137
+ t.column :observer_test_id, :integer
138
+ t.column :observer_type, :string # for polymorphic test only
139
+ t.column :observer_id, :integer # for polymorphic test only
140
+ t.column :has_many_observable_test_id, :integer
141
+ t.column :name, :string
142
+ end
143
+ create_table :habtm_observable_tests_observer_tests, :id => false, :force => true do |t|
144
+ t.column :habtm_observable_test_id, :integer
145
+ t.column :observer_test_id, :integer
146
+ end
147
+ end
148
+
149
+ let(:observer1) {ObserverTest.create(:has_one_observable_test => HasOneObservableTest.new,
150
+ :has_many_observable_tests => [HasManyObservableTest.new,
151
+ HasManyObservableTest.new,
152
+ HasManyObservableTest.new])}
153
+ let(:observer2) { ObserverTest.create }
154
+ let(:belongs_to_observable) {BelongsToObservableTest.new(:observer_test => observer1)}
155
+ let(:collection_observable) {CollectionObservableTest.create(:observer_tests => [observer1, observer2])}
156
+ let(:polymorphic_observable) {PolymorphicHasManyObservableTest.create(:observer => observer2)}
157
+ describe "observer_methods" do
158
+ let(:observer) {ObserverObserverTest.new}
159
+ it "should be available" do
160
+ observer.should be_observer
161
+ observer.should_not be_observable
162
+ end
163
+ end
164
+
165
+ describe "observable_methods" do
166
+ let(:observable){BelongsToObservableTest.new}
167
+ it "should be available" do
168
+ observable.should_not be_observer
169
+ observable.should be_observable
170
+ end
171
+ end
172
+
173
+ describe "when the belongs to observable is updated" do
174
+ before(:each) do
175
+ belongs_to_observable.update_attributes(:name => "doof")
176
+ end
177
+ it "should update its observer" do
178
+ belongs_to_observable.name.should == "doof"
179
+ observer1.reload.should be_updated
180
+ observer1.should_not be_deleted
181
+ end
182
+ end
183
+ # TODO: currently failing spec; after_destroy not being triggered in rspec: investigate further
184
+ pending "when the belongs to observable is deleted" do
185
+ before(:each) do
186
+ observer1.belongs_to_observable_test.destroy
187
+ end
188
+ it "should destroy its observer" do
189
+ observer1.reload.should be_deleted
190
+ end
191
+ end
192
+ describe "when the observable hides itself" do
193
+ before(:each) do
194
+ observer1.update_column(:updated, nil)
195
+ belongs_to_observable.unobservable!
196
+ belongs_to_observable.update_attributes(:name => "doof")
197
+ end
198
+ it "should not update its observer" do
199
+ observer1.belongs_to_observable_test.name.should == "doof"
200
+ observer1.reload.should_not be_updated
201
+ end
202
+ end
203
+ describe "when the has one observable is updated" do
204
+ before(:each) do
205
+ observer1.has_one_observable_test.update_attributes(:name => "doof")
206
+ end
207
+ it "should update its observer" do
208
+ observer1.has_one_observable_test.name.should == "doof"
209
+ observer1.reload.should be_updated
210
+ observer1.should_not be_deleted
211
+ end
212
+ end
213
+ describe "when one of the has many observables is updated" do
214
+ before(:each) do
215
+ observer1.has_many_observable_tests.first.update_attributes(:name => "doof")
216
+ end
217
+ it "should update its observer" do
218
+ observer1.has_many_observable_tests.first.name.should == "doof"
219
+ observer1.reload.should be_updated
220
+ observer1.should_not be_deleted
221
+ end
222
+ describe "and afterwards deleted" do
223
+ before(:each) do
224
+ observer1.update_column(:deleted, false)
225
+ observer1.has_many_observable_tests.first.destroy
226
+ end
227
+ it "should update its observer" do
228
+ observer1.reload.should be_deleted
229
+ end
230
+ end
231
+ end
232
+ describe "when one of the polymorphic has many is updated" do
233
+ before(:each) do
234
+ observer1.polymorphic_has_many_observable_tests = [PolymorphicHasManyObservableTest.new,
235
+ PolymorphicHasManyObservableTest.new,
236
+ PolymorphicHasManyObservableTest.new]
237
+ observer1.update_column(:updated, false)
238
+ observer1.reload
239
+ end
240
+ it "should update its observer" do
241
+ observer1.polymorphic_has_many_observable_tests.first.update_attributes(:name => "doof")
242
+ observer1.polymorphic_has_many_observable_tests.first.name.should == "doof"
243
+ observer1.reload.should be_updated
244
+ observer1.should_not be_deleted
245
+ end
246
+ describe "having another polymorphic observable of same type somewhere else" do
247
+ before(:each) do
248
+ observer2.update_column(:updated, false)
249
+ end
250
+ it "should not update its observer" do
251
+ observer1.polymorphic_has_many_observable_tests.first.update_attributes(:name => "doof")
252
+ observer1.polymorphic_has_many_observable_tests.first.name.should == "doof"
253
+ observer1.reload.should be_updated
254
+ observer1.should_not be_deleted
255
+ observer2.reload.should_not be_updated
256
+ observer2.should_not be_deleted
257
+ end
258
+ end
259
+ end
260
+ describe "when the has many through has been updated" do
261
+ before(:each) do
262
+ observer1.has_many_observable_tests.first.has_many_through_observable_tests.create
263
+ observer1.update_column(:updated, false)
264
+ end
265
+ it "should update its observer" do
266
+ observer1.has_many_observable_tests.first.has_many_through_observable_tests.first.update_attributes(:name => "doof")
267
+ observer1.has_many_observable_tests.first.has_many_through_observable_tests.first.name.should == "doof"
268
+ observer1.reload.should be_updated
269
+ observer1.should_not be_deleted
270
+ end
271
+ end
272
+ describe "when an has and belongs to many association" do
273
+ describe "has been created" do
274
+ before(:each) do
275
+ observer1.update_column(:updated, false)
276
+ observer1.habtm_observable_tests.create(:name => "doof")
277
+ end
278
+ it "should update its observer" do
279
+ observer1.habtm_observable_tests.first.name.should == "doof"
280
+ observer1.reload.should be_updated
281
+ observer1.should_not be_deleted
282
+ end
283
+ describe "and then updated" do
284
+ before(:each) do
285
+ observer1.update_column(:updated, false)
286
+ observer1.habtm_observable_tests.first.update_attributes(:name => "superdoof")
287
+ end
288
+ it "should update its observer" do
289
+ observer1.habtm_observable_tests.first.name.should == "superdoof"
290
+ observer1.reload.should be_updated
291
+ observer1.should_not be_deleted
292
+ end
293
+ end
294
+ describe "and afterwards deleted" do
295
+ before(:each) do
296
+ observer1.update_column(:deleted, false)
297
+ observer1.habtm_observable_tests.delete(observer1.habtm_observable_tests.first)
298
+ end
299
+ it "should update its observer" do
300
+ observer1.reload.should be_deleted
301
+ end
302
+ end
303
+ describe "and completely replaced" do
304
+ before(:each) do
305
+ observer1.update_column(:updated, false)
306
+ observer1.update_column(:deleted, false)
307
+ observer1.habtm_observable_tests = [HabtmObservableTest.new]
308
+ end
309
+ it "should update and delete the observer" do
310
+ observer1.reload.should be_updated
311
+ observer1.reload.should be_deleted
312
+ end
313
+ end
314
+
315
+ end
316
+ describe "has been linked from an existing assoc" do
317
+ before(:each) do
318
+ t = HabtmObservableTest.create(:name => "doof")
319
+ observer1.habtm_observable_tests << t
320
+ observer1.update_column(:updated, false)
321
+ observer1.habtm_observable_tests.first.update_attributes(:name => "superdoof")
322
+ end
323
+ it "should update its observer" do
324
+ observer1.habtm_observable_tests.first.name.should == "superdoof"
325
+ observer1.reload.should be_updated
326
+ observer1.should_not be_deleted
327
+ end
328
+ describe "and afterwards deleted" do
329
+ before(:each) do
330
+ observer1.update_column(:deleted, false)
331
+ observer1.habtm_observable_tests.delete(observer1.habtm_observable_tests.first)
332
+ end
333
+ it "should update its observer" do
334
+ observer1.reload.should be_deleted
335
+ end
336
+ end
337
+
338
+ end
339
+ end
340
+ describe "when the collection observable is updated" do
341
+ before(:each) do
342
+ collection_observable.update_attributes(:name => "doof")
343
+ end
344
+ it "should update its observers" do
345
+ collection_observable.name.should == "doof"
346
+ observer1.reload.should be_updated
347
+ observer1.should_not be_deleted
348
+ observer2.reload.should be_updated
349
+ observer2.should_not be_deleted
350
+ end
351
+ end
352
+
353
+ describe "when the observer has an observer itself" do
354
+ let(:observer_observer) { ObserverObserverTest.create }
355
+ before(:each) do
356
+ observer1.update_attribute(:observer_observer_test, observer_observer)
357
+ end
358
+ describe "when the belongs to observable is updated" do
359
+ before(:each) do
360
+ belongs_to_observable.update_attributes(:name => "doof")
361
+ end
362
+ it "should update its observer and its observer's observer" do
363
+ belongs_to_observable.name.should == "doof"
364
+ observer1.reload.should be_updated
365
+ observer_observer.reload.should be_updated
366
+ end
367
+ end
368
+ describe "when the has one observable is updated" do
369
+ before(:each) do
370
+ observer_observer.observer_test.has_one_observable_test.update_attributes(:name => "doof")
371
+ end
372
+ it "should update its observer and its observer's observer" do
373
+ observer_observer.observer_test.has_one_observable_test.name.should == "doof"
374
+ observer1.reload.should be_updated
375
+ observer_observer.reload.should be_updated
376
+ end
377
+ end
378
+ describe "when one of the has many observables is updated" do
379
+ before(:each) do
380
+ observer_observer.observer_test.has_many_observable_tests.first.update_attributes(:name => "doof")
381
+ end
382
+ it "should update its observer and its observer's observer" do
383
+ observer_observer.observer_test.has_many_observable_tests.first.name.should == "doof"
384
+ observer1.reload.should be_updated
385
+ observer_observer.reload.should be_updated
386
+ end
387
+ end
388
+ describe "when one of the polymorphic has many is updated" do
389
+ before(:each) do
390
+ observer1.polymorphic_has_many_observable_tests = [PolymorphicHasManyObservableTest.new,
391
+ PolymorphicHasManyObservableTest.new,
392
+ PolymorphicHasManyObservableTest.new]
393
+ observer1.update_column(:updated, false)
394
+ observer_observer.update_column(:updated, false)
395
+ end
396
+ it "should update its observer" do
397
+ observer_observer.observer_test.polymorphic_has_many_observable_tests.first.update_attributes(:name => "doof")
398
+ observer_observer.observer_test.polymorphic_has_many_observable_tests.first.name.should == "doof"
399
+ observer1.reload.should be_updated
400
+ observer_observer.reload.should be_updated
401
+ end
402
+ end
403
+ describe "when the has many through has been updated" do
404
+ before(:each) do
405
+ observer1.has_many_observable_tests.first.has_many_through_observable_tests.create
406
+ observer1.update_column(:updated, false)
407
+ observer_observer.update_column(:updated, false)
408
+ end
409
+ it "should update its observer" do
410
+ observer1.has_many_observable_tests.first.has_many_through_observable_tests.first.update_attributes(:name => "doof")
411
+ observer1.has_many_observable_tests.first.has_many_through_observable_tests.first.name.should == "doof"
412
+ observer1.reload.should be_updated
413
+ observer_observer.reload.should be_updated
414
+ end
415
+ end
416
+ describe "when the collection observable is updated" do
417
+ before(:each) do
418
+ collection_observable
419
+ observer_observer.observer_test.reload.collection_observable_test.update_attributes(:name => "doof")
420
+ end
421
+ it "should update its observers and its observer's observer" do
422
+ observer_observer.observer_test.collection_observable_test.name.should == "doof"
423
+ observer1.reload.should be_updated
424
+ observer_observer.reload.should be_updated
425
+ end
426
+ end
427
+ end
428
+
429
+
430
+ describe "when the association is polymorphic" do
431
+ class HasOnePolymorphicObservableTest < ObservableAbstractTest
432
+ self.table_name ='association_polymorphic_observable_tests'
433
+ belongs_to :observer, :polymorphic => true
434
+ attr_accessible :observer
435
+ end
436
+
437
+ class ObserverTest < ObserverAbstractTest
438
+ has_one :has_one_polymorphic_observable_test, :as => :observer
439
+ attr_accessible :has_one_polymorphic_observable_test
440
+ observes :has_one_polymorphic_observable_test, :as => :observer, :notifiers => :test_update, :on => :update
441
+ end
442
+
443
+ class OtherPossibleObserverTest < ActiveRecord::Base # Why Active::Record? The association klass is the base class
444
+ self.table_name = 'association_observer_tests'
445
+ attr_accessible :updated, :deleted
446
+ has_one :has_one_polymorphic_observable_test, :as => :observer
447
+ attr_accessible :has_one_polymorphic_observable_test
448
+ # does not define observables, therefore should not observe
449
+ end
450
+
451
+ ActiveRecord::Schema.define do
452
+ create_table :association_polymorphic_observable_tests, :force => true do |t|
453
+ t.column :observer_id, :integer
454
+ t.column :observer_type, :string
455
+ t.column :name, :string
456
+ end
457
+ end
458
+ let(:polymorphic_observable) { HasOnePolymorphicObservableTest.create(:observer => ObserverTest.new) }
459
+ let(:observer) {polymorphic_observable.observer}
460
+ before(:each) do
461
+ observer.update_column(:updated, nil)
462
+ end
463
+ describe "when the has one association is updated" do
464
+ before(:each) do
465
+ polymorphic_observable.update_attributes(:name => "doof")
466
+ end
467
+ it "should update its observer" do
468
+ polymorphic_observable.name.should == "doof"
469
+ observer.reload.should be_updated
470
+ end
471
+ end
472
+ describe "when the observable is not observed" do
473
+ let(:other_polymorphic_observable) { HasOnePolymorphicObservableTest.create(:observer => OtherPossibleObserverTest.new) }
474
+ let(:non_observer){ other_polymorphic_observable.observer }
475
+ describe " and the observable is updated" do
476
+ before(:each) do
477
+ other_polymorphic_observable.update_attributes(:name => "doof")
478
+ end
479
+ it "should not update its (non) observer" do
480
+ other_polymorphic_observable.name.should == "doof"
481
+ non_observer.reload.should_not be_updated
482
+ end
483
+ end
484
+ end
485
+
486
+ end
487
+
488
+ describe "when the association is a joined through" do
489
+ class HasOneThroughObservableTest < ObservableAbstractTest
490
+ self.table_name ='association_polymorphic_observable_tests'
491
+ belongs_to :has_one_observable_test
492
+ has_one :observer_test, :through => :has_one_observable_test
493
+ attr_accessible :has_one_observable_test
494
+ end
495
+
496
+ class HasOneObservableTest < ObservableAbstractTest
497
+ has_one :has_one_through_observable_test
498
+ attr_accessible :has_one_through_observable_test
499
+ end
500
+
501
+ class ObserverTest < ObserverAbstractTest
502
+ has_one :has_one_through_observable_test, :through => :has_one_observable_test
503
+ attr_accessible :has_one_through_observable_test
504
+ observes :has_one_through_observable_test, :notifiers => :test_update, :on => :update
505
+ end
506
+
507
+ ActiveRecord::Schema.define do
508
+ add_column :association_polymorphic_observable_tests, :has_one_observable_test_id, :integer
509
+ end
510
+
511
+ let(:through_observable) { HasOneThroughObservableTest.create(:has_one_observable_test => HasOneObservableTest.new(:observer_test => ObserverTest.new)) }
512
+ let(:observer) {through_observable.has_one_observable_test.observer_test}
513
+ describe "when the has one association is updated" do
514
+ before(:each) do
515
+ observer.update_attributes(:updated => nil)
516
+ through_observable.reload
517
+ through_observable.update_attributes(:name => "doof")
518
+ end
519
+ it "should update its observer" do
520
+ through_observable.name.should == "doof"
521
+ observer.reload.should be_updated
522
+ end
523
+ end
524
+
525
+ end
526
+
527
+ # TODO: should this be responsibility of the associations or from the observers? check further into this
528
+ pending "when an observable also observes its observer" do
529
+ ActiveRecord::Schema.define do
530
+ add_column :association_observable_tests, :successful, :boolean
531
+ end
532
+ before(:all) do
533
+ class HasOneObservableTest < ObservableAbstractTest
534
+ observes :observer_test, :notifiers => :test_update, :on => :update
535
+ end
536
+ end
537
+ describe "when something changes on the observable" do
538
+ before(:each) do
539
+ observer1.has_one_observable_test.update_attributes(:name => "doof")
540
+ end
541
+ it "should update only the observer (and therefore avoid infinite cycle)" do
542
+ observer1.has_one_observable_test.name.should == "doof"
543
+ observer1.reload.should be_updated
544
+ end
545
+ end
546
+ end
547
+
548
+ end