active_record_shards 2.8.0 → 3.0.0.beta1

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.
@@ -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: