replica 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0
1
+ 1.2.0
@@ -1,145 +1,7 @@
1
1
  require 'activerecord'
2
+ require 'replica/base'
3
+ require 'replica/association_collection'
4
+ require 'replica/connection_pool'
2
5
 
3
- module ActiveRecord # :nodoc:
4
- class Base # :nodoc:
5
- module Replica
6
-
7
- # Executes queries using the slave database. Fails over to master if no slave is found.
8
- # if you want to execute a block of code on the slave you can go:
9
- # Account.with_slave do
10
- # Account.first
11
- # end
12
- # the first account will be found on the slave DB
13
- #
14
- # For one-liners you can simply do
15
- # Account.with_slave.first
16
- #
17
- # this is the same as:
18
- # Account.with_replica(:slave) do
19
- # Account.first
20
- # end
21
- def with_slave(&block)
22
- with_replica(:slave, &block)
23
- end
24
-
25
- # See with_slave
26
- def with_master(&block)
27
- with_replica(nil, &block)
28
- end
29
-
30
- def with_slave_if(condition, &block)
31
- condition ? with_slave(&block) : with_master(&block)
32
- end
33
-
34
- def with_slave_unless(condition, &block)
35
- with_slave_if(!condition, &block)
36
- end
37
-
38
- # Name of the connection pool. Used by ConnectionHandler to retrieve the current connection pool.
39
- def connection_pool_name # :nodoc:
40
- replica = current_replica_name
41
- if replica
42
- "#{name}_#{replica}"
43
- elsif self == ActiveRecord::Base
44
- name
45
- else
46
- superclass.connection_pool_name
47
- end
48
- end
49
-
50
- # Specify which database to use.
51
- #
52
- # Example:
53
- # database.yml
54
- # test_slave:
55
- # adapter: mysql
56
- # ...
57
- #
58
- # Account.with_replica(:slave) { Account.count }
59
- # Account.with_replica(:slave).count
60
- #
61
- def with_replica(replica_name, &block)
62
- if block_given?
63
- with_replica_block(replica_name, &block)
64
- else
65
- Proxy.new(self, replica_name)
66
- end
67
- end
68
-
69
- def with_replica_block(replica_name, &block)
70
- old_replica_name = current_replica_name
71
- begin
72
- self.current_replica_name = replica_name
73
- rescue ActiveRecord::AdapterNotSpecified => e
74
- self.current_replica_name = old_replica_name
75
- logger.warn("Failed to establish replica connection: #{e.message} - defaulting to master")
76
- end
77
- yield
78
- ensure
79
- self.current_replica_name = old_replica_name
80
- end
81
-
82
- private
83
-
84
- def current_replica_name
85
- Thread.current[replica_key]
86
- end
87
-
88
- def current_replica_name=(new_replica_name)
89
- Thread.current[replica_key] = new_replica_name
90
-
91
- establish_replica_connection(new_replica_name) unless connected_to_replica?
92
- end
93
-
94
- def establish_replica_connection(replica_name)
95
- name = replica_name ? "#{RAILS_ENV}_#{replica_name}" : RAILS_ENV
96
- spec = configurations[name]
97
- raise AdapterNotSpecified.new("No database defined by #{name} in database.yml") if spec.nil?
98
-
99
- connection_handler.establish_connection(connection_pool_name, ConnectionSpecification.new(spec, "#{spec['adapter']}_connection"))
100
- end
101
-
102
- def connected_to_replica?
103
- connection_handler.connection_pools.has_key?(connection_pool_name)
104
- end
105
-
106
- def replica_key
107
- @replica_key ||= "#{name}_replica"
108
- end
109
-
110
- class Proxy
111
- def initialize(target, replica)
112
- @target = target
113
- @replica = replica
114
- end
115
-
116
- def method_missing(method, *args, &block)
117
- @target.with_replica_block(@replica) { @target.send(method, *args, &block) }
118
- end
119
- end
120
- end
121
- end
122
- Base.extend(Base::Replica)
123
-
124
- # The only difference here is that we use klass.connection_pool_name
125
- # instead of klass.name as the pool key
126
- module ConnectionAdapters # :nodoc:
127
- class ConnectionHandler # :nodoc:
128
-
129
- def retrieve_connection_pool(klass)
130
- pool = @connection_pools[klass.connection_pool_name]
131
- return pool if pool
132
- return nil if ActiveRecord::Base == klass
133
- retrieve_connection_pool klass.superclass
134
- end
135
-
136
- def remove_connection(klass)
137
- pool = @connection_pools[klass.connection_pool_name]
138
- @connection_pools.delete_if { |key, value| value == pool }
139
- pool.disconnect! if pool
140
- pool.spec.config if pool
141
- end
142
-
143
- end
144
- end
145
- end
6
+ ActiveRecord::Base.extend ActiveRecord::Base::Replica
7
+ ActiveRecord::Associations::AssociationCollection.send(:include, ActiveRecord::Associations::AssociationCollection::Replica)
@@ -0,0 +1,38 @@
1
+ module ActiveRecord # :nodoc:
2
+ module Associations
3
+ class AssociationCollection < AssociationProxy #:nodoc:
4
+ module Replica
5
+ def with_slave
6
+ with_replica(:slave)
7
+ end
8
+
9
+ def with_master
10
+ with_replica(nil)
11
+ end
12
+
13
+ def with_slave_if(condition)
14
+ condition ? with_slave : with_master
15
+ end
16
+
17
+ def with_slave_unless(condition)
18
+ with_slave_if(!condition)
19
+ end
20
+
21
+ def with_replica(replica_name)
22
+ Proxy.new(self, replica_name)
23
+ end
24
+
25
+ class Proxy
26
+ def initialize(association_collection, replica)
27
+ @association_collection = association_collection
28
+ @replica = replica
29
+ end
30
+
31
+ def method_missing(method, *args, &block)
32
+ @association_collection.proxy_reflection.klass.with_replica_block(@replica) { @association_collection.send(method, *args, &block) }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,120 @@
1
+ module ActiveRecord # :nodoc:
2
+ class Base # :nodoc:
3
+ module Replica
4
+
5
+ # Executes queries using the slave database. Fails over to master if no slave is found.
6
+ # if you want to execute a block of code on the slave you can go:
7
+ # Account.with_slave do
8
+ # Account.first
9
+ # end
10
+ # the first account will be found on the slave DB
11
+ #
12
+ # For one-liners you can simply do
13
+ # Account.with_slave.first
14
+ #
15
+ # this is the same as:
16
+ # Account.with_replica(:slave) do
17
+ # Account.first
18
+ # end
19
+ def with_slave(&block)
20
+ with_replica(:slave, &block)
21
+ end
22
+
23
+ # See with_slave
24
+ def with_master(&block)
25
+ with_replica(nil, &block)
26
+ end
27
+
28
+ def with_slave_if(condition, &block)
29
+ condition ? with_slave(&block) : with_master(&block)
30
+ end
31
+
32
+ def with_slave_unless(condition, &block)
33
+ with_slave_if(!condition, &block)
34
+ end
35
+
36
+ # Name of the connection pool. Used by ConnectionHandler to retrieve the current connection pool.
37
+ def connection_pool_name # :nodoc:
38
+ replica = current_replica_name
39
+ if replica
40
+ "#{name}_#{replica}"
41
+ elsif self == ActiveRecord::Base
42
+ name
43
+ else
44
+ superclass.connection_pool_name
45
+ end
46
+ end
47
+
48
+ # Specify which database to use.
49
+ #
50
+ # Example:
51
+ # database.yml
52
+ # test_slave:
53
+ # adapter: mysql
54
+ # ...
55
+ #
56
+ # Account.with_replica(:slave) { Account.count }
57
+ # Account.with_replica(:slave).count
58
+ #
59
+ def with_replica(replica_name, &block)
60
+ if block_given?
61
+ with_replica_block(replica_name, &block)
62
+ else
63
+ Proxy.new(self, replica_name)
64
+ end
65
+ end
66
+
67
+ def with_replica_block(replica_name, &block)
68
+ old_replica_name = current_replica_name
69
+ begin
70
+ self.current_replica_name = replica_name
71
+ rescue ActiveRecord::AdapterNotSpecified => e
72
+ self.current_replica_name = old_replica_name
73
+ logger.warn("Failed to establish replica connection: #{e.message} - defaulting to master")
74
+ end
75
+ yield
76
+ ensure
77
+ self.current_replica_name = old_replica_name
78
+ end
79
+
80
+ private
81
+
82
+ def current_replica_name
83
+ Thread.current[replica_key]
84
+ end
85
+
86
+ def current_replica_name=(new_replica_name)
87
+ Thread.current[replica_key] = new_replica_name
88
+
89
+ establish_replica_connection(new_replica_name) unless connected_to_replica?
90
+ end
91
+
92
+ def establish_replica_connection(replica_name)
93
+ name = replica_name ? "#{RAILS_ENV}_#{replica_name}" : RAILS_ENV
94
+ spec = configurations[name]
95
+ raise AdapterNotSpecified.new("No database defined by #{name} in database.yml") if spec.nil?
96
+
97
+ connection_handler.establish_connection(connection_pool_name, ConnectionSpecification.new(spec, "#{spec['adapter']}_connection"))
98
+ end
99
+
100
+ def connected_to_replica?
101
+ connection_handler.connection_pools.has_key?(connection_pool_name)
102
+ end
103
+
104
+ def replica_key
105
+ @replica_key ||= "#{name}_replica"
106
+ end
107
+
108
+ class Proxy
109
+ def initialize(target, replica)
110
+ @target = target
111
+ @replica = replica
112
+ end
113
+
114
+ def method_missing(method, *args, &block)
115
+ @target.with_replica_block(@replica) { @target.send(method, *args, &block) }
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,23 @@
1
+ module ActiveRecord # :nodoc:
2
+ # The only difference here is that we use klass.connection_pool_name
3
+ # instead of klass.name as the pool key
4
+ module ConnectionAdapters # :nodoc:
5
+ class ConnectionHandler # :nodoc:
6
+
7
+ def retrieve_connection_pool(klass)
8
+ pool = @connection_pools[klass.connection_pool_name]
9
+ return pool if pool
10
+ return nil if ActiveRecord::Base == klass
11
+ retrieve_connection_pool klass.superclass
12
+ end
13
+
14
+ def remove_connection(klass)
15
+ pool = @connection_pools[klass.connection_pool_name]
16
+ @connection_pools.delete_if { |key, value| value == pool }
17
+ pool.disconnect! if pool
18
+ pool.spec.config if pool
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -1,7 +1,9 @@
1
1
  class Account < ActiveRecord::Base
