observational 0.2.5

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 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