active_record_shards 2.8.0 → 3.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0
2
+ class ActiveRecord::Base
3
+ def self.arel_engine
4
+ @arel_engine ||= begin
5
+ if self == ActiveRecord::Base
6
+ Arel::Table.engine
7
+ else
8
+ connection_handler.connection_pools[connection_pool_name] ? self : superclass.arel_engine
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ ActiveRecord::ConnectionAdapters::ConnectionHandler.class_eval do
2
+ if ActiveRecord::VERSION::MAJOR >= 4
3
+ def retrieve_connection_pool(klass)
4
+ class_to_pool[klass.connection_pool_name] ||= pool_for(klass)
5
+ end
6
+ else
7
+ def retrieve_connection_pool(klass)
8
+ (@class_to_pool || @connection_pools)[klass.connection_pool_name]
9
+ end
10
+ end
11
+ end
@@ -1,55 +1,32 @@
1
- ActiveRecord::ConnectionAdapters::ConnectionHandler.class_eval do
2
- # The only difference here is that we use klass.connection_pool_name
3
- # instead of klass.name as the pool key
4
- def retrieve_connection_pool(klass)
5
- pool = (@class_to_pool || @connection_pools)[klass.connection_pool_name]
6
- return pool if pool
7
- return nil if ActiveRecord::Base == klass
8
- retrieve_connection_pool klass.superclass
9
- end
10
-
11
- def remove_connection(klass)
12
- # rails 2: @connection_pools is a hash of klass.name => pool
13
- # rails 3: @connection_pools is a hash of pool.spec => pool
14
- # @class_to_pool is a hash of klass.name => pool
15
- #
16
- if @class_to_pool
17
- pool = @class_to_pool.delete(klass.connection_pool_name)
18
- @connection_pools.delete(pool.spec) if pool
19
- else
20
- pool = @connection_pools.delete(klass.connection_pool_name)
21
- @connection_pools.delete_if { |key, value| value == pool }
22
- end
23
-
24
- return nil unless pool
25
-
26
- pool.disconnect! if pool
27
- pool.spec.config if pool
28
- end
29
- end
30
-
31
- ActiveRecord::Base.singleton_class.class_eval do
32
- def establish_connection_with_connection_pool_name(spec = nil)
33
- case spec
34
- when ActiveRecord::Base::ConnectionSpecification
35
- connection_handler.establish_connection(connection_pool_name, spec)
36
- else
37
- establish_connection_without_connection_pool_name(spec)
38
- end
39
- end
40
- alias_method_chain :establish_connection, :connection_pool_name
41
- end
1
+ module ActiveRecordShards
2
+ ConnectionPoolNameDecorator = Struct.new(:name)
42
3
 
43
- # old_code.sub('name', 'connection_pool_name')
44
- if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0
45
- class ActiveRecord::Base
46
- def self.arel_engine
47
- @arel_engine ||= begin
48
- if self == ActiveRecord::Base
49
- Arel::Table.engine
50
- else
51
- connection_handler.connection_pools[connection_pool_name] ? self : superclass.arel_engine
4
+ # It overrides given connection handler methods (they differ depend on
5
+ # Rails version).
6
+ #
7
+ # It takes the first argument, ActiveRecord::Base object or
8
+ # String (connection_pool_name), converts it in Struct object and
9
+ # passes to the original method.
10
+ #
11
+ # Example:
12
+ # methods_to_override = [:establish_connection, :remove_connection]
13
+ # ActiveRecordShards.override_connection_handler_methods(methods_to_override)
14
+ #
15
+ def self.override_connection_handler_methods(method_names)
16
+ method_names.each do |method_name|
17
+ ActiveRecord::ConnectionAdapters::ConnectionHandler.class_eval do
18
+ define_method("#{method_name}_with_connection_pool_name") do |*args|
19
+ unless args[0].is_a? ConnectionPoolNameDecorator
20
+ name = if args[0].is_a? String
21
+ args[0]
22
+ else
23
+ args[0].connection_pool_name
24
+ end
25
+ args[0] = ConnectionPoolNameDecorator.new(name)
26
+ end
27
+ send("#{method_name}_without_connection_pool_name", *args)
52
28
  end
29
+ alias_method_chain method_name, :connection_pool_name
53
30
  end
54
31
  end
55
32
  end
@@ -1,7 +1,6 @@
1
1
  class ActiveRecord::Base
2
-
3
2
  def self.establish_connection(spec = ENV["DATABASE_URL"])
