observational 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ doc
7
+ .yardoc
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 James Golick
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,54 @@
1
+ = Observational
2
+
3
+ How many times have you seen this in a rails app?
4
+
5
+ class User
6
+ after_create :deliver_welcome_message
7
+
8
+ protected
9
+ def deliver_welcome_message
10
+ Notifier.deliver_welcome_message(self)
11
+ end
12
+ end
13
+
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
+
16
+ Observational makes it possible to make it the Notifier's responsibility, using the observer pattern.
17
+
18
+ The equivalent of the above example is:
19
+
20
+ class Notifier < ActionMailer::Base
21
+ observes :user, :invokes => :deliver_welcome_message, :after => :create
22
+
23
+ def welcome_message(user)
24
+ # do mailer stuff here
25
+ end
26
+ end
27
+
28
+ After a user is created, Notifier.deliver_welcome_message(that_user) will be invoked.
29
+
30
+ It's also possible to specify that the observer method gets called with a specific attribute from the observed object.
31
+
32
+ class Creditor
33
+ observes :message, :invokes => :use_credit, :with => :creator, :after => :create
34
+
35
+ def use_credit(user)
36
+ # do something
37
+ end
38
+ end
39
+
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 :-).
51
+
52
+ == Copyright
53
+
54
+ Copyright (c) 2009 James Golick. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "observational"
8
+ gem.summary = %Q{TODO}
9
+ gem.email = "james@giraffesoft.ca"
10
+ gem.homepage = "http://github.com/giraffesoft/observational"
11
+ gem.authors = ["James Golick"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'spec/rake/spectask'
20
+ Spec::Rake::SpecTask.new(:spec) do |spec|
21
+ spec.libs << 'lib' << 'spec'
22
+ spec.spec_files = FileList['spec/**/*_spec.rb']
23
+ end
24
+
25
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.pattern = 'spec/**/*_spec.rb'
28
+ spec.rcov = true
29
+ end
30
+
31
+
32
+ task :default => :spec
33
+
34
+ require 'yard'
35
+ YARD::Rake::YardocTask.new do |t|
36
+ t.files = ['lib/**/*.rb']
37
+ end
38
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.5
@@ -0,0 +1,32 @@
1
+ require 'observational/observer'
2
+ require 'observational/observable'
3
+
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
+ #
16
+ def observes(model_name, opts = {})
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)
28
+ end
29
+ end
30
+
31
+ Class.send(:include, Observational) unless Class.include?(Observational)
32
+
@@ -0,0 +1,8 @@
1
+ module Observational
2
+ module ActiveRecordObservers
3
+ def run_callbacks(kind, options = {}, &block)
4
+ self.send(:notify_observers, kind)
5
+ super
6
+ end
7
+ end
8
+ end
@@ -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.to_sym)
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
+
@@ -0,0 +1,56 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{observational}
5
+ s.version = "0.2.5"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["James Golick"]
9
+ s.date = %q{2009-08-05}
10
+ s.email = %q{james@giraffesoft.ca}
11
+ s.extra_rdoc_files = [
12
+ "LICENSE",
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ ".gitignore",
17
+ "LICENSE",
18
+ "README.rdoc",
19
+ "Rakefile",
20
+ "VERSION",
21
+ "lib/observational.rb",
22
+ "lib/observational/active_record_observers.rb",
23
+ "lib/observational/observable.rb",
24
+ "lib/observational/observer.rb",
25
+ "observational.gemspec",
26
+ "rails/init.rb",
27
+ "spec/active_record_observers_spec.rb",
28
+ "spec/observable_spec.rb",
29
+ "spec/observational_spec.rb",
30
+ "spec/observer_spec.rb",
31
+ "spec/spec_helper.rb"
32
+ ]
33
+ s.has_rdoc = true
34
+ s.homepage = %q{http://github.com/giraffesoft/observational}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.1}
38
+ s.summary = "Use the observer pattern to better divide your objects' responsibilities."
39
+ s.test_files = [
40
+ "spec/active_record_observers_spec.rb",
41
+ "spec/observable_spec.rb",
42
+ "spec/observational_spec.rb",
43
+ "spec/observer_spec.rb",
44
+ "spec/spec_helper.rb"
45
+ ]
46
+
47
+ if s.respond_to? :specification_version then
48
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
49
+ s.specification_version = 2
50
+
51
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
52
+ else
53
+ end
54
+ else
55
+ end
56
+ end
data/rails/init.rb ADDED
@@ -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.to_sym
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
@@ -0,0 +1,64 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ class Observed; end
4
+
5
+ describe "Observational" do
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
25
+
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)
32
+ end
33
+ end
34
+
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]
47
+ end
48
+ end
49
+
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
63
+ end
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
+
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'observational'
7
+ require 'activerecord'
8
+
9
+ Spec::Runner.configure do |config|
10
+ config.mock_with :mocha
11
+ end
12
+
13
+ ActiveRecord::Base.configurations = {'sqlite3' => {:adapter => 'sqlite3', :database => ':memory:'}}
14
+ ActiveRecord::Base.establish_connection('sqlite3')
15
+
16
+ ActiveRecord::Base.logger = Logger.new(STDERR)
17
+ ActiveRecord::Base.logger.level = Logger::WARN
18
+
19
+ ActiveRecord::Schema.define(:version => 0) do
20
+ create_table :users do |t|
21
+ t.string :email, :default => ''
22
+ end
23
+
24
+ create_table :messages do |t|
25
+ t.integer :creator_id
26
+ t.string :email, :default => ''
27
+ end
28
+ end
29
+
30
+ class User < ActiveRecord::Base
31
+ end
32
+
33
+ class Message < ActiveRecord::Base
34
+ belongs_to :creator, :class_name => "User"
35
+ end
36
+
37
+ class Notifier
38
+ observes :user, :invokes => :deliver_new_user, :on => :after_create
39
+
40
+ def self.deliver_new_user(user)
41
+ end
42
+ end
43
+
44
+ class Creditor
45
+ observes :message, :invokes => :use_credits, :with => :creator, :on => :create
46
+
47
+ def self.use_credits(user)
48
+ end
49
+ end
50
+
51
+ require 'observational/active_record_observers'
52
+ User.send(:include, Observational::ActiveRecordObservers)
53
+
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: observational
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.5
5
+ platform: ruby
6
+ authors:
7
+ - James Golick
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-05 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: james@giraffesoft.ca
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - .gitignore
27
+ - LICENSE
28
+ - README.rdoc
29
+ - Rakefile
30
+ - VERSION
31
+ - lib/observational.rb
32
+ - lib/observational/active_record_observers.rb
33
+ - lib/observational/observable.rb
34
+ - lib/observational/observer.rb
35
+ - observational.gemspec
36
+ - rails/init.rb
37
+ - spec/active_record_observers_spec.rb
38
+ - spec/observable_spec.rb
39
+ - spec/observational_spec.rb
40
+ - spec/observer_spec.rb
41
+ - spec/spec_helper.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/giraffesoft/observational
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --charset=UTF-8
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.2
67
+ signing_key:
68
+ specification_version: 2
69
+ summary: Use the observer pattern to better divide your objects' responsibilities.
70
+ test_files:
71
+ - spec/active_record_observers_spec.rb
72
+ - spec/observable_spec.rb
73
+ - spec/observational_spec.rb
74
+ - spec/observer_spec.rb
75
+ - spec/spec_helper.rb