giraffesoft-observational 0.1.1 → 0.2.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 +2 -0
- data/README.rdoc +13 -3
- data/Rakefile +3 -13
- data/VERSION +1 -1
- data/lib/observational.rb +25 -8
- data/lib/observational/active_record_observers.rb +25 -0
- data/lib/observational/observable.rb +35 -0
- data/lib/observational/observer.rb +26 -0
- data/observational.gemspec +14 -5
- data/rails/init.rb +2 -0
- data/spec/active_record_observers_spec.rb +31 -0
- data/spec/observable_spec.rb +51 -0
- data/spec/observational_spec.rb +52 -15
- data/spec/observer_spec.rb +46 -0
- data/spec/spec_helper.rb +4 -1
- metadata +14 -4
- data/.document +0 -5
data/README.rdoc
CHANGED
@@ -11,14 +11,14 @@ How many times have you seen this in a rails app?
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
Why is the user concerned with the delivery of his own welcome message? It seems like
|
14
|
+
Why is the user concerned with the delivery of his own welcome message? It seems like the Notifier should be responsible for that.
|
15
15
|
|
16
16
|
Observational makes it possible to make it the Notifier's responsibility, using the observer pattern.
|
17
17
|
|
18
18
|
The equivalent of the above example is:
|
19
19
|
|
20
20
|
class Notifier < ActionMailer::Base
|
21
|
-
observes :user, :invokes => :deliver_welcome_message, :
|
21
|
+
observes :user, :invokes => :deliver_welcome_message, :after => :create
|
22
22
|
|
23
23
|
def welcome_message(user)
|
24
24
|
# do mailer stuff here
|
@@ -30,7 +30,7 @@ After a user is created, Notifier.deliver_welcome_message(that_user) will be inv
|
|
30
30
|
It's also possible to specify that the observer method gets called with a specific attribute from the observed object.
|
31
31
|
|
32
32
|
class Creditor
|
33
|
-
observes :message, :invokes => :use_credit, :with => :creator, :
|
33
|
+
observes :message, :invokes => :use_credit, :with => :creator, :after => :create
|
34
34
|
|
35
35
|
def use_credit(user)
|
36
36
|
# do something
|
@@ -38,6 +38,16 @@ It's also possible to specify that the observer method gets called with a specif
|
|
38
38
|
end
|
39
39
|
|
40
40
|
After a message is created, Creditor.use_credit(message.creator) will be called.
|
41
|
+
|
42
|
+
Observational supports all of ActiveRecord's callbacks.
|
43
|
+
|
44
|
+
== YARDOC
|
45
|
+
|
46
|
+
Observational uses YARD, because it's a million times better than RDoc. You can find the docs at {docs.github.com/giraffesoft/observational}[http://docs.github.com/giraffesoft/observational]
|
47
|
+
|
48
|
+
== General Purpose Observers
|
49
|
+
|
50
|
+
Observational can also be used to add observers to ruby classes that aren't related to active_record. But, that's not documented yet :-).
|
41
51
|
|
42
52
|
== Copyright
|
43
53
|
|
data/Rakefile
CHANGED
@@ -31,18 +31,8 @@ end
|
|
31
31
|
|
32
32
|
task :default => :spec
|
33
33
|
|
34
|
-
require '
|
35
|
-
Rake::
|
36
|
-
|
37
|
-
config = YAML.load(File.read('VERSION.yml'))
|
38
|
-
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
39
|
-
else
|
40
|
-
version = ""
|
41
|
-
end
|
42
|
-
|
43
|
-
rdoc.rdoc_dir = 'rdoc'
|
44
|
-
rdoc.title = "observational #{version}"
|
45
|
-
rdoc.rdoc_files.include('README*')
|
46
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
34
|
+
require 'yard'
|
35
|
+
YARD::Rake::YardocTask.new do |t|
|
36
|
+
t.files = ['lib/**/*.rb']
|
47
37
|
end
|
48
38
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.3
|
data/lib/observational.rb
CHANGED
@@ -1,13 +1,30 @@
|
|
1
|
+
require 'observational/observer'
|
2
|
+
require 'observational/observable'
|
3
|
+
|
1
4
|
module Observational
|
5
|
+
##
|
6
|
+
# Declares an observer
|
7
|
+
#
|
8
|
+
# @param [Symbol] the name of the model to observe
|
9
|
+
# @param [Hash] opts observer options
|
10
|
+
# @option opts [Symbol] :invokes The method to invoke on the subscriber.
|
11
|
+
# @option opts [Symbol] :with The parameters to pass to the method that gets invoked.
|
12
|
+
# @option opts [Symbol] :on The action to observe. e.g. :after_create for an active_record object
|
13
|
+
# @option opts [Symbol] :after A shortcut for AR objects. :after => :create is the equivalent of :on => :after_create
|
14
|
+
# @option opts [Symbol] :before Same as :after
|
15
|
+
#
|
2
16
|
def observes(model_name, opts = {})
|
3
|
-
opts.assert_valid_keys :with, :invokes, :on
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
model_klass
|
8
|
-
|
9
|
-
|
10
|
-
|
17
|
+
opts.assert_valid_keys :with, :invokes, :on, :before, :after
|
18
|
+
|
19
|
+
opts[:on] = :"before_#{opts[:before]}" unless opts[:before].nil?
|
20
|
+
opts[:on] = :"after_#{opts[:after]}" unless opts[:after].nil?
|
21
|
+
model_klass = model_name.to_s.classify.constantize
|
22
|
+
model_klass.send(:include, Observable) unless model_klass.include?(Observable)
|
23
|
+
observer = Observational::Observer.new :method => opts[:invokes],
|
24
|
+
:parameters => opts[:with].nil? ? nil : [*opts[:with]],
|
25
|
+
:subscriber => self,
|
26
|
+
:actions => opts[:on]
|
27
|
+
model_klass.add_observer(observer)
|
11
28
|
end
|
12
29
|
end
|
13
30
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Observational
|
2
|
+
module ActiveRecordObservers
|
3
|
+
def self.included(klass)
|
4
|
+
ActiveRecord::Callbacks::CALLBACKS.each do |callback|
|
5
|
+
klass.send(callback) { |obj| obj.send :notify_observers, callback }
|
6
|
+
end
|
7
|
+
klass.class_eval do
|
8
|
+
extend ClassMethods
|
9
|
+
class << self
|
10
|
+
alias_method_chain :define_callbacks, :observational
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def define_callbacks_with_observational(*callbacks)
|
17
|
+
define_callbacks_without_observational *callbacks
|
18
|
+
callbacks.each do |c|
|
19
|
+
send(c) { |obj| obj.send :notify_observers, c }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Observational
|
2
|
+
module Observable
|
3
|
+
def self.included(klass)
|
4
|
+
klass.send(:extend, ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def observers
|
9
|
+
@observers ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_observer(observer)
|
13
|
+
observers << observer
|
14
|
+
end
|
15
|
+
|
16
|
+
def observed_by?(observer)
|
17
|
+
observers.include?(observer)
|
18
|
+
end
|
19
|
+
|
20
|
+
def observers_for(action)
|
21
|
+
observers.select { |o| o.observes_action?(action) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete_observers
|
25
|
+
@observers = []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
def notify_observers(action)
|
31
|
+
self.class.observers_for(action).each { |o| o.invoke(self) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Observational
|
2
|
+
class Observer
|
3
|
+
attr_reader :subscriber, :method, :parameters, :actions
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@subscriber = options[:subscriber]
|
7
|
+
@method = options[:method]
|
8
|
+
@parameters = options[:parameters]
|
9
|
+
@actions = [*options[:actions]]
|
10
|
+
end
|
11
|
+
|
12
|
+
def invoke(observable)
|
13
|
+
@subscriber.send(method, *arguments(observable))
|
14
|
+
end
|
15
|
+
|
16
|
+
def observes_action?(action)
|
17
|
+
actions.include?(action)
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
def arguments(observable)
|
22
|
+
parameters.nil? ? observable : parameters.map { |p| observable.send(p) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
data/observational.gemspec
CHANGED
@@ -2,26 +2,32 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{observational}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.2.3"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["James Golick"]
|
9
|
-
s.date = %q{2009-
|
9
|
+
s.date = %q{2009-08-05}
|
10
10
|
s.email = %q{james@giraffesoft.ca}
|
11
11
|
s.extra_rdoc_files = [
|
12
12
|
"LICENSE",
|
13
13
|
"README.rdoc"
|
14
14
|
]
|
15
15
|
s.files = [
|
16
|
-
".
|
17
|
-
".gitignore",
|
16
|
+
".gitignore",
|
18
17
|
"LICENSE",
|
19
18
|
"README.rdoc",
|
20
19
|
"Rakefile",
|
21
20
|
"VERSION",
|
22
21
|
"lib/observational.rb",
|
22
|
+
"lib/observational/active_record_observers.rb",
|
23
|
+
"lib/observational/observable.rb",
|
24
|
+
"lib/observational/observer.rb",
|
23
25
|
"observational.gemspec",
|
26
|
+
"rails/init.rb",
|
27
|
+
"spec/active_record_observers_spec.rb",
|
28
|
+
"spec/observable_spec.rb",
|
24
29
|
"spec/observational_spec.rb",
|
30
|
+
"spec/observer_spec.rb",
|
25
31
|
"spec/spec_helper.rb"
|
26
32
|
]
|
27
33
|
s.has_rdoc = true
|
@@ -31,7 +37,10 @@ Gem::Specification.new do |s|
|
|
31
37
|
s.rubygems_version = %q{1.3.1}
|
32
38
|
s.summary = %q{TODO}
|
33
39
|
s.test_files = [
|
34
|
-
"spec/
|
40
|
+
"spec/active_record_observers_spec.rb",
|
41
|
+
"spec/observable_spec.rb",
|
42
|
+
"spec/observational_spec.rb",
|
43
|
+
"spec/observer_spec.rb",
|
35
44
|
"spec/spec_helper.rb"
|
36
45
|
]
|
37
46
|
|
data/rails/init.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Observational::ActiveRecord" do
|
4
|
+
include Observational
|
5
|
+
|
6
|
+
before do
|
7
|
+
@user = User.new
|
8
|
+
end
|
9
|
+
|
10
|
+
ActiveRecord::Callbacks::CALLBACKS.each do |callback|
|
11
|
+
describe "observing #{callback}" do
|
12
|
+
it "should fire the observer during that callback" do
|
13
|
+
self.expects(:subscription).with(@user)
|
14
|
+
observes :user, :invokes => :subscription, :on => callback
|
15
|
+
@user.send :callback, callback
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "adding custom callbacks" do
|
21
|
+
before do
|
22
|
+
User.send :define_callbacks, :after_something_else
|
23
|
+
self.expects(:subscription).with(@user)
|
24
|
+
observes :user, :invokes => :subscription, :after => :something_else
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should make it possible to observe those using observational" do
|
28
|
+
@user.send :callback, :after_something_else
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Observable" do
|
4
|
+
before do
|
5
|
+
@klass = Class.new do
|
6
|
+
include Observational::Observable
|
7
|
+
|
8
|
+
def save
|
9
|
+
fire_observers :after_create
|
10
|
+
end
|
11
|
+
end
|
12
|
+
@observer = Observational::Observer.new :method => :do_stuff,
|
13
|
+
:subscriber => stub(:do_stuff => ""),
|
14
|
+
:actions => :some_action
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should accept new observers" do
|
18
|
+
@klass.add_observer @observer
|
19
|
+
@klass.should be_observed_by(@observer)
|
20
|
+
end
|
21
|
+
|
22
|
+
it { @klass.should_not be_observed_by(:non_observer) }
|
23
|
+
|
24
|
+
describe "notifying observers" do
|
25
|
+
before do
|
26
|
+
@observable = @klass.new
|
27
|
+
@klass.add_observer @observer
|
28
|
+
@different_action = Observational::Observer.new :method => :do_stuff,
|
29
|
+
:subscriber => stub(:do_stuff => ""),
|
30
|
+
:actions => :something_else
|
31
|
+
@klass.add_observer @different_action
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should notify the observers who are interested" do
|
35
|
+
@observer.expects(:invoke).with(@observable)
|
36
|
+
@different_action.expects(:invoke).never
|
37
|
+
@observable.send :notify_observers, :some_action
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "removing the observers" do
|
42
|
+
before do
|
43
|
+
@klass.observers << @different_action
|
44
|
+
@klass.delete_observers
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should clear out all the observers" do
|
48
|
+
@klass.observers.should be_empty
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/spec/observational_spec.rb
CHANGED
@@ -1,27 +1,64 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
|
+
class Observed; end
|
4
|
+
|
3
5
|
describe "Observational" do
|
4
|
-
|
5
|
-
|
6
|
-
|
6
|
+
after { Observed.delete_observers }
|
7
|
+
|
8
|
+
describe "declaring an observer" do
|
9
|
+
before do
|
10
|
+
@observing_klass = Class.new do
|
11
|
+
observes :observed, :on => :some_action,
|
12
|
+
:invokes => :do_stuff
|
13
|
+
end
|
14
|
+
|
15
|
+
@observer = Observed.observers.first
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should set the observing class as the subscriber" do
|
19
|
+
@observer.subscriber.should == @observing_klass
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should set the action to the value of the :on parameter" do
|
23
|
+
@observer.actions.should == [:some_action]
|
24
|
+
end
|
7
25
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
26
|
+
it "should set the method to the value of the :invokes parameter" do
|
27
|
+
@observer.method.should == :do_stuff
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should raise ArgumentError if passed any unfamiliar keys" do
|
31
|
+
lambda { Class.new.observes :user, :asdf => "whatever" }.should raise_error(ArgumentError)
|
12
32
|
end
|
13
33
|
end
|
14
34
|
|
15
|
-
describe "
|
16
|
-
|
17
|
-
@
|
18
|
-
|
19
|
-
|
20
|
-
|
35
|
+
describe "using the activerecord shortcut :before => :whatever" do
|
36
|
+
before do
|
37
|
+
@observing_klass = Class.new do
|
38
|
+
observes :observed, :before => :create,
|
39
|
+
:invokes => :do_stuff
|
40
|
+
end
|
41
|
+
|
42
|
+
@observer = Observed.observers.first
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should create an observer with :action => :before_create" do
|
46
|
+
@observer.actions.should == [:before_create]
|
21
47
|
end
|
22
48
|
end
|
23
49
|
|
24
|
-
|
25
|
-
|
50
|
+
describe "using the activerecord shortcut :after => :whatever" do
|
51
|
+
before do
|
52
|
+
@observing_klass = Class.new do
|
53
|
+
observes :observed, :after => :create,
|
54
|
+
:invokes => :do_stuff
|
55
|
+
end
|
56
|
+
|
57
|
+
@observer = Observed.observers.first
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should create an observer with :action => :after_create" do
|
61
|
+
@observer.actions.should == [:after_create]
|
62
|
+
end
|
26
63
|
end
|
27
64
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Observer" do
|
4
|
+
before do
|
5
|
+
@klass = Class.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "without paraemeters specified" do
|
9
|
+
before do
|
10
|
+
@observer = Observational::Observer.new :subscriber => @klass,
|
11
|
+
:method => :do_stuff
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should invoke the :method with the object" do
|
15
|
+
@klass.expects(:do_stuff).with(:the_object)
|
16
|
+
@observer.invoke(:the_object)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "with particular parameters" do
|
21
|
+
before do
|
22
|
+
@observer = Observational::Observer.new :subscriber => @klass,
|
23
|
+
:method => :do_stuff,
|
24
|
+
:parameters => [:foo, :bar]
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should invoke the method with the parameters as specified" do
|
28
|
+
@klass.expects(:do_stuff).with("bar", "foo")
|
29
|
+
@observer.invoke(stub(:foo => "bar", :bar => "foo"))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "observing :after_create" do
|
34
|
+
before do
|
35
|
+
@observer = Observational::Observer.new :subscriber => @klass,
|
36
|
+
:method => :do_stuff,
|
37
|
+
:parameters => [:foo, :bar],
|
38
|
+
:actions => :after_create
|
39
|
+
end
|
40
|
+
subject { @observer }
|
41
|
+
|
42
|
+
it { should be_observes_action(:after_create) }
|
43
|
+
it { should_not be_observes_action(:other_stuff) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -35,7 +35,7 @@ class Message < ActiveRecord::Base
|
|
35
35
|
end
|
36
36
|
|
37
37
|
class Notifier
|
38
|
-
observes :user, :invokes => :deliver_new_user, :on => :
|
38
|
+
observes :user, :invokes => :deliver_new_user, :on => :after_create
|
39
39
|
|
40
40
|
def self.deliver_new_user(user)
|
41
41
|
end
|
@@ -48,3 +48,6 @@ class Creditor
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
require 'observational/active_record'
|
52
|
+
ActiveRecord::Base.send(:include, Observational::ActiveRecordObservers)
|
53
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: giraffesoft-observational
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Golick
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-08-05 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -23,18 +23,25 @@ extra_rdoc_files:
|
|
23
23
|
- LICENSE
|
24
24
|
- README.rdoc
|
25
25
|
files:
|
26
|
-
- .document
|
27
26
|
- .gitignore
|
28
27
|
- LICENSE
|
29
28
|
- README.rdoc
|
30
29
|
- Rakefile
|
31
30
|
- VERSION
|
32
31
|
- lib/observational.rb
|
32
|
+
- lib/observational/active_record_observers.rb
|
33
|
+
- lib/observational/observable.rb
|
34
|
+
- lib/observational/observer.rb
|
33
35
|
- observational.gemspec
|
36
|
+
- rails/init.rb
|
37
|
+
- spec/active_record_observers_spec.rb
|
38
|
+
- spec/observable_spec.rb
|
34
39
|
- spec/observational_spec.rb
|
40
|
+
- spec/observer_spec.rb
|
35
41
|
- spec/spec_helper.rb
|
36
42
|
has_rdoc: true
|
37
43
|
homepage: http://github.com/giraffesoft/observational
|
44
|
+
licenses:
|
38
45
|
post_install_message:
|
39
46
|
rdoc_options:
|
40
47
|
- --charset=UTF-8
|
@@ -55,10 +62,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
62
|
requirements: []
|
56
63
|
|
57
64
|
rubyforge_project:
|
58
|
-
rubygems_version: 1.
|
65
|
+
rubygems_version: 1.3.5
|
59
66
|
signing_key:
|
60
67
|
specification_version: 2
|
61
68
|
summary: TODO
|
62
69
|
test_files:
|
70
|
+
- spec/active_record_observers_spec.rb
|
71
|
+
- spec/observable_spec.rb
|
63
72
|
- spec/observational_spec.rb
|
73
|
+
- spec/observer_spec.rb
|
64
74
|
- spec/spec_helper.rb
|