4
- resolver = ConnectionSpecification::Resolver.new spec, configurations
3
+ resolver = ActiveRecordShards::ConnectionSpecification::Resolver.new spec, configurations
5
4
  spec = resolver.spec
6
5
 
7
6
  unless respond_to?(spec.adapter_method)
@@ -10,6 +9,11 @@ class ActiveRecord::Base
10
9
 
11
10
  remove_connection
12
11
  specification_cache[connection_pool_name] = spec
13
- connection_handler.establish_connection connection_pool_name, spec
12
+
13
+ if ActiveRecord::VERSION::STRING >= "4.0.0"
14
+ connection_handler.establish_connection self, spec
15
+ else
16
+ connection_handler.establish_connection connection_pool_name, spec
17
+ end
14
18
  end
15
19
  end
@@ -95,7 +95,11 @@ module ActiveRecordShards
95
95
  if self == ActiveRecord::Base || !switch_to_slave || options[:construct_ro_scope] == false
96
96
  yield
97
97
  else
98
- with_scope({:find => {:readonly => true}}, &block)
98
+ if ActiveRecord::VERSION::MAJOR == 2
99
+ with_scope({:find => {:readonly => true}}, &block)
100
+ else
101
+ readonly.scoping(&block)
102
+ end
99
103
  end
100
104
  ensure
101
105
  @disallow_slave -= 1 if which == :master
@@ -174,11 +178,11 @@ module ActiveRecordShards
174
178
  # connection adapter ourselves.
175
179
  specification_cache[name] ||= begin
176
180
  if ActiveRecord::VERSION::STRING >= "3.2.0"
177
- resolver = ActiveRecord::Base::ConnectionSpecification::Resolver.new spec, configurations
181
+ resolver = ActiveRecordShards::ConnectionSpecification::Resolver.new spec, configurations
178
182
  resolver.spec
179
183
  else
180
184
  autoload_adapter(spec['adapter'])
181
- ActiveRecord::Base::ConnectionSpecification.new(spec, "#{spec['adapter']}_connection")
185
+ ActiveRecordShards::ConnectionSpecification.new(spec, "#{spec['adapter']}_connection")
182
186
  end
183
187
  end
184
188
 
@@ -198,7 +202,13 @@ module ActiveRecordShards
198
202
  end
199
203
 
200
204
  def connected_to_shard?
201
- connection_handler.connection_pools.has_key?(connection_pool_key)
205
+ if ActiveRecord::VERSION::MAJOR == 4
206
+ specs_to_pools = Hash[connection_handler.connection_pool_list.map { |pool| [pool.spec, pool] }]
207
+ else
208
+ specs_to_pools = connection_handler.connection_pools
209
+ end
210
+
211
+ specs_to_pools.has_key?(connection_pool_key)
202
212
  end
203
213
 
204
214
  def columns_with_default_shard
@@ -1,32 +1,23 @@
1
1
  module ActiveRecordShards
2
2
  module DefaultSlavePatches
3
- def self.wrap_method_in_on_slave(class_method, base, method)
3
+ CLASS_SLAVE_METHODS = [ :find_by_sql, :count_by_sql, :calculate, :find_one, :find_some, :find_every, :quote_value, :sanitize_sql_hash_for_conditions, :exists? ]
4
4
 
5
- if class_method
6
- base_methods = (base.methods | base.private_methods).map(&:to_sym)
7
- else
8
- base_methods = (base.instance_methods | base.private_instance_methods).map(&:to_sym)
9
- end
10
-
11
- return unless base_methods.include?(method)
12
- _, method, punctuation = method.to_s.match(/^(.*?)([\?\!]?)$/).to_a
13
- base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
14
- #{class_method ? "class << self" : ""}
15
- def #{method}_with_default_slave#{punctuation}(*args, &block)
16
- on_slave_unless_tx do
17
- #{method}_without_default_slave#{punctuation}(*args, &block)
5
+ def self.extended(base)
6
+ base_methods = (base.methods | base.private_methods).map(&:to_sym)
7
+ (CLASS_SLAVE_METHODS & base_methods).each do |slave_method|
8
+ _, slave_method, punctuation = slave_method.to_s.match(/^(.*?)([\?\!]?)$/).to_a
9
+ base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
10
+ class << self
11
+ def #{slave_method}_with_default_slave#{punctuation}(*args, &block)
12
+ on_slave_unless_tx do
13
+ #{slave_method}_without_default_slave#{punctuation}(*args, &block)
14
+ end
18
15
  end
19
- end
20
-
21
- alias_method_chain :#{method}#{punctuation}, :default_slave
22
- #{class_method ? "end" : ""}
23
- RUBY
24
- end
25
16
 
