Skip to content
Browse files

Initial checkin, Observables::Array included.

  • Loading branch information...
1 parent 972b7a0 commit c0232eab2521bd04556c5de720364638d343cfe4 @PlasticLizard committed Sep 30, 2010
View
2 Gemfile
@@ -0,0 +1,2 @@
+source 'http://rubygems.org'
+gemspec
View
60 Rakefile
@@ -1,53 +1,31 @@
require 'rubygems'
+require 'bundler/setup'
require 'rake'
-
-begin
- require 'jeweler'
- Jeweler::Tasks.new do |gem|
- gem.name = "Observables"
- gem.summary = %Q{TODO: one-line summary of your gem}
- gem.description = %Q{TODO: longer description of your gem}
- gem.email = "hereiam@sonic.net"
- gem.homepage = "http://github.com/PlasticLizard/Observables"
- gem.authors = ["Nathan Stults"]
- gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
- end
- Jeweler::GemcutterTasks.new
-rescue LoadError
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
-end
-
require 'rake/testtask'
+require File.expand_path('../lib/observables/version', __FILE__)
+
Rake::TestTask.new(:test) do |test|
test.libs << 'lib' << 'test'
test.pattern = 'test/**/test_*.rb'
- test.verbose = true
-end
-
-begin
- require 'rcov/rcovtask'
- Rcov::RcovTask.new do |test|
- test.libs << 'test'
- test.pattern = 'test/**/test_*.rb'
- test.verbose = true
- end
-rescue LoadError
- task :rcov do
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
- end
end
-task :test => :check_dependencies
-
task :default => :test
-require 'rake/rdoctask'
-Rake::RDocTask.new do |rdoc|
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
+desc 'Builds the gem'
+task :build do
+ sh "gem build observables.gemspec"
+end
- rdoc.rdoc_dir = 'rdoc'
- rdoc.title = "Observables #{version}"
- rdoc.rdoc_files.include('README*')
- rdoc.rdoc_files.include('lib/**/*.rb')
+desc 'Builds and installs the gem'
+task :install => :build do
+ sh "gem install observables-#{Observables::Version}"
end
+
+desc 'Tags version, pushes to remote, and pushes gem'
+task :release => :build do
+ sh "git tag v#{Observables::Version}"
+ sh "git push origin master"
+ sh "git push origin v#{Observables::Version}"
+ sh "gem push mongo_mapper-#{Observables::Version}.gem"
+end
+
View
31 lib/observables/array_watcher.rb
@@ -0,0 +1,31 @@
+
+module Observables
+ module ArrayWatcher
+ include Observables::Base
+
+ [:<<, :push, :concat, :fill, :insert, :unshift].each do |add_method|
+ class_eval <<-EOS
+ def #{add_method}(*args)
+ changing(:added,:trigger=>:#{add_method}) { super }
+ end
+ EOS
+ end
+
+ [:"[]=", :collect!, :map!, :flatten!, :replace, :reverse!, :sort!].each do |mod_method|
+ class_eval <<-EOS
+ def #{mod_method}(*args)
+ changing(:modified,:trigger=>:#{mod_method}) { super }
+ end
+ EOS
+ end
+
+ [:clear, :compact!, :delete, :delete_at, :delete_if, :pop, :reject!, :shift, :slice!, :uniq!].each do |del_method|
+ class_eval <<-EOS
+ def #{del_method}(*args)
+ changing(:removed,:trigger=>:#{del_method}) { super }
+ end
+ EOS
+ end
+
+ end
+end
View
32 lib/observables/base.rb
@@ -0,0 +1,32 @@
+require "active_support/notifications/fanout"
+require 'active_support/core_ext/object/duplicable'
+
+module Observables
+ module Base
+ def notifier
+ @notifier ||= ActiveSupport::Notifications::Fanout.new
+ end
+
+ def subscribe(pattern=nil,&block)
+ notifier.subscribe(pattern,&block)
+ end
+
+ def unsubscribe(subscriber)
+ notifier.unsubscribe(subscriber)
+ end
+
+ protected
+ def changing(change_type,opts)
+ args = create_event_args(change_type,opts)
+ notifier.publish "before_#{change_type}".to_sym, args
+ yield.tap do
+ notifier.publish "after_#{change_type}".to_sym, args
+ end
+ end
+
+ def create_event_args(change_type,opts={})
+ {:change_type=>change_type}.merge(opts)
+ end
+
+ end
+end
View
8 lib/observables/collections.rb
@@ -0,0 +1,8 @@
+module Observables
+ class Array < ::Array
+ include ArrayWatcher
+ def initialize(*args)
+ super(*args)
+ end
+ end
+end
View
10 lib/observables/extensions/array.rb
@@ -0,0 +1,10 @@
+class ::Array
+ def make_observable
+ class << self; include Observables::ArrayWatcher; end
+ end
+
+ def observable?
+ respond_to?(:notifier)
+ end
+
+end
View
4 lib/observables/version.rb
@@ -0,0 +1,4 @@
+# encoding: UTF-8
+module Observables
+ Version = '0.0.1'
+end
View
25 observables.gemspec
@@ -0,0 +1,25 @@
+# encoding: UTF-8
+require File.expand_path('../lib/observables/version', __FILE__)
+
+Gem::Specification.new do |s|
+ s.name = 'observables'
+ s.homepage = 'http://github.com/jnunemaker/observables'
+ s.summary = 'Observable arrays and hashes'
+ s.require_path = 'lib'
+ #s.default_executable = ''
+ s.authors = ['Nathan Stults']
+ s.email = ['hereiam@sonic.net']
+ #s.executables = ['mmconsole']
+ s.version = Observables::Version
+ s.platform = Gem::Platform::RUBY
+ s.files = Dir.glob("{bin,examples,lib,rails,test}/**/*") + %w[LICENSE README.rdoc]
+
+ s.add_dependency 'activesupport', '~> 3.0.0'
+ s.add_dependency 'i18n'
+
+ s.add_development_dependency 'rake'
+ s.add_development_dependency 'shoulda', '~> 2.11'
+ s.add_development_dependency 'timecop', '~> 0.3.1'
+ s.add_development_dependency 'mocha', '~> 0.9.8'
+end
+
View
21 test/extensions/test_extensions.rb
@@ -0,0 +1,21 @@
+require "test_helper"
+
+class TestExtensions < Test::Unit::TestCase
+ context "Array" do
+ context "#make_observable" do
+
+ should "detect an observable array" do
+ x, y = [], [].tap{|a|a.make_observable}
+ assert_equal false, x.observable?
+ assert y.observable?
+ end
+
+ should "make an instance of an array observable" do
+ x = []
+ assert_equal false, x.observable?
+ x.make_observable
+ assert x.observable?
+ end
+ end
+ end
+end
View
10 test/helper.rb
@@ -1,10 +0,0 @@
-require 'rubygems'
-require 'test/unit'
-require 'shoulda'
-
-$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
-$LOAD_PATH.unshift(File.dirname(__FILE__))
-require 'Observables'
-
-class Test::Unit::TestCase
-end
View
62 test/test_array_watcher.rb
@@ -0,0 +1,62 @@
+require "test_helper"
+
+class TestArrayWatcher < Test::Unit::TestCase
+ context "An array which has included Observables::ArrayWatcher" do
+ setup do
+ @ary = Array.new([1,2,3]).tap do |a|
+ class << a
+ include Observables::ArrayWatcher
+ end
+ end
+ end
+
+ should "notify observers of any change that adds elements to itself" do
+ before_methods, after_methods = [],[]
+ method_list = [:<<, :push, :concat, :fill, :insert, :unshift]
+ @ary.subscribe(/before_added/){|_,args|before_methods<<args[:trigger]}
+ @ary.subscribe(/after_added/) {|_,args|after_methods<<args[:trigger]}
+ method_list.each do |method|
+ @ary.send(method,*args_for(method))
+ end
+ assert_equal method_list, before_methods
+ assert_equal method_list, after_methods
+ end
+
+ should "notify observers of any change that modifies elements" do
+ before_methods, after_methods = [],[]
+ method_list = [:"[]=", :collect!, :map!, :flatten!, :replace, :reverse!, :sort!]
+ @ary.subscribe(/before_modified/){|_,args|before_methods<<args[:trigger]}
+ @ary.subscribe(/after_modified/) {|_,args|after_methods<<args[:trigger]}
+ method_list.each do |method|
+ args = args_for(method)
+ args ? @ary.send(method,*args) : @ary.send(method)
+ end
+ assert_equal method_list, before_methods
+ assert_equal method_list, after_methods
+ end
+
+ should "notify observers of any change that removes elements" do
+ before_methods, after_methods = [],[]
+ method_list = [:clear, :compact!, :delete, :delete_at, :delete_if, :pop, :reject!, :shift, :slice!, :uniq!]
+ @ary.subscribe(/before_removed/){|_,args|before_methods<<args[:trigger]}
+ @ary.subscribe(/after_removed/) {|_,args|after_methods<<args[:trigger]}
+ method_list.each do |method|
+ args = args_for(method)
+ args ? @ary.send(method,*args) : @ary.send(method)
+ end
+ assert_equal method_list, before_methods
+ assert_equal method_list, after_methods
+ end
+ end
+
+ def args_for(method)
+ case method
+ when :<<, :push, :unshift, :delete, :delete_at then [1]
+ when :concat, :replace then [[1,2]]
+ when :fill then ["*"]
+ when :insert, :"[]=", :slice! then [1,1]
+ when :flatten!, :collect!, :map!, :reverse!, :sort!,
+ :clear, :compact!, :pop, :reject!, :uniq!, :delete_if, :shift then nil
+ end
+ end
+end
View
53 test/test_base.rb
@@ -0,0 +1,53 @@
+require 'test_helper'
+
+class TestBase < Test::Unit::TestCase
+
+ context "An instance of a class that include Observables:Base" do
+ setup do
+ @obs = Class.new{include Observables::Base}.new
+ end
+
+ should "have a notifier" do
+ assert @obs.notifier.is_a?(ActiveSupport::Notifications::Fanout)
+ end
+
+ should "allow subscriptions with just a block" do
+ assert @obs.subscribe{}.is_a?(ActiveSupport::Notifications::Fanout::Subscriber)
+ end
+
+ should "allow subscriptions with a pattern and a block" do
+ assert @obs.subscribe(/whatever/){}.is_a?(ActiveSupport::Notifications::Fanout::Subscriber)
+ end
+
+ should "allow unsubscribing" do
+ sub = @obs.subscribe(/hi/){}
+ assert @obs.notifier.listening?("hi")
+ @obs.unsubscribe(sub)
+ assert_equal false, @obs.notifier.listening?("hi")
+ end
+
+ should "publish a before notification prior to executing a change" do
+ vals = []
+ x = 0
+ @obs.subscribe(/before/){|c,a|vals << c << a << x}
+ @obs.send(:changing,:a_change,:this=>:that){x+=1}
+ assert_equal :before_a_change, vals[0]
+ assert_equal @obs.send(:create_event_args,:a_change,:this=>:that),vals[1]
+ assert_equal 0, vals[2]
+ assert_equal 1, x
+ end
+
+ should "publish an after notification after executing a change" do
+ vals = []
+ x = 0
+ @obs.subscribe(/after/){|c,a|vals << c << a << x}
+ @obs.send(:changing,:a_change,:this=>:that){x+=1}
+ assert_equal :after_a_change, vals[0]
+ assert_equal @obs.send(:create_event_args,:a_change,:this=>:that),vals[1]
+ assert_equal 1, vals[2]
+ assert_equal 1, x
+ end
+
+ end
+
+end
View
15 test/test_helper.rb
@@ -0,0 +1,15 @@
+require 'rubygems'
+require 'bundler/setup'
+
+$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
+
+require 'observables'
+
+require 'shoulda'
+require 'timecop'
+require 'mocha'
+
+class Test::Unit::TestCase
+
+end
+
View
13 test/test_observable_array.rb
@@ -0,0 +1,13 @@
+require "test_helper"
+
+class TestObservablesArray < Test::Unit::TestCase
+ context "An Observables::Array" do
+ should "be observable" do
+ assert Observables::Array.new.observable?
+ end
+ should "be an array" do
+ assert Observables::Array.new.kind_of?(::Array)
+ end
+
+ end
+end

0 comments on commit c0232ea

Please sign in to comment.
Something went wrong with that request. Please try again.