seamless_database_pool 1.0.0
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/MIT-LICENSE +20 -0
- data/README +79 -0
- data/Rakefile +43 -0
- data/init.rb +2 -0
- data/lib/active_record/connection_adapters/seamless_database_pool_adapter.rb +302 -0
- data/lib/seamless_database_pool.rb +108 -0
- data/lib/seamless_database_pool/connect_timeout.rb +22 -0
- data/lib/seamless_database_pool/connection_statistics.rb +63 -0
- data/lib/seamless_database_pool/controller_filter.rb +91 -0
- data/spec/connection_statistics_spec.rb +77 -0
- data/spec/filter_spec.rb +134 -0
- data/spec/seamless_database_pool_adapter_spec.rb +432 -0
- data/spec/seamless_database_pool_spec.rb +134 -0
- metadata +72 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
# This module allows setting the read pool connection type. Generally you will use one of
|
2
|
+
#
|
3
|
+
# - use_random_read_connection
|
4
|
+
# - use_persistent_read_connection
|
5
|
+
# - use_master_connection
|
6
|
+
#
|
7
|
+
# Each of these methods can take an optional block. If they are called with a block, they
|
8
|
+
# will set the read connection type only within the block. Otherwise they will set the default
|
9
|
+
# read connection type. If none is ever called, the read connection type will be :master.
|
10
|
+
|
11
|
+
module SeamlessDatabasePool
|
12
|
+
|
13
|
+
READ_CONNECTION_METHODS = [:master, :persistent, :random]
|
14
|
+
|
15
|
+
# Call this method to use a random connection from the read pool for every select statement.
|
16
|
+
# This method is good if your replication is very fast. Otherwise there is a chance you could
|
17
|
+
# get inconsistent results from one request to the next. This can result in mysterious failures
|
18
|
+
# if your code selects a value in one statement and then uses in another statement. You can wind
|
19
|
+
# up trying to use a value from one server that hasn't been replicated to another one yet.
|
20
|
+
# This method is best if you have few processes which generate a lot of queries and you have
|
21
|
+
# fast replication.
|
22
|
+
def self.use_random_read_connection
|
23
|
+
if block_given?
|
24
|
+
set_read_only_connection_type(:random){yield}
|
25
|
+
else
|
26
|
+
Thread.current[:read_only_connection] = :random
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Call this method to pick a random connection from the read pool and use it for all subsequent
|
31
|
+
# select statements. This provides consistency from one select statement to the next. This
|
32
|
+
# method should always be called with a block otherwise you can end up with an imbalanced read
|
33
|
+
# pool. This method is best if you have lots of processes which have a relatively few select
|
34
|
+
# statements or a slow replication mechanism. Generally this is the best method to use for web
|
35
|
+
# applications.
|
36
|
+
def self.use_persistent_read_connection
|
37
|
+
if block_given?
|
38
|
+
set_read_only_connection_type(:persistent){yield}
|
39
|
+
else
|
40
|
+
Thread.current[:read_only_connection] = {}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Call this method to use the master connection for all subsequent select statements. This
|
45
|
+
# method is most useful when you are doing lots of updates since it guarantees consistency
|
46
|
+
# if you do a select immediately after an update or insert.
|
47
|
+
#
|
48
|
+
# The master connection will also be used for selects inside any transaction blocks. It will
|
49
|
+
# also be used if you pass :readonly => false to any ActiveRecord.find method.
|
50
|
+
def self.use_master_connection
|
51
|
+
if block_given?
|
52
|
+
set_read_only_connection_type(:master){yield}
|
53
|
+
else
|
54
|
+
Thread.current[:read_only_connection] = :master
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Set the read only connection type to either :master, :random, or :persistent.
|
59
|
+
def self.set_read_only_connection_type (connection_type)
|
60
|
+
saved_connection = Thread.current[:read_only_connection]
|
61
|
+
retval = nil
|
62
|
+
begin
|
63
|
+
connection_type = {} if connection_type == :persistent
|
64
|
+
Thread.current[:read_only_connection] = connection_type
|
65
|
+
retval = yield if block_given?
|
66
|
+
ensure
|
67
|
+
Thread.current[:read_only_connection] = saved_connection
|
68
|
+
end
|
69
|
+
return retval
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get the read only connection type currently in use. Will be one of :master, :random, or :persistent.
|
73
|
+
def self.read_only_connection_type (default = :master)
|
74
|
+
connection_type = Thread.current[:read_only_connection] || default
|
75
|
+
connection_type = :persistent if connection_type.kind_of?(Hash)
|
76
|
+
return connection_type
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get a read only connection from a connection pool.
|
80
|
+
def self.read_only_connection (pool_connection)
|
81
|
+
return pool_connection.master_connection if pool_connection.using_master_connection?
|
82
|
+
connection_type = Thread.current[:read_only_connection]
|
83
|
+
|
84
|
+
if connection_type.kind_of?(Hash)
|
85
|
+
connection = connection_type[pool_connection]
|
86
|
+
unless connection
|
87
|
+
connection = pool_connection.random_read_connection
|
88
|
+
connection_type[pool_connection] = connection
|
89
|
+
end
|
90
|
+
return connection
|
91
|
+
elsif connection_type == :random
|
92
|
+
return pool_connection.random_read_connection
|
93
|
+
else
|
94
|
+
return pool_connection.master_connection
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# This method is provided as a way to change the persistent connection when it fails and a new one is substituted.
|
99
|
+
def self.set_persistent_read_connection (pool_connection, read_connection)
|
100
|
+
connection_type = Thread.current[:read_only_connection]
|
101
|
+
connection_type[pool_connection] = read_connection if connection_type.kind_of?(Hash)
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.clear_read_only_connection
|
105
|
+
Thread.current[:read_only_connection] = nil
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SeamlessDatabasePool
|
2
|
+
# This module is mixed into connection adapters to allow the reconnect! method to timeout if the
|
3
|
+
# IP address becomes unreachable. The default timeout is 1 second, but you can change it by setting
|
4
|
+
# the connect_timeout parameter in the adapter configuration.
|
5
|
+
module ConnectTimeout
|
6
|
+
attr_accessor :connect_timeout
|
7
|
+
|
8
|
+
def self.included (base)
|
9
|
+
base.alias_method_chain :reconnect!, :connect_timeout
|
10
|
+
end
|
11
|
+
|
12
|
+
def reconnect_with_connect_timeout!
|
13
|
+
begin
|
14
|
+
timeout(connect_timeout || 1) do
|
15
|
+
reconnect_without_connect_timeout!
|
16
|
+
end
|
17
|
+
rescue TimeoutError
|
18
|
+
raise "reconnect timed out"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module SeamlessDatabasePool
|
2
|
+
# This module is included for testing. Mix it into each of your database pool connections
|
3
|
+
# and it will keep track of how often each connection calls update, insert, execute,
|
4
|
+
# or select.
|
5
|
+
module ConnectionStatistics
|
6
|
+
def self.included (base)
|
7
|
+
base.alias_method_chain(:update, :connection_statistics)
|
8
|
+
base.alias_method_chain(:insert, :connection_statistics)
|
9
|
+
base.alias_method_chain(:execute, :connection_statistics)
|
10
|
+
base.alias_method_chain(:select, :connection_statistics)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Get the connection statistics
|
14
|
+
def connection_statistics
|
15
|
+
@connection_statistics ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def reset_connection_statistics
|
19
|
+
@connection_statistics = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def update_with_connection_statistics (sql, name = nil)
|
23
|
+
increment_connection_statistic(:update) do
|
24
|
+
update_without_connection_statistics(sql, name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def insert_with_connection_statistics (sql, name = nil)
|
29
|
+
increment_connection_statistic(:insert) do
|
30
|
+
insert_without_connection_statistics(sql, name)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def execute_with_connection_statistics (sql, name = nil)
|
35
|
+
increment_connection_statistic(:execute) do
|
36
|
+
execute_without_connection_statistics(sql, name)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def select_with_connection_statistics (sql, name = nil)
|
43
|
+
increment_connection_statistic(:select) do
|
44
|
+
select_without_connection_statistics(sql, name)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def increment_connection_statistic (method)
|
49
|
+
if @counting_pool_statistics
|
50
|
+
yield
|
51
|
+
else
|
52
|
+
begin
|
53
|
+
@counting_pool_statistics = true
|
54
|
+
stat = connection_statistics[method] || 0
|
55
|
+
@connection_statistics[method] = stat + 1
|
56
|
+
yield
|
57
|
+
ensure
|
58
|
+
@counting_pool_statistics = false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module SeamlessDatabasePool
|
2
|
+
# This module provides a simple method of declaring which read pool connection type should
|
3
|
+
# be used for various ActionController actions. To use it, you must first mix it into
|
4
|
+
# you controller and then call use_database_pool to configure the connection types. Generally
|
5
|
+
# you should just do this in ApplicationController and call use_database_pool in your controllers
|
6
|
+
# when you need different connection types.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# ApplicationController < ActionController::Base
|
11
|
+
# include SeamlessDatabasePool
|
12
|
+
# use_database_pool :all => :persistent, [:save, :delete] => :master
|
13
|
+
# ...
|
14
|
+
|
15
|
+
module ControllerFilter
|
16
|
+
def self.included (base)
|
17
|
+
unless base.respond_to?(:use_database_pool)
|
18
|
+
base.extend(ClassMethods)
|
19
|
+
base.class_eval do
|
20
|
+
alias_method_chain :perform_action, :seamless_database_pool
|
21
|
+
alias_method_chain :redirect_to, :seamless_database_pool
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
|
28
|
+
def seamless_database_pool_options
|
29
|
+
return @seamless_database_pool_options if @seamless_database_pool_options
|
30
|
+
@seamless_database_pool_options = superclass.seamless_database_pool_options.dup if superclass.respond_to?(:seamless_database_pool_options)
|
31
|
+
@seamless_database_pool_options ||= {}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Call this method to set up the connection types that will be used for your actions.
|
35
|
+
# The configuration is given as a hash where the key is the action name and the value is
|
36
|
+
# the connection type (:master, :persistent, or :random). You can specify :all as the action
|
37
|
+
# to define a default connection type. You can also specify the action names in an array
|
38
|
+
# to easily map multiple actions to one connection type.
|
39
|
+
#
|
40
|
+
# The configuration is inherited from parent controller classes, so if you have default
|
41
|
+
# behavior, you should simply specify it in ApplicationController to have it available
|
42
|
+
# globally.
|
43
|
+
def use_database_pool (options)
|
44
|
+
remapped_options = seamless_database_pool_options
|
45
|
+
options.each_pair do |actions, connection_method|
|
46
|
+
unless SeamlessDatabasePool::READ_CONNECTION_METHODS.include?(connection_method)
|
47
|
+
raise "Invalid read pool method: #{connection_method}; should be one of #{SeamlessDatabasePool::READ_CONNECTION_METHODS.inspect}"
|
48
|
+
end
|
49
|
+
actions = [actions] unless actions.kind_of?(Array)
|
50
|
+
actions.each do |action|
|
51
|
+
remapped_options[action.to_sym] = connection_method
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@seamless_database_pool_options = remapped_options
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Force the master connection to be used on the next request. This is very useful for the Post-Redirect pattern
|
59
|
+
# where you post a request to your save action and then redirect the user back to the edit action. By calling
|
60
|
+
# this method, you won't have to worry if the replication engine is slower than the redirect. Normally you
|
61
|
+
# won't need to call this method yourself as it is automatically called when you perform a redirect from within
|
62
|
+
# a master connection block. It is made available just in case you have special needs that don't quite fit
|
63
|
+
# into this module's default logic.
|
64
|
+
def use_master_db_connection_on_next_request
|
65
|
+
session[:next_request_db_connection] = :master if session
|
66
|
+
end
|
67
|
+
|
68
|
+
def seamless_database_pool_options
|
69
|
+
self.class.seamless_database_pool_options
|
70
|
+
end
|
71
|
+
|
72
|
+
def perform_action_with_seamless_database_pool
|
73
|
+
read_pool_method = session.delete(:next_request_db_connection) if session
|
74
|
+
read_pool_method ||= seamless_database_pool_options[action_name.to_sym] || seamless_database_pool_options[:all]
|
75
|
+
if read_pool_method
|
76
|
+
SeamlessDatabasePool.set_read_only_connection_type(read_pool_method) do
|
77
|
+
perform_action_without_seamless_database_pool
|
78
|
+
end
|
79
|
+
else
|
80
|
+
perform_action_without_seamless_database_pool
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def redirect_to_with_seamless_database_pool (options = {}, response_status = {})
|
85
|
+
if SeamlessDatabasePool.read_only_connection_type(nil) == :master
|
86
|
+
use_master_db_connection_on_next_request
|
87
|
+
end
|
88
|
+
redirect_to_without_seamless_database_pool(options, response_status)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe "SeamlessDatabasePool::ConnectionStatistics" do
|
4
|
+
|
5
|
+
module SeamlessDatabasePool
|
6
|
+
class ConnectionStatisticsTester
|
7
|
+
def insert (sql, name = nil)
|
8
|
+
"INSERT #{sql}/#{name}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def update (sql, name = nil)
|
12
|
+
execute(sql)
|
13
|
+
"UPDATE #{sql}/#{name}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute (sql, name = nil)
|
17
|
+
"EXECUTE #{sql}/#{name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def select (sql, name = nil)
|
23
|
+
"SELECT #{sql}/#{name}"
|
24
|
+
end
|
25
|
+
|
26
|
+
include ConnectionStatistics
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should increment statistics on update" do
|
31
|
+
connection = SeamlessDatabasePool::ConnectionStatisticsTester.new
|
32
|
+
connection.update('SQL', 'name').should == "UPDATE SQL/name"
|
33
|
+
connection.connection_statistics.should == {:update => 1}
|
34
|
+
connection.update('SQL 2').should == "UPDATE SQL 2/"
|
35
|
+
connection.connection_statistics.should == {:update => 2}
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should increment statistics on insert" do
|
39
|
+
connection = SeamlessDatabasePool::ConnectionStatisticsTester.new
|
40
|
+
connection.insert('SQL', 'name').should == "INSERT SQL/name"
|
41
|
+
connection.connection_statistics.should == {:insert => 1}
|
42
|
+
connection.insert('SQL 2').should == "INSERT SQL 2/"
|
43
|
+
connection.connection_statistics.should == {:insert => 2}
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should increment statistics on execute" do
|
47
|
+
connection = SeamlessDatabasePool::ConnectionStatisticsTester.new
|
48
|
+
connection.execute('SQL', 'name').should == "EXECUTE SQL/name"
|
49
|
+
connection.connection_statistics.should == {:execute => 1}
|
50
|
+
connection.execute('SQL 2').should == "EXECUTE SQL 2/"
|
51
|
+
connection.connection_statistics.should == {:execute => 2}
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should increment statistics on select" do
|
55
|
+
connection = SeamlessDatabasePool::ConnectionStatisticsTester.new
|
56
|
+
connection.send(:select, 'SQL', 'name').should == "SELECT SQL/name"
|
57
|
+
connection.connection_statistics.should == {:select => 1}
|
58
|
+
connection.send(:select, 'SQL 2').should == "SELECT SQL 2/"
|
59
|
+
connection.connection_statistics.should == {:select => 2}
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should increment counts only once within a block" do
|
63
|
+
connection = SeamlessDatabasePool::ConnectionStatisticsTester.new
|
64
|
+
connection.should_receive(:execute).with('SQL')
|
65
|
+
connection.update('SQL')
|
66
|
+
connection.connection_statistics.should == {:update => 1}
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should be able to clear the statistics" do
|
70
|
+
connection = SeamlessDatabasePool::ConnectionStatisticsTester.new
|
71
|
+
connection.update('SQL')
|
72
|
+
connection.connection_statistics.should == {:update => 1}
|
73
|
+
connection.reset_connection_statistics
|
74
|
+
connection.connection_statistics.should == {}
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
data/spec/filter_spec.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe "SeamlessDatabasePool::ControllerFilter" do
|
4
|
+
|
5
|
+
module SeamlessDatabasePool
|
6
|
+
class TestApplicationController
|
7
|
+
attr_reader :action_name, :session
|
8
|
+
|
9
|
+
def initialize(action, session = {})
|
10
|
+
@action_name = action
|
11
|
+
@session = session
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform_action
|
15
|
+
send action_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def redirect_to (options = {}, response_status = {})
|
19
|
+
options
|
20
|
+
end
|
21
|
+
|
22
|
+
def base_action
|
23
|
+
SeamlessDatabasePool.read_only_connection_type
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class TestBaseController < TestApplicationController
|
28
|
+
include SeamlessDatabasePool::ControllerFilter
|
29
|
+
|
30
|
+
use_database_pool :read => :persistent
|
31
|
+
|
32
|
+
def read
|
33
|
+
SeamlessDatabasePool.read_only_connection_type
|
34
|
+
end
|
35
|
+
|
36
|
+
def other
|
37
|
+
SeamlessDatabasePool.read_only_connection_type
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class TestOtherController < TestBaseController
|
42
|
+
use_database_pool :all => :random, [:edit, :save, :redirect_master_action] => :master
|
43
|
+
|
44
|
+
def edit
|
45
|
+
SeamlessDatabasePool.read_only_connection_type
|
46
|
+
end
|
47
|
+
|
48
|
+
def save
|
49
|
+
SeamlessDatabasePool.read_only_connection_type
|
50
|
+
end
|
51
|
+
|
52
|
+
def redirect_master_action
|
53
|
+
redirect_to(:action => :read)
|
54
|
+
end
|
55
|
+
|
56
|
+
def redirect_read_action
|
57
|
+
redirect_to(:action => :read)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should work with nothing set" do
|
63
|
+
controller = SeamlessDatabasePool::TestApplicationController.new('base_action')
|
64
|
+
controller.perform_action.should == :master
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should allow setting a connection type for a single action" do
|
68
|
+
controller = SeamlessDatabasePool::TestBaseController.new('read')
|
69
|
+
controller.perform_action.should == :persistent
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should allow setting a connection type for actions" do
|
73
|
+
controller = SeamlessDatabasePool::TestOtherController.new('edit')
|
74
|
+
controller.perform_action.should == :master
|
75
|
+
controller = SeamlessDatabasePool::TestOtherController.new('save')
|
76
|
+
controller.perform_action.should == :master
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should allow setting a connection type for all actions" do
|
80
|
+
controller = SeamlessDatabasePool::TestOtherController.new('other')
|
81
|
+
controller.perform_action.should == :random
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should inherit the superclass' options" do
|
85
|
+
controller = SeamlessDatabasePool::TestOtherController.new('read')
|
86
|
+
controller.perform_action.should == :persistent
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should be able to force using the master connection on the next request" do
|
90
|
+
session = {}
|
91
|
+
|
92
|
+
# First request
|
93
|
+
controller = SeamlessDatabasePool::TestOtherController.new('read', session)
|
94
|
+
controller.perform_action.should == :persistent
|
95
|
+
controller.use_master_db_connection_on_next_request
|
96
|
+
|
97
|
+
# Second request
|
98
|
+
controller = SeamlessDatabasePool::TestOtherController.new('read', session)
|
99
|
+
controller.perform_action.should == :master
|
100
|
+
|
101
|
+
# Third request
|
102
|
+
controller = SeamlessDatabasePool::TestOtherController.new('read', session)
|
103
|
+
controller.perform_action.should == :persistent
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should not break trying to force the master connection if sessions are not enabled" do
|
107
|
+
controller = SeamlessDatabasePool::TestOtherController.new('read', nil)
|
108
|
+
controller.perform_action.should == :persistent
|
109
|
+
controller.use_master_db_connection_on_next_request
|
110
|
+
|
111
|
+
# Second request
|
112
|
+
controller = SeamlessDatabasePool::TestOtherController.new('read', nil)
|
113
|
+
controller.perform_action.should == :persistent
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should force the master connection on the next request for a redirect in master connection block" do
|
117
|
+
session = {}
|
118
|
+
controller = SeamlessDatabasePool::TestOtherController.new('redirect_master_action', session)
|
119
|
+
controller.perform_action.should == {:action => :read}
|
120
|
+
|
121
|
+
controller = SeamlessDatabasePool::TestOtherController.new('read', session)
|
122
|
+
controller.perform_action.should == :master
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should not force the master connection on the next request for a redirect not in master connection block" do
|
126
|
+
session = {}
|
127
|
+
controller = SeamlessDatabasePool::TestOtherController.new('redirect_read_action', session)
|
128
|
+
controller.perform_action.should == {:action => :read}
|
129
|
+
|
130
|
+
controller = SeamlessDatabasePool::TestOtherController.new('read', session)
|
131
|
+
controller.perform_action.should == :persistent
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|