26
- CLASS_SLAVE_METHODS = [ :find_by_sql, :count_by_sql, :calculate, :find_one, :find_some, :find_every, :quote_value, :sanitize_sql_hash_for_conditions, :exists?, :table_exists? ]
27
-
28
- def self.extended(base)
29
- CLASS_SLAVE_METHODS.each { |m| ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(true, base, m) }
17
+ alias_method_chain :#{slave_method}#{punctuation}, :default_slave
18
+ end
19
+ RUBY
20
+ end
30
21
 
31
22
  base.class_eval do
32
23
  # fix ActiveRecord to do the right thing, and use our aliased quote_value
@@ -67,10 +58,33 @@ module ActiveRecordShards
67
58
  end
68
59
 
69
60
  alias_method_chain :transaction, :slave_off
61
+
62
+
63
+ def table_exists_with_default_slave?(*args)
64
+ on_slave_unless_tx(*args) { table_exists_without_default_slave?(*args) }
65
+ end
66
+
67
+ alias_method_chain :table_exists?, :default_slave
70
68
  end
71
69
  end
72
- ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, ActiveRecord::Associations::HasAndBelongsToManyAssociation, :construct_sql)
73
- ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, ActiveRecord::Associations::HasAndBelongsToManyAssociation, :construct_find_options!)
70
+
71
+
72
+ ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do
73
+ def construct_sql_with_default_slave(*args, &block)
74
+ on_slave_unless_tx do
75
+ construct_sql_without_default_slave(*args, &block)
76
+ end
77
+ end
78
+
79
+ def construct_find_options_with_default_slave!(*args, &block)
80
+ on_slave_unless_tx do
81
+ construct_find_options_without_default_slave!(*args, &block)
82
+ end
83
+ end
84
+
85
+ alias_method_chain :construct_sql, :default_slave if respond_to?(:construct_sql)
86
+ alias_method_chain :construct_find_options!, :default_slave if respond_to?(:construct_find_options!)
87
+ end
74
88
  end
75
89
 
76
90
  def on_slave_unless_tx(&block)
@@ -83,23 +97,20 @@ module ActiveRecordShards
83
97
 
84
98
  module ActiveRelationPatches
85
99
  def self.included(base)
86
- ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, base, :calculate)
87
- ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, base, :exists?)
88
- ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, base, :pluck)
100
+ base.send :alias_method_chain, :calculate, :default_slave
101
+ base.send :alias_method_chain, :exists?, :default_slave
89
102
  end
90
103
 
91
104
  def on_slave_unless_tx
92
105
  @klass.on_slave_unless_tx { yield }
93
106
  end
94
- end
95
107
 
96
- module HasAndBelongsToManyPreloaderPatches
97
- def self.included(base)
98
- ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, base, :records_for) rescue nil
108
+ def calculate_with_default_slave(*args, &block)
109
+ on_slave_unless_tx { calculate_without_default_slave(*args, &block) }
99
110
  end
100
111
 
101
- def on_slave_unless_tx
102
- klass.on_slave_unless_tx { yield }
112
+ def exists_with_default_slave?(*args, &block)
113
+ on_slave_unless_tx { exists_without_default_slave?(*args, &block) }
103
114
  end
104
115
  end
105
116
  end
@@ -8,11 +8,24 @@ require 'active_record_shards/association_collection_connection_selection'
8
8
  require 'active_record_shards/connection_pool'
9
9
  require 'active_record_shards/migration'
10
10
  require 'active_record_shards/default_slave_patches'
11
+ require 'active_record_shards/arel_engine'
12
+ require 'active_record_shards/connection_handler'
11
13
 
12
14
  if ActiveRecord::VERSION::STRING >= "3.2.0"
13
15
  require 'active_record_shards/connection_specification'
14
16
  end
15
17
 
18
+ if ActiveRecord::VERSION::STRING >= "4.0.0"
19
+ methods_to_override = [:establish_connection, :remove_connection, :pool_for,
20
+ :pool_from_any_process_for]
21
+ ActiveRecordShards::ConnectionSpecification = ActiveRecord::ConnectionAdapters::ConnectionSpecification
22
+ else
23
+ methods_to_override = [:remove_connection]
24
+ ActiveRecordShards::ConnectionSpecification = ActiveRecord::Base::ConnectionSpecification
25
+ end
26
+
27
+ ActiveRecordShards.override_connection_handler_methods(methods_to_override)
28
+
16
29
  ActiveRecord::Base.extend(ActiveRecordShards::ConfigurationParser)
