rails-observers 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +11 -0
- data/LICENSE +22 -0
- data/README.md +102 -0
- data/Rakefile +34 -0
- data/lib/generators/active_record/observer/observer_generator.rb +17 -0
- data/lib/generators/active_record/observer/templates/observer.rb +4 -0
- data/lib/generators/rails/observer/USAGE +12 -0
- data/lib/generators/rails/observer/observer_generator.rb +7 -0
- data/lib/generators/test_unit/observer/observer_generator.rb +15 -0
- data/lib/generators/test_unit/observer/templates/unit_test.rb +9 -0
- data/lib/rails-observers.rb +30 -0
- data/lib/rails/observers/action_controller/caching.rb +12 -0
- data/lib/rails/observers/action_controller/caching/sweeping.rb +113 -0
- data/lib/rails/observers/active_model/active_model.rb +4 -0
- data/lib/rails/observers/active_model/observer_array.rb +152 -0
- data/lib/rails/observers/active_model/observing.rb +374 -0
- data/lib/rails/observers/activerecord/active_record.rb +5 -0
- data/lib/rails/observers/activerecord/base.rb +8 -0
- data/lib/rails/observers/activerecord/observer.rb +125 -0
- data/lib/rails/observers/version.rb +5 -0
- data/rails-observers.gemspec +26 -0
- data/test/configuration_test.rb +37 -0
- data/test/console_test.rb +38 -0
- data/test/fixtures/developers.yml +4 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/topics.yml +41 -0
- data/test/generators/generators_test_helper.rb +16 -0
- data/test/generators/namespaced_generators_test.rb +34 -0
- data/test/generators/observer_generator_test.rb +33 -0
- data/test/helper.rb +74 -0
- data/test/isolation/abstract_unit.rb +108 -0
- data/test/lifecycle_test.rb +249 -0
- data/test/models/observers.rb +27 -0
- data/test/observer_array_test.rb +222 -0
- data/test/observing_test.rb +183 -0
- data/test/rake_test.rb +40 -0
- data/test/sweeper_test.rb +83 -0
- data/test/transaction_callbacks_test.rb +278 -0
- metadata +216 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/rails/observers/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "rails-observers"
|
6
|
+
s.authors = ["Rafael Mendonça França", "Steve Klabnik"]
|
7
|
+
s.email = ["rafaelmfranca@gmail.com", "steve@steveklabnik.com"]
|
8
|
+
s.description = %q{Rails observer (removed from core in Rails 4.0)}
|
9
|
+
s.summary = %q{ActiveModel::Observer, ActiveRecord::Observer and ActionController::Caching::Sweeper extracted from Rails.}
|
10
|
+
s.homepage = "https://github.com/rails/rails-observers"
|
11
|
+
s.version = Rails::Observers::VERSION
|
12
|
+
|
13
|
+
s.files = [".gitignore","Gemfile","LICENSE","README.md","Rakefile","lib/generators/active_record/observer/observer_generator.rb","lib/generators/active_record/observer/templates/observer.rb","lib/generators/rails/observer/USAGE","lib/generators/rails/observer/observer_generator.rb","lib/generators/test_unit/observer/observer_generator.rb","lib/generators/test_unit/observer/templates/unit_test.rb","lib/rails-observers.rb","lib/rails/observers/action_controller/caching.rb","lib/rails/observers/action_controller/caching/sweeping.rb","lib/rails/observers/active_model/active_model.rb","lib/rails/observers/active_model/observer_array.rb","lib/rails/observers/active_model/observing.rb","lib/rails/observers/activerecord/active_record.rb","lib/rails/observers/activerecord/base.rb","lib/rails/observers/activerecord/observer.rb","lib/rails/observers/version.rb","rails-observers.gemspec","test/configuration_test.rb","test/console_test.rb","test/fixtures/developers.yml","test/fixtures/minimalistics.yml","test/fixtures/topics.yml","test/generators/generators_test_helper.rb","test/generators/namespaced_generators_test.rb","test/generators/observer_generator_test.rb","test/helper.rb","test/isolation/abstract_unit.rb","test/lifecycle_test.rb","test/models/observers.rb","test/observer_array_test.rb","test/observing_test.rb","test/rake_test.rb","test/sweeper_test.rb","test/transaction_callbacks_test.rb"]
|
14
|
+
s.test_files = ["test/configuration_test.rb","test/console_test.rb","test/fixtures/developers.yml","test/fixtures/minimalistics.yml","test/fixtures/topics.yml","test/generators/generators_test_helper.rb","test/generators/namespaced_generators_test.rb","test/generators/observer_generator_test.rb","test/helper.rb","test/isolation/abstract_unit.rb","test/lifecycle_test.rb","test/models/observers.rb","test/observer_array_test.rb","test/observing_test.rb","test/rake_test.rb","test/sweeper_test.rb","test/transaction_callbacks_test.rb"]
|
15
|
+
s.executables = []
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_dependency 'railties', '~> 4.0.0.beta'
|
19
|
+
|
20
|
+
s.add_development_dependency 'minitest', '>= 3'
|
21
|
+
s.add_development_dependency 'activerecord', '~> 4.0.0.beta'
|
22
|
+
s.add_development_dependency 'activemodel', '~> 4.0.0.beta'
|
23
|
+
s.add_development_dependency 'actionmailer', '~> 4.0.0.beta'
|
24
|
+
s.add_development_dependency 'actionpack', '~> 4.0.0.beta'
|
25
|
+
s.add_development_dependency 'sqlite3', '~> 1.3'
|
26
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'isolation/abstract_unit'
|
2
|
+
require 'rails-observers'
|
3
|
+
|
4
|
+
class ConfigurationTest < ActiveSupport::TestCase
|
5
|
+
include ActiveSupport::Testing::Isolation
|
6
|
+
|
7
|
+
def setup
|
8
|
+
build_app
|
9
|
+
boot_rails
|
10
|
+
FileUtils.rm_rf("#{app_path}/config/environments")
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
teardown_app
|
15
|
+
end
|
16
|
+
|
17
|
+
test "config.active_record.observers" do
|
18
|
+
add_to_config <<-RUBY
|
19
|
+
config.active_record.observers = :foo_observer
|
20
|
+
RUBY
|
21
|
+
|
22
|
+
app_file 'app/models/foo.rb', <<-RUBY
|
23
|
+
class Foo < ActiveRecord::Base
|
24
|
+
end
|
25
|
+
RUBY
|
26
|
+
|
27
|
+
app_file 'app/models/foo_observer.rb', <<-RUBY
|
28
|
+
class FooObserver < ActiveRecord::Observer
|
29
|
+
end
|
30
|
+
RUBY
|
31
|
+
|
32
|
+
require "#{app_path}/config/environment"
|
33
|
+
|
34
|
+
_ = ActiveRecord::Base
|
35
|
+
assert defined?(FooObserver)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'isolation/abstract_unit'
|
2
|
+
require 'rails-observers'
|
3
|
+
|
4
|
+
class ConsoleTest < ActiveSupport::TestCase
|
5
|
+
include ActiveSupport::Testing::Isolation
|
6
|
+
|
7
|
+
def setup
|
8
|
+
build_app
|
9
|
+
boot_rails
|
10
|
+
end
|
11
|
+
|
12
|
+
def teardown
|
13
|
+
teardown_app
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_environment
|
17
|
+
require "#{rails_root}/config/environment"
|
18
|
+
Rails.application.sandbox = false
|
19
|
+
Rails.application.load_console
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_active_record_does_not_panic_when_referencing_an_observed_constant
|
23
|
+
add_to_config "config.active_record.observers = :user_observer"
|
24
|
+
|
25
|
+
app_file "app/models/user.rb", <<-MODEL
|
26
|
+
class User < ActiveRecord::Base
|
27
|
+
end
|
28
|
+
MODEL
|
29
|
+
|
30
|
+
app_file "app/models/user_observer.rb", <<-MODEL
|
31
|
+
class UserObserver < ActiveRecord::Observer
|
32
|
+
end
|
33
|
+
MODEL
|
34
|
+
|
35
|
+
load_environment
|
36
|
+
assert_nothing_raised { User }
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
first:
|
2
|
+
id: 1
|
3
|
+
title: The First Topic
|
4
|
+
author_name: David
|
5
|
+
author_email_address: david@loudthinking.com
|
6
|
+
written_on: 2003-07-16t15:28:11.2233+01:00
|
7
|
+
last_read: 2004-04-15
|
8
|
+
bonus_time: 2005-01-30t15:28:00.00+01:00
|
9
|
+
content: Have a nice day
|
10
|
+
approved: false
|
11
|
+
replies_count: 1
|
12
|
+
|
13
|
+
second:
|
14
|
+
id: 2
|
15
|
+
title: The Second Topic of the day
|
16
|
+
author_name: Mary
|
17
|
+
written_on: 2004-07-15t15:28:00.0099+01:00
|
18
|
+
content: Have a nice day
|
19
|
+
approved: true
|
20
|
+
replies_count: 0
|
21
|
+
parent_id: 1
|
22
|
+
type: Reply
|
23
|
+
|
24
|
+
third:
|
25
|
+
id: 3
|
26
|
+
title: The Third Topic of the day
|
27
|
+
author_name: Carl
|
28
|
+
written_on: 2012-08-12t20:24:22.129346+00:00
|
29
|
+
content: I'm a troll
|
30
|
+
approved: true
|
31
|
+
replies_count: 1
|
32
|
+
|
33
|
+
fourth:
|
34
|
+
id: 4
|
35
|
+
title: The Fourth Topic of the day
|
36
|
+
author_name: Carl
|
37
|
+
written_on: 2006-07-15t15:28:00.0099+01:00
|
38
|
+
content: Why not?
|
39
|
+
approved: true
|
40
|
+
type: Reply
|
41
|
+
parent_id: 3
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
require 'rails/all'
|
6
|
+
require 'rails/generators'
|
7
|
+
require 'rails/generators/test_case'
|
8
|
+
|
9
|
+
module TestApp
|
10
|
+
class Application < Rails::Application
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Call configure to load the settings from
|
15
|
+
# Rails.application.config.generators to Rails::Generators
|
16
|
+
Rails.application.load_generators
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'generators/generators_test_helper'
|
2
|
+
require 'generators/rails/observer/observer_generator'
|
3
|
+
|
4
|
+
class NamespacedObserverGeneratorTest < Rails::Generators::TestCase
|
5
|
+
tests Rails::Generators::ObserverGenerator
|
6
|
+
arguments %w(account)
|
7
|
+
destination File.expand_path("../../tmp", __FILE__)
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
prepare_destination
|
12
|
+
Rails::Generators.namespace = TestApp
|
13
|
+
end
|
14
|
+
|
15
|
+
def teardown
|
16
|
+
super
|
17
|
+
Rails::Generators.namespace = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_invokes_default_orm
|
21
|
+
run_generator
|
22
|
+
assert_file "app/models/test_app/account_observer.rb", /module TestApp/, / class AccountObserver < ActiveRecord::Observer/
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_invokes_default_orm_with_class_path
|
26
|
+
run_generator ["admin/account"]
|
27
|
+
assert_file "app/models/test_app/admin/account_observer.rb", /module TestApp/, / class Admin::AccountObserver < ActiveRecord::Observer/
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_invokes_default_test_framework
|
31
|
+
run_generator
|
32
|
+
assert_file "test/unit/test_app/account_observer_test.rb", /module TestApp/, / class AccountObserverTest < ActiveSupport::TestCase/
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'generators/generators_test_helper'
|
2
|
+
require 'generators/rails/observer/observer_generator'
|
3
|
+
|
4
|
+
class ObserverGeneratorTest < Rails::Generators::TestCase
|
5
|
+
tests Rails::Generators::ObserverGenerator
|
6
|
+
destination File.expand_path("../../tmp", __FILE__)
|
7
|
+
arguments %w(account)
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
prepare_destination
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_invokes_default_orm
|
15
|
+
run_generator
|
16
|
+
assert_file "app/models/account_observer.rb", /class AccountObserver < ActiveRecord::Observer/
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_invokes_default_orm_with_class_path
|
20
|
+
run_generator ["admin/account"]
|
21
|
+
assert_file "app/models/admin/account_observer.rb", /class Admin::AccountObserver < ActiveRecord::Observer/
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_invokes_default_test_framework
|
25
|
+
run_generator
|
26
|
+
assert_file "test/unit/account_observer_test.rb", /class AccountObserverTest < ActiveSupport::TestCase/
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_logs_if_the_test_framework_cannot_be_found
|
30
|
+
content = run_generator ["account", "--test-framework=rspec"]
|
31
|
+
assert_match(/rspec \[not found\]/, content)
|
32
|
+
end
|
33
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
require 'rails/observers/activerecord/active_record'
|
5
|
+
|
6
|
+
FIXTURES_ROOT = File.expand_path(File.dirname(__FILE__)) + "/fixtures"
|
7
|
+
|
8
|
+
class ActiveSupport::TestCase
|
9
|
+
include ActiveRecord::TestFixtures
|
10
|
+
|
11
|
+
self.fixture_path = FIXTURES_ROOT
|
12
|
+
self.use_instantiated_fixtures = false
|
13
|
+
self.use_transactional_fixtures = true
|
14
|
+
end
|
15
|
+
|
16
|
+
ActiveRecord::Base.configurations = { "test" => { adapter: 'sqlite3', database: ':memory:' } }
|
17
|
+
ActiveRecord::Base.establish_connection(:test)
|
18
|
+
|
19
|
+
ActiveRecord::Schema.verbose = false
|
20
|
+
ActiveRecord::Schema.define do
|
21
|
+
create_table :topics do |t|
|
22
|
+
t.string :title
|
23
|
+
t.string :author_name
|
24
|
+
t.string :author_email_address
|
25
|
+
t.datetime :written_on
|
26
|
+
t.time :bonus_time
|
27
|
+
t.date :last_read
|
28
|
+
t.text :content
|
29
|
+
t.text :important
|
30
|
+
t.boolean :approved, :default => true
|
31
|
+
t.integer :replies_count, :default => 0
|
32
|
+
t.integer :parent_id
|
33
|
+
t.string :parent_title
|
34
|
+
t.string :type
|
35
|
+
t.string :group
|
36
|
+
t.timestamps
|
37
|
+
end
|
38
|
+
|
39
|
+
create_table :comments do |t|
|
40
|
+
t.string :title
|
41
|
+
end
|
42
|
+
|
43
|
+
create_table :minimalistics do |t|
|
44
|
+
end
|
45
|
+
|
46
|
+
create_table :developers do |t|
|
47
|
+
t.string :name
|
48
|
+
t.integer :salary
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Topic < ActiveRecord::Base
|
53
|
+
has_many :replies, dependent: :destroy, foreign_key: "parent_id"
|
54
|
+
end
|
55
|
+
|
56
|
+
class Reply < Topic
|
57
|
+
belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true
|
58
|
+
end
|
59
|
+
|
60
|
+
class Comment < ActiveRecord::Base
|
61
|
+
def self.lol
|
62
|
+
"lol"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Developer < ActiveRecord::Base
|
67
|
+
end
|
68
|
+
|
69
|
+
class Minimalistic < ActiveRecord::Base
|
70
|
+
end
|
71
|
+
|
72
|
+
ActiveSupport::Deprecation.silence do
|
73
|
+
require 'active_record/test_case'
|
74
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Note:
|
2
|
+
# It is important to keep this file as light as possible
|
3
|
+
# the goal for tests that require this is to test booting up
|
4
|
+
# rails from an empty state, so anything added here could
|
5
|
+
# hide potential failures
|
6
|
+
#
|
7
|
+
# It is also good to know what is the bare minimum to get
|
8
|
+
# Rails booted up.
|
9
|
+
require 'fileutils'
|
10
|
+
|
11
|
+
require 'bundler/setup'
|
12
|
+
require 'minitest/autorun'
|
13
|
+
require 'active_support/test_case'
|
14
|
+
|
15
|
+
# These files do not require any others and are needed
|
16
|
+
# to run the tests
|
17
|
+
require "active_support/testing/isolation"
|
18
|
+
require "active_support/core_ext/kernel/reporting"
|
19
|
+
require 'tmpdir'
|
20
|
+
|
21
|
+
module TestHelpers
|
22
|
+
module Paths
|
23
|
+
def app_template_path
|
24
|
+
File.join Dir.tmpdir, 'app_template'
|
25
|
+
end
|
26
|
+
|
27
|
+
def tmp_path(*args)
|
28
|
+
@tmp_path ||= File.realpath(Dir.mktmpdir)
|
29
|
+
File.join(@tmp_path, *args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def app_path(*args)
|
33
|
+
tmp_path(*%w[app] + args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def rails_root
|
37
|
+
app_path
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module Generation
|
42
|
+
# Build an application by invoking the generator and going through the whole stack.
|
43
|
+
def build_app(options = {})
|
44
|
+
@prev_rails_env = ENV['RAILS_ENV']
|
45
|
+
ENV['RAILS_ENV'] = 'development'
|
46
|
+
|
47
|
+
FileUtils.rm_rf(app_path)
|
48
|
+
FileUtils.cp_r(app_template_path, app_path)
|
49
|
+
|
50
|
+
# Delete the initializers unless requested
|
51
|
+
unless options[:initializers]
|
52
|
+
Dir["#{app_path}/config/initializers/*.rb"].each do |initializer|
|
53
|
+
File.delete(initializer)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
unless options[:gemfile]
|
58
|
+
File.delete "#{app_path}/Gemfile"
|
59
|
+
end
|
60
|
+
|
61
|
+
routes = File.read("#{app_path}/config/routes.rb")
|
62
|
+
if routes =~ /(\n\s*end\s*)\Z/
|
63
|
+
File.open("#{app_path}/config/routes.rb", 'w') do |f|
|
64
|
+
f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)', :via => :all\n" + $1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
add_to_config <<-RUBY
|
69
|
+
config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
|
70
|
+
config.session_store :cookie_store, :key => "_myapp_session"
|
71
|
+
config.active_support.deprecation = :log
|
72
|
+
config.action_controller.allow_forgery_protection = false
|
73
|
+
config.eager_load = false
|
74
|
+
RUBY
|
75
|
+
end
|
76
|
+
|
77
|
+
def teardown_app
|
78
|
+
ENV['RAILS_ENV'] = @prev_rails_env if @prev_rails_env
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_to_config(str)
|
82
|
+
environment = File.read("#{app_path}/config/application.rb")
|
83
|
+
if environment =~ /(\n\s*end\s*end\s*)\Z/
|
84
|
+
File.open("#{app_path}/config/application.rb", 'w') do |f|
|
85
|
+
f.puts $` + "\n#{str}\n" + $1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def app_file(path, contents)
|
91
|
+
FileUtils.mkdir_p File.dirname("#{app_path}/#{path}")
|
92
|
+
File.open("#{app_path}/#{path}", 'w') do |f|
|
93
|
+
f.puts contents
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def boot_rails
|
98
|
+
require 'rubygems' unless defined? Gem
|
99
|
+
require 'bundler'
|
100
|
+
Bundler.setup
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class ActiveSupport::TestCase
|
106
|
+
include TestHelpers::Paths
|
107
|
+
include TestHelpers::Generation
|
108
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class SpecialDeveloper < Developer; end
|
4
|
+
|
5
|
+
class DeveloperObserver < ActiveRecord::Observer
|
6
|
+
def calls
|
7
|
+
@calls ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
def before_save(developer)
|
11
|
+
calls << developer
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class SalaryChecker < ActiveRecord::Observer
|
16
|
+
observe :special_developer
|
17
|
+
attr_accessor :last_saved
|
18
|
+
|
19
|
+
def before_save(developer)
|
20
|
+
return developer.salary > 80000
|
21
|
+
end
|
22
|
+
|
23
|
+
module Implementation
|
24
|
+
def after_save(developer)
|
25
|
+
self.last_saved = developer
|
26
|
+
end
|
27
|
+
end
|
28
|
+
include Implementation
|
29
|
+
end
|
30
|
+
|
31
|
+
class TopicaAuditor < ActiveRecord::Observer
|
32
|
+
observe :topic
|
33
|
+
|
34
|
+
attr_reader :topic
|
35
|
+
|
36
|
+
def after_find(topic)
|
37
|
+
@topic = topic
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class TopicObserver < ActiveRecord::Observer
|
42
|
+
attr_reader :topic
|
43
|
+
|
44
|
+
def after_find(topic)
|
45
|
+
@topic = topic
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create an after_save callback, so a notify_observer hook is created
|
49
|
+
# on :topic.
|
50
|
+
def after_save(nothing)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class MinimalisticObserver < ActiveRecord::Observer
|
55
|
+
attr_reader :minimalistic
|
56
|
+
|
57
|
+
def after_find(minimalistic)
|
58
|
+
@minimalistic = minimalistic
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class MultiObserver < ActiveRecord::Observer
|
63
|
+
attr_reader :record
|
64
|
+
|
65
|
+
def self.observed_class() [ Topic, Developer ] end
|
66
|
+
|
67
|
+
cattr_reader :last_inherited
|
68
|
+
@@last_inherited = nil
|
69
|
+
|
70
|
+
def observed_class_inherited_with_testing(subclass)
|
71
|
+
observed_class_inherited_without_testing(subclass)
|
72
|
+
@@last_inherited = subclass
|
73
|
+
end
|
74
|
+
|
75
|
+
alias_method_chain :observed_class_inherited, :testing
|
76
|
+
|
77
|
+
def after_find(record)
|
78
|
+
@record = record
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class ValidatedComment < Comment
|
83
|
+
attr_accessor :callers
|
84
|
+
|
85
|
+
before_validation :record_callers
|
86
|
+
|
87
|
+
after_validation do
|
88
|
+
record_callers
|
89
|
+
end
|
90
|
+
|
91
|
+
def record_callers
|
92
|
+
callers << self.class if callers
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class ValidatedCommentObserver < ActiveRecord::Observer
|
97
|
+
attr_accessor :callers
|
98
|
+
|
99
|
+
def after_validation(model)
|
100
|
+
callers << self.class if callers
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
class AroundTopic < Topic
|
106
|
+
end
|
107
|
+
|
108
|
+
class AroundTopicObserver < ActiveRecord::Observer
|
109
|
+
observe :around_topic
|
110
|
+
def topic_ids
|
111
|
+
@topic_ids ||= []
|
112
|
+
end
|
113
|
+
|
114
|
+
def around_save(topic)
|
115
|
+
topic_ids << topic.id
|
116
|
+
yield(topic)
|
117
|
+
topic_ids << topic.id
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class LifecycleTest < ActiveRecord::TestCase
|
122
|
+
fixtures :topics, :developers, :minimalistics
|
123
|
+
|
124
|
+
def test_before_destroy
|
125
|
+
topic = Topic.find(1)
|
126
|
+
assert_difference 'Topic.count', -(1 + topic.replies.size) do
|
127
|
+
topic.destroy
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_auto_observer
|
132
|
+
topic_observer = TopicaAuditor.instance
|
133
|
+
assert_nil TopicaAuditor.observed_class
|
134
|
+
assert_equal [Topic], TopicaAuditor.observed_classes.to_a
|
135
|
+
|
136
|
+
topic = Topic.find(1)
|
137
|
+
assert_equal topic.title, topic_observer.topic.title
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_inferred_auto_observer
|
141
|
+
topic_observer = TopicObserver.instance
|
142
|
+
assert_equal Topic, TopicObserver.observed_class
|
143
|
+
|
144
|
+
topic = Topic.find(1)
|
145
|
+
assert_equal topic.title, topic_observer.topic.title
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_observing_two_classes
|
149
|
+
multi_observer = MultiObserver.instance
|
150
|
+
|
151
|
+
topic = Topic.find(1)
|
152
|
+
assert_equal topic.title, multi_observer.record.title
|
153
|
+
|
154
|
+
developer = Developer.find(1)
|
155
|
+
assert_equal developer.name, multi_observer.record.name
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_observing_subclasses
|
159
|
+
multi_observer = MultiObserver.instance
|
160
|
+
|
161
|
+
developer = SpecialDeveloper.find(1)
|
162
|
+
assert_equal developer.name, multi_observer.record.name
|
163
|
+
|
164
|
+
klass = Class.new(Developer)
|
165
|
+
assert_equal klass, multi_observer.last_inherited
|
166
|
+
|
167
|
+
developer = klass.find(1)
|
168
|
+
assert_equal developer.name, multi_observer.record.name
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_after_find_can_be_observed_when_its_not_defined_on_the_model
|
172
|
+
observer = MinimalisticObserver.instance
|
173
|
+
assert_equal Minimalistic, MinimalisticObserver.observed_class
|
174
|
+
|
175
|
+
minimalistic = Minimalistic.find(1)
|
176
|
+
assert_equal minimalistic, observer.minimalistic
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_after_find_can_be_observed_when_its_defined_on_the_model
|
180
|
+
observer = TopicObserver.instance
|
181
|
+
assert_equal Topic, TopicObserver.observed_class
|
182
|
+
|
183
|
+
topic = Topic.find(1)
|
184
|
+
assert_equal topic, observer.topic
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_invalid_observer
|
188
|
+
assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers }
|
189
|
+
end
|
190
|
+
|
191
|
+
test "model callbacks fire before observers are notified" do
|
192
|
+
callers = []
|
193
|
+
|
194
|
+
comment = ValidatedComment.new
|
195
|
+
comment.callers = ValidatedCommentObserver.instance.callers = callers
|
196
|
+
|
197
|
+
comment.valid?
|
198
|
+
assert_equal [ValidatedComment, ValidatedComment, ValidatedCommentObserver], callers,
|
199
|
+
"model callbacks did not fire before observers were notified"
|
200
|
+
end
|
201
|
+
|
202
|
+
test "able to save developer" do
|
203
|
+
SalaryChecker.instance # activate
|
204
|
+
developer = SpecialDeveloper.new :name => 'Roger', :salary => 100000
|
205
|
+
assert developer.save, "developer with normal salary failed to save"
|
206
|
+
end
|
207
|
+
|
208
|
+
test "unable to save developer with low salary" do
|
209
|
+
SalaryChecker.instance # activate
|
210
|
+
developer = SpecialDeveloper.new :name => 'Rookie', :salary => 50000
|
211
|
+
assert !developer.save, "allowed to save a developer with too low salary"
|
212
|
+
end
|
213
|
+
|
214
|
+
test "able to call methods defined with included module" do # https://rails.lighthouseapp.com/projects/8994/tickets/6065-activerecordobserver-is-not-aware-of-method-added-by-including-modules
|
215
|
+
SalaryChecker.instance # activate
|
216
|
+
developer = SpecialDeveloper.create! :name => 'Roger', :salary => 100000
|
217
|
+
assert_equal developer, SalaryChecker.instance.last_saved
|
218
|
+
end
|
219
|
+
|
220
|
+
test "around filter from observer should accept block" do
|
221
|
+
observer = AroundTopicObserver.instance
|
222
|
+
topic = AroundTopic.new
|
223
|
+
topic.save
|
224
|
+
assert_nil observer.topic_ids.first
|
225
|
+
assert_not_nil observer.topic_ids.last
|
226
|
+
end
|
227
|
+
|
228
|
+
test "able to disable observers" do
|
229
|
+
observer = DeveloperObserver.instance # activate
|
230
|
+
observer.calls.clear
|
231
|
+
|
232
|
+
ActiveRecord::Base.observers.disable DeveloperObserver do
|
233
|
+
Developer.create! :name => 'Ancestor', :salary => 100000
|
234
|
+
SpecialDeveloper.create! :name => 'Descendent', :salary => 100000
|
235
|
+
end
|
236
|
+
|
237
|
+
assert_equal [], observer.calls
|
238
|
+
end
|
239
|
+
|
240
|
+
def test_observer_is_called_once
|
241
|
+
observer = DeveloperObserver.instance # activate
|
242
|
+
observer.calls.clear
|
243
|
+
|
244
|
+
developer = Developer.create! :name => 'Ancestor', :salary => 100000
|
245
|
+
special_developer = SpecialDeveloper.create! :name => 'Descendent', :salary => 100000
|
246
|
+
|
247
|
+
assert_equal [developer, special_developer], observer.calls
|
248
|
+
end
|
249
|
+
end
|