association_observers 0.0.3

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.
@@ -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