17
30
  ActiveRecord::Base.extend(ActiveRecordShards::Model)
18
31
  ActiveRecord::Base.extend(ActiveRecordShards::ConnectionSwitcher)
@@ -22,10 +35,6 @@ if ActiveRecord.const_defined?(:Relation)
22
35
  ActiveRecord::Relation.send(:include, ActiveRecordShards::DefaultSlavePatches::ActiveRelationPatches)
23
36
  end
24
37
 
25
- if ActiveRecord::Associations.const_defined?(:Preloader)
26
- ActiveRecord::Associations::Preloader::HasAndBelongsToMany.send(:include, ActiveRecordShards::DefaultSlavePatches::HasAndBelongsToManyPreloaderPatches)
27
- end
28
-
29
38
  if ActiveRecord::VERSION::STRING >= "3.1.0"
30
39
  ActiveRecord::Associations::CollectionProxy.send(:include, ActiveRecordShards::AssociationCollectionConnectionSelection)
31
40
  else
@@ -39,3 +48,15 @@ module ActiveRecordShards
39
48
  env ||= ENV['RAILS_ENV']
40
49
  end
41
50
  end
51
+
52
+ ActiveRecord::Base.singleton_class.class_eval do
53
+ def establish_connection_with_connection_pool_name(spec = nil)
54
+ case spec
55
+ when ActiveRecordShards::ConnectionSpecification
56
+ connection_handler.establish_connection(connection_pool_name, spec)
57
+ else
58
+ establish_connection_without_connection_pool_name(spec)
59
+ end
60
+ end
61
+ alias_method_chain :establish_connection, :connection_pool_name
62
+ end
@@ -233,7 +233,6 @@ describe "connection switching" do
233
233
  end
234
234
 
235
235
  it "execute the block on all shard masters" do
236
- @database_names
237
236
  assert_equal([ActiveRecord::Base.connection.select_value("SELECT DATABASE()")], @database_names)
238
237
  end
239
238
  end
@@ -244,7 +243,11 @@ describe "connection switching" do
244
243
 
245
244
  before do
246
245
  ActiveRecord::Base.configurations.delete('test_slave')
247
- ActiveRecord::Base.connection_handler.connection_pools.clear
246
+ if ActiveRecord::VERSION::MAJOR == 4
247
+ ActiveRecord::Base.connection_handler.connection_pool_list.clear
248
+ else
249
+ ActiveRecord::Base.connection_handler.connection_pools.clear
250
+ end
248
251
  ActiveRecord::Base.establish_connection('test')
249
252
  end
250
253
 
@@ -380,19 +383,13 @@ describe "connection switching" do
380
383
  end
381
384
  end
382
385
 
386
+ # TODO: make all this stuff rails 3 compatible.
383
387
  describe "with finds routed to the slave by default" do
384
388
  before do
385
389
  Account.on_slave_by_default = true
386
- Person.on_slave_by_default = true
387
390
  Account.connection.execute("INSERT INTO accounts (id, name, created_at, updated_at) VALUES(1000, 'master_name', '2009-12-04 20:18:48', '2009-12-04 20:18:48')")
388
391
  Account.on_slave.connection.execute("INSERT INTO accounts (id, name, created_at, updated_at) VALUES(1000, 'slave_name', '2009-12-04 20:18:48', '2009-12-04 20:18:48')")
389
392
  Account.on_slave.connection.execute("INSERT INTO accounts (id, name, created_at, updated_at) VALUES(1001, 'slave_name2', '2009-12-04 20:18:48', '2009-12-04 20:18:48')")
390
-
391
- Person.connection.execute("REPLACE INTO people(id, name) VALUES(10, 'master person')")
392
- Person.on_slave.connection.execute("REPLACE INTO people(id, name) VALUES(20, 'slave person')")
393
-
394
- Account.connection.execute("INSERT INTO account_people(account_id, person_id) VALUES(1000, 10)")
395
- Account.on_slave.connection.execute("INSERT INTO account_people(account_id, person_id) VALUES(1001, 20)")
396
393
  end
397
394
 
398
395
  it "find() by default on the slave" do
@@ -451,21 +448,8 @@ describe "connection switching" do
451
448
  assert AccountInherited.on_slave_by_default?
452
449
  end
453
450
 
454
- it "will :include things via has_and_belongs associations correctly" do
455
- a = Account.first(:conditions => "id = 1001", :include => :people)
456
- assert a.people.size > 0
457
- assert_equal 'slave person', a.people.first.name
458
- end
459
-
460
- if ActiveRecord::VERSION::MAJOR >= 3 && ActiveRecord::VERSION::MINOR >= 2
461
- it "supports .pluck" do
462
- assert_equal ["slave_name", "slave_name2"], Account.pluck(:name)
463
- end
464
- end
465
-
466
451
  after do