2
2
  # attributes: id, name, updated_at, created_at
3
+ has_many :tickets
3
4
  end
4
5
 
5
6
  class Ticket < ActiveRecord::Base
6
7
  # attributes: id, title, account_id, updated_at, created_at
8
+ belongs_to :account
7
9
  end
@@ -36,7 +36,6 @@ class ReplicaTest < ActiveRecord::TestCase
36
36
 
37
37
  assert_not_equal Account.count, ActiveRecord::Base.with_slave { Account.count }
38
38
  assert_not_equal Account.count, Account.with_slave { Account.count }
39
- assert_not_equal Account.count, Account.with_slave.count
40
39
  assert_equal Account.count, Ticket.with_slave { Account.count }
41
40
  end
42
41
 
@@ -112,4 +111,24 @@ class ReplicaTest < ActiveRecord::TestCase
112
111
  end
113
112
 
114
113
  end
114
+
115
+ context "replica proxy" do
116
+ should "successfully execute queries" do
117
+ assert_using_master_db(Account)
118
+ Account.create!
119
+
120
+ assert_not_equal Account.count, Account.with_slave.count
121
+ end
122
+
123
+ should "work association collections" do
124
+ assert_using_master_db(Account)
125
+ account = Account.create!
126
+
127
+ Ticket.connection.expects(:select_all).with("SELECT * FROM `tickets` WHERE (`tickets`.account_id = #{account.id}) LIMIT 1", anything).returns([])
128
+ Ticket.with_slave.connection.expects(:select_all).with("SELECT * FROM `tickets` WHERE (`tickets`.account_id = #{account.id}) LIMIT 1", anything).returns([])
129
+
130
+ account.tickets.first
131
+ account.tickets.with_slave.first
132
+ end
133
+ end
115
134
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: replica
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Chapweske
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2010-01-28 00:00:00 -08:00
13
+ date: 2010-02-08 00:00:00 -08:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -40,6 +40,9 @@ files:
40
40
  - Rakefile
41
41
  - VERSION
42
42
  - lib/replica.rb
43
+ - lib/replica/association_collection.rb
44
+ - lib/replica/base.rb
45
+ - lib/replica/connection_pool.rb
43
46
  - test/database.yml
44
47
  - test/helper.rb
45
48
  - test/models.rb