1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
require 'observer'
require 'singleton'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/string/inflections'
module ActiveModel
module Observing
extend ActiveSupport::Concern
included do
extend Observable
end
module ClassMethods
# Activates the observers assigned. Examples:
#
# # Calls PersonObserver.instance
# ActiveRecord::Base.observers = :person_observer
#
# # Calls Cacher.instance and GarbageCollector.instance
# ActiveRecord::Base.observers = :cacher, :garbage_collector
#
# # Same as above, just using explicit class references
# ActiveRecord::Base.observers = Cacher, GarbageCollector
#
# Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
# called during startup, and before each development request.
def observers=(*values)
@observers = values.flatten
end
# Gets the current observers.
def observers
@observers ||= []
end
# Instantiate the global Active Record observers.
def instantiate_observers
observers.each { |o| instantiate_observer(o) }
end
# Wraps methods with before and after notifications.
#
# wrap_with_notifications :create, :save, :update, :destroy
def wrap_with_notifications(*methods)
methods.each do |method|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{method}_with_notifications(*args, &block)
notify_observers(:before_#{method})
result = #{method}_without_notifications(*args, &block)
notify_observers(:after_#{method})
result
end
EOS
alias_method_chain(method, :notifications)
end
end
protected
def instantiate_observer(observer)
# string/symbol
if observer.respond_to?(:to_sym)
observer = observer.to_s.camelize.constantize.instance
elsif observer.respond_to?(:instance)
observer.instance
else
raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
end
end
# Notify observers when the observed class is subclassed.
def inherited(subclass)
super
changed
notify_observers :observed_class_inherited, subclass
end
end
private
def notify_observers(method)
self.class.changed
self.class.notify_observers(method, self)
end
end
class Observer
include Singleton
class << self
# Attaches the observer to the supplied model classes.
def observe(*models)
models.flatten!
models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
define_method(:observed_classes) { models }
end
def observed_classes
Array.wrap(observed_class)
end
# The class observed by default is inferred from the observer's class name:
# assert_equal Person, PersonObserver.observed_class
def observed_class
if observed_class_name = name[/(.*)Observer/, 1]
observed_class_name.constantize
else
nil
end
end
end
# Start observing the declared classes and their subclasses.
def initialize
observed_classes.each { |klass| add_observer!(klass) }
end
def observed_classes
self.class.observed_classes
end
# Send observed_method(object) if the method exists.
def update(observed_method, object) #:nodoc:
send(observed_method, object) if respond_to?(observed_method)
end
# Special method sent by the observed class when it is inherited.
# Passes the new subclass.
def observed_class_inherited(subclass) #:nodoc:
self.class.observe(observed_classes + [subclass])
add_observer!(subclass)
end
protected
def add_observer!(klass)
klass.add_observer(self)
end
end
end
|