replica 1.1.0 → 1.2.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/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