giraffesoft-observational 0.1.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,3 +3,5 @@
3
3
  coverage
4
4
  rdoc
5
5
  pkg
6
+ doc
7
+ .yardoc
@@ -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 that responsibility would be better suited to the Notifier.
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, :on => :create
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, :on => :create
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 'rake/rdoctask'
35
- Rake::RDocTask.new do |rdoc|
36
- if File.exist?('VERSION.yml')
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.1
1
+ 0.2.3
@@ -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
- model_klass = model_name.to_s.classify.constantize
6
- observer_klass = self
7
- model_klass.send(:"after_#{opts[:on]}") do |object|
8
- argument = opts.has_key?(:with) ? object.send(opts[:with]) : object
9
- observer_klass.send(opts[:invokes], argument)
10
- end
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
+
@@ -2,26 +2,32 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{observational}
5
- s.version = "0.1.1"
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-07-16}
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
- ".document",
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/observational_spec.rb",
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
 
@@ -0,0 +1,2 @@
1
+ ActiveRecord::Base.send(:include, Observational::ActiveRecordObservers)
2
+ ActiveRecord::Base.send(:include, Observational::Observable)
@@ -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
@@ -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
- before do
5
- @user = User.new
6
- end
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
- describe "creating an observer on create" do
9
- it "should invoke that method on create of the observed object" do
10
- Notifier.expects(:deliver_new_user).with(@user)
11
- @user.save
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 "an observer that returns specific data from the observed object" do
16
- it "should pass that specific data to the observer method" do
17
- @user.save
18
- @message = Message.new :creator => @user
19
- Creditor.expects(:use_credits).with(@user)
20
- @message.save
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
- it "should raise ArgumentError if passed any unfamiliar keys" do
25
- lambda { Class.new.observes :user, :asdf => "whatever" }.should raise_error(ArgumentError)
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
+
@@ -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 => :create
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.1.1
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-07-16 00:00:00 -07:00
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.2.0
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
data/.document DELETED
@@ -1,5 +0,0 @@
1
- README.rdoc
2
- lib/**/*.rb
3
- bin/*
4
- features/**/*.feature
5
- LICENSE