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.
- data/.gitignore +21 -0
- data/.pryrc +24 -0
- data/.rspec +2 -0
- data/.travis.yml +14 -0
- data/Gemfile +20 -0
- data/LICENSE.txt +22 -0
- data/README.md +214 -0
- data/Rakefile +3 -0
- data/association_observers.gemspec +35 -0
- data/database.yml +5 -0
- data/lib/association_observers.rb +199 -0
- data/lib/association_observers/activerecord.rb +4 -0
- data/lib/association_observers/notifiers/base.rb +77 -0
- data/lib/association_observers/notifiers/propagation_notifier.rb +16 -0
- data/lib/association_observers/railtie.rb +16 -0
- data/lib/association_observers/ruby18.rb +12 -0
- data/lib/association_observers/version.rb +3 -0
- data/lib/examples/notifiers/update_timestamp_notifier.rb +29 -0
- data/lib/examples/readme_example.rb +114 -0
- data/spec/activerecord/association_observers_spec.rb +548 -0
- data/spec/examples/readme_example_spec.rb +44 -0
- data/spec/spec_helper.rb +26 -0
- metadata +223 -0
@@ -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,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
|