seamless_database_pool 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|