467
452
  Account.on_slave_by_default = false
468
- Person.on_slave_by_default = false
469
453
  end
470
454
  end
471
455
  end
@@ -518,4 +502,14 @@ describe "connection switching" do
518
502
  assert_using_database('ars_test_alternative', Email)
519
503
  end
520
504
  end
505
+
506
+ it "raises an exception if a connection is not found" do
507
+ ActiveRecord::Base.on_shard(0) do
508
+ ActiveRecord::Base.connection_handler.remove_connection(Ticket)
509
+ assert_raises(ActiveRecord::ConnectionNotEstablished) do
510
+ ActiveRecord::Base.connection_handler.retrieve_connection_pool(Ticket)
511
+ assert_using_database('ars_test_shard0', Ticket)
512
+ end
513
+ end
514
+ end
521
515
  end
data/test/models.rb CHANGED
@@ -4,7 +4,6 @@ class Account < ActiveRecord::Base
4
4
 
5
5
  has_many :tickets
6
6
  has_many :account_things
7
- has_and_belongs_to_many :people, :join_table => 'account_people'
8
7
  end
9
8
 
10
9
  class AccountThing < ActiveRecord::Base
@@ -37,4 +36,3 @@ end
37
36
  class User < Person
38
37
  end
39
38
 
40
-
data/test/schema.rb CHANGED
@@ -12,11 +12,6 @@ ActiveRecord::Schema.define(:version => 1) do
12
12
  t.boolean "enabled", :default => true
13
13
  end
14
14
 
15
- create_table "account_people", :force => true, :id => false do |t|
16
- t.integer "account_id"
17
- t.integer "person_id"
18
- end
19
-
20
15
  create_table "emails", :force => true do |t|
21
16
  t.string "from"
22
17
  t.string "to"
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_shards
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.0
5
- prerelease:
4
+ version: 3.0.0.beta1
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Mick Staugaard
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2013-11-22 00:00:00.000000000 Z
14
+ date: 2013-11-06 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: activerecord
@@ -21,9 +21,9 @@ dependencies:
21
21
  - - ! '>='
22
22
  - !ruby/object:Gem::Version
23
23
  version: 2.3.5
24
- - - <
24
+ - - <=
25
25
  - !ruby/object:Gem::Version
26
- version: '3.3'
26
+ version: '4.1'
27
27
  type: :runtime
28
28
  prerelease: false
29
29
  version_requirements: !ruby/object:Gem::Requirement
@@ -32,9 +32,9 @@ dependencies:
32
32
  - - ! '>='
33
33
  - !ruby/object:Gem::Version
34
34
  version: 2.3.5
35
- - - <
35
+ - - <=
36
36
  - !ruby/object:Gem::Version
37
- version: '3.3'
37
+ version: '4.1'
38
38
  description: Easily run queries on shard and slave databases.
39
39
  email:
40
40
  - mick@staugaard.com
@@ -44,8 +44,10 @@ executables: []
44
44
  extensions: []
45
45
  extra_rdoc_files: []
46
46
  files:
47
+ - lib/active_record_shards/arel_engine.rb
47
48
  - lib/active_record_shards/association_collection_connection_selection.rb
48
49
  - lib/active_record_shards/configuration_parser.rb
50
+ - lib/active_record_shards/connection_handler.rb
49
51
  - lib/active_record_shards/connection_pool.rb
50
52
  - lib/active_record_shards/connection_specification.rb
51
53
  - lib/active_record_shards/connection_switcher.rb
@@ -81,18 +83,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
81
83
  - - ! '>='
82
84
  - !ruby/object:Gem::Version
83
85
  version: '0'
84
- segments:
85
- - 0
86
- hash: -479183148352030802
87
86
  required_rubygems_version: !ruby/object:Gem::Requirement
88
87
  none: false
89
88
  requirements:
90
- - - ! '>='
89
+ - - ! '>'
91
90
  - !ruby/object:Gem::Version
92
- version: '0'
93
- segments:
94
- - 0
95
- hash: -479183148352030802
91
+ version: 1.3.1
96
92
  requirements: []
97
93
  rubyforge_project:
98
94
  rubygems_version: 1.8.25
@@ -112,3 +108,4 @@ test_files:
112
108
  - test/migrator_test.rb
113
109
  - test/models.rb
114
110
  - test/schema.rb
111
+ has_rdoc: