├── lib ├── no_peeping_toms │ └── version.rb └── no_peeping_toms.rb ├── spec ├── db │ ├── database.yml │ └── schema.rb ├── spec_helper.rb └── no_peeping_toms_spec.rb ├── .gitignore ├── Gemfile ├── History.rdoc ├── no_peeping_toms.gemspec └── README.rdoc /lib/no_peeping_toms/version.rb: -------------------------------------------------------------------------------- 1 | module NoPeepingToms 2 | VERSION = "2.1.3" 3 | end 4 | -------------------------------------------------------------------------------- /spec/db/database.yml: -------------------------------------------------------------------------------- 1 | sqlite3: 2 | :adapter: sqlite3 3 | :database: ":memory:" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | spec/debug.log 6 | .*.sw? 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in no-peeping-toms.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /spec/db/schema.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Schema.define(:version => 0) do 2 | create_table :people, :force => true do |t| 3 | t.column :name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | 3 | plugin_spec_dir = File.dirname(__FILE__) 4 | plugin_lib_dir = File.join(plugin_spec_dir, '..', 'lib') 5 | 6 | require File.join(plugin_lib_dir, 'no_peeping_toms') 7 | 8 | # Setup ActiveRecord 9 | ActiveRecord::Base.logger = Logger.new(plugin_spec_dir + "/debug.log") 10 | databases = YAML::load(IO.read(plugin_spec_dir + "/db/database.yml")) 11 | ActiveRecord::Base.establish_connection(databases[ENV["DB"] || "sqlite3"]) 12 | load(File.join(plugin_spec_dir, "db", "schema.rb")) 13 | -------------------------------------------------------------------------------- /History.rdoc: -------------------------------------------------------------------------------- 1 | === Version 2.1.3 / 2012-07-26 2 | * Fix deprecation warning 3 | 4 | === Version 2.0.1 / 2011-02-16 5 | * Supports current Rails 3.x release. No longer supports Rails 2. 6 | 7 | === Version 1.1.0 / 2010-01-06 8 | 9 | * Explicit enable/disable API (Corey Haines & Drew Olson) 10 | ActiveRecord::Observer.enable_observers 11 | ActiveRecord::Observer.disable_observers 12 | NOTE: Observers are enabled by default. You now need to explicitly disable them in your test helper 13 | 14 | === Version 1.0.1 / 2010-01-06 15 | 16 | * official gem release 17 | * Updated #with_observers to take in class references, or an anonymous observer (Zach Dennis) 18 | * peeping toms can be re-enabled, specifically for use in Cucumber (Brandon Keepers) 19 | To enable: 20 | ActiveRecord::Observer.allow_peeping_toms = true 21 | * Fixed broken specs. Added fix for exceptions raised in with_observers (Jeff Siegel) 22 | -------------------------------------------------------------------------------- /no_peeping_toms.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "no_peeping_toms/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "no_peeping_toms" 7 | s.version = NoPeepingToms::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Pat Maddox"] 10 | s.email = ["pat.maddox@gmail.com"] 11 | s.homepage = "https://github.com/patmaddox/no-peeping-toms" 12 | s.summary = %q{Disables observers during testing, allowing you to write model tests that are completely decoupled from the observer.} 13 | s.description = %q{Disables observers during testing, allowing you to write model tests that are completely decoupled from the observer.} 14 | s.rubyforge_project = "no_peeping_toms" 15 | 16 | s.files = Dir["*.rdoc", "{lib,spec}/**/*"] 17 | s.require_paths = ["lib"] 18 | 19 | s.add_runtime_dependency 'activerecord', '>=3.0.0' 20 | s.add_runtime_dependency 'activesupport', '>=3.0.0' 21 | 22 | s.add_development_dependency 'rspec', '~>2.5' 23 | s.add_development_dependency 'sqlite3' 24 | end 25 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = no_peeping_toms 2 | 3 | This plugin disables observers in your specs, so that model specs can run in complete isolation. 4 | 5 | == NOTE: This functionality is included in Rails 3.1 and above: https://apidock.com/rails/v3.1.0/ActiveModel/ObserverArray/disable. Use that instead! 6 | 7 | == Installation 8 | 9 | Add to your Gemfile: 10 | 11 | gem 'no_peeping_toms', :git => 'git://github.com/patmaddox/no-peeping-toms.git' 12 | 13 | and run `bundle install`. 14 | 15 | no_peeping_toms >= 2.0.0 only supports on Rails 3. If you need Rails 2 support, use 1.1.0: `gem install no_peeping_toms -v 1.1.0` 16 | 17 | == Usage 18 | 19 | To disable observers, place the following code in your test.rb, or spec_helper.rb, or wherever: 20 | 21 | ActiveRecord::Observer.disable_observers 22 | 23 | You can easily reenable observers: 24 | 25 | ActiveRecord::Observer.enable_observers 26 | 27 | You can choose to run some code with specific observers turned on. This is useful when spec'ing an observer. For example, if you write the following observer: 28 | 29 | class PersonObserver < ActiveRecord::Observer 30 | def before_update(person) 31 | old_person = Person.find person.id 32 | if old_person.name != person.name 33 | NameChange.create! :person => person, :old_name => old_person.name, :new_name => person.name 34 | end 35 | end 36 | end 37 | 38 | You can spec the Person class in complete isolation. 39 | 40 | describe Person, " when changing a name" do 41 | before(:each) do 42 | @person = Person.create! :name => "Pat Maddox" 43 | end 44 | 45 | # By default, don't run any observers 46 | it "should not register a name change" do 47 | lambda { @person.update_attribute :name, "Don Juan Demarco" }.should_not change(NameChange, :count) 48 | end 49 | 50 | # Run only a portion of code with certain observers turned on 51 | it "should register a name change with the person observer turned on" do 52 | ActiveRecord::Observer.with_observers(:person_observer) do 53 | lambda { @person.update_attribute :name, "Don Juan Demarco" }.should change(NameChange, :count).by(1) 54 | end 55 | 56 | lambda { @person.update_attribute :name, "Man Without a Name" }.should_not change(NameChange, :count) 57 | end 58 | end 59 | 60 | == Credits 61 | 62 | * Brandon Keepers 63 | * Corey Haines 64 | * Drew Olson 65 | * Jeff Siegel 66 | * Zach Dennis 67 | * Andy Lindeman 68 | * Ryan McGeary 69 | 70 | Copyright (c) 2007-2011 Pat Maddox, released under the MIT license. 71 | -------------------------------------------------------------------------------- /lib/no_peeping_toms.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | require 'active_support/core_ext/class/attribute_accessors' 3 | require 'active_support/core_ext/string/inflections' 4 | 5 | module NoPeepingToms 6 | extend ActiveSupport::Concern 7 | 8 | included do 9 | # Define class-level accessors 10 | cattr_accessor :default_observers_enabled, :observers_enabled 11 | 12 | # By default, enable all observers 13 | enable_observers 14 | self.observers_enabled = [] 15 | 16 | alias_method_chain :define_callbacks, :enabled_check 17 | end 18 | 19 | module ClassMethods 20 | # Enables all observers (default behavior) 21 | def enable_observers 22 | self.default_observers_enabled = true 23 | end 24 | 25 | # Disables all observers 26 | def disable_observers 27 | self.default_observers_enabled = false 28 | end 29 | 30 | # Run a block with a specific set of observers enabled 31 | def with_observers(*observer_syms) 32 | self.observers_enabled = Array(observer_syms).map do |o| 33 | o.respond_to?(:instance) ? o.instance : o.to_s.classify.constantize.instance 34 | end 35 | yield 36 | ensure 37 | self.observers_enabled = [] 38 | end 39 | 40 | # Determines whether an observer is enabled. Either: 41 | # - All observers are enabled OR 42 | # - The observer is in the whitelist 43 | def observer_enabled?(observer) 44 | default_observers_enabled or self.observers_enabled.include?(observer) 45 | end 46 | end 47 | 48 | # Overrides ActiveRecord#define_callbacks so that observers are only called 49 | # when enabled. 50 | # 51 | # This is a bit yuck being a protected method, but appears to be the cleanest 52 | # way so far 53 | def define_callbacks_with_enabled_check(klass) 54 | observer = self 55 | observer_name = observer.class.name.underscore.gsub('/', '__') 56 | 57 | ActiveRecord::Callbacks::CALLBACKS.each do |callback| 58 | next unless respond_to?(callback) 59 | callback_meth = :"_notify_#{observer_name}_for_#{callback}" 60 | unless klass.respond_to?(callback_meth) 61 | klass.send(:define_method, callback_meth) do 62 | observer.send(callback, self) if observer.observer_enabled? 63 | end 64 | klass.send(callback, callback_meth) 65 | end 66 | end 67 | end 68 | 69 | # Enables interception of custom observer notifications, i.e. 70 | # notify_observers(:custom_notification) 71 | def update(*args, &block) 72 | super if observer_enabled? 73 | end 74 | 75 | # Determines whether this observer should be run 76 | def observer_enabled? 77 | self.class.observer_enabled?(self) 78 | end 79 | end 80 | 81 | ActiveRecord::Observer.__send__ :include, NoPeepingToms 82 | -------------------------------------------------------------------------------- /spec/no_peeping_toms_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | module NoPeepingTomsSpec 4 | class Person < ActiveRecord::Base; end 5 | class SpecialPerson < Person; end 6 | class HungryPerson < Person 7 | def eat_dinner! 8 | notify_observers(:ate_dinner) 9 | end 10 | end 11 | 12 | class DinnerObserver < ActiveRecord::Observer 13 | observe Person 14 | cattr_accessor :called 15 | 16 | def ate_dinner(person) 17 | self.class.called ||= 0 18 | self.class.called += 1 19 | end 20 | end 21 | 22 | class PersonObserver < ActiveRecord::Observer 23 | cattr_accessor :called 24 | 25 | def before_create(person) 26 | self.class.called ||= 0 27 | self.class.called += 1 28 | end 29 | end 30 | 31 | class AnotherObserver < ActiveRecord::Observer 32 | observe Person 33 | cattr_accessor :called 34 | 35 | def before_create(person) 36 | self.class.called ||= 0 37 | self.class.called += 1 38 | end 39 | end 40 | 41 | # Register the observers with the host app 42 | PersonObserver.instance 43 | AnotherObserver.instance 44 | DinnerObserver.instance 45 | 46 | describe NoPeepingToms, "configuration" do 47 | it "enables observers by default" do 48 | load 'no_peeping_toms.rb' 49 | ActiveRecord::Observer.default_observers_enabled.should be_true 50 | end 51 | 52 | it "runs default observers when default observers are enabled" do 53 | ActiveRecord::Observer.enable_observers 54 | PersonObserver.called = 0 55 | Person.create! 56 | PersonObserver.called.should == 1 57 | end 58 | 59 | it "does not run default observers when default observers are disabled" do 60 | ActiveRecord::Observer.disable_observers 61 | PersonObserver.called = 0 62 | Person.create! 63 | PersonObserver.called.should == 0 64 | end 65 | 66 | it "only runs observers once on descendent classes" do 67 | ActiveRecord::Observer.enable_observers 68 | PersonObserver.called = 0 69 | SpecialPerson.create! 70 | PersonObserver.called.should == 1 71 | end 72 | end 73 | 74 | describe ActiveRecord::Observer, 'with_observers' do 75 | before(:each) do 76 | ActiveRecord::Observer.disable_observers 77 | end 78 | 79 | it "should enable an observer via stringified class name" do 80 | PersonObserver.called = false 81 | ActiveRecord::Observer.with_observers("NoPeepingTomsSpec::PersonObserver") { Person.create! } 82 | PersonObserver.called.should be_true 83 | end 84 | 85 | it "should enable an observer via class" do 86 | PersonObserver.called = false 87 | ActiveRecord::Observer.with_observers(NoPeepingTomsSpec::PersonObserver) { Person.create! } 88 | PersonObserver.called.should be_true 89 | end 90 | 91 | it "should accept multiple observers" do 92 | PersonObserver.called = false 93 | AnotherObserver.called = false 94 | ActiveRecord::Observer.with_observers(NoPeepingTomsSpec::PersonObserver, NoPeepingTomsSpec::AnotherObserver) do 95 | Person.create! 96 | end 97 | PersonObserver.called.should be_true 98 | AnotherObserver.called.should be_true 99 | end 100 | 101 | it "should not call observers with custom notifications if they are disabled" do 102 | DinnerObserver.called = 0 103 | lambda { 104 | HungryPerson.new.eat_dinner! 105 | }.should_not change(DinnerObserver, :called) 106 | end 107 | 108 | it "should call observers with custom notifications if they are enabled" do 109 | DinnerObserver.called = 0 110 | ActiveRecord::Observer.with_observers(NoPeepingTomsSpec::DinnerObserver) do 111 | lambda { 112 | HungryPerson.new.eat_dinner! 113 | }.should change(DinnerObserver, :called).by(1) 114 | end 115 | end 116 | 117 | it "should ensure peeping toms are reset after raised exception" do 118 | lambda { 119 | ActiveRecord::Observer.with_observers(NoPeepingTomsSpec::PersonObserver) do 120 | raise ArgumentError, "Michael, I've made a huge mistake" 121 | end 122 | }.should raise_error(ArgumentError) 123 | ActiveRecord::Observer.observers_enabled.should_not include(NoPeepingTomsSpec::PersonObserver) 124 | end 125 | end 126 | end 127 | --------------------------------------------------------------------------------