db-charmer 1.8.4 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,7 @@
2
2
  require 'active_record/version' unless defined?(::ActiveRecord::VERSION::MAJOR)
3
3
  require 'active_support/core_ext'
4
4
 
5
+ #---------------------------------------------------------------------------------------------------
5
6
  module DbCharmer
6
7
  # Configure autoload
7
8
  autoload :Sharding, 'db_charmer/sharding'
@@ -68,64 +69,33 @@ module DbCharmer
68
69
  ::ActionController::Base.extend(DbCharmer::ActionController::ForceSlaveReads::ClassMethods)
69
70
  ::ActionController::Base.send(:include, DbCharmer::ActionController::ForceSlaveReads::InstanceMethods)
70
71
  end
71
-
72
- #-------------------------------------------------------------------------------------------------
73
- def self.with_remapped_databases(mappings, &proc)
74
- old_mappings = ::ActiveRecord::Base.db_charmer_database_remappings
75
- begin
76
- ::ActiveRecord::Base.db_charmer_database_remappings = mappings
77
- if mappings[:master] || mappings['master']
78
- with_all_hijacked(&proc)
79
- else
80
- proc.call
81
- end
82
- ensure
83
- ::ActiveRecord::Base.db_charmer_database_remappings = old_mappings
84
- end
85
- end
86
-
87
- def self.hijack_new_classes?
88
- @@hijack_new_classes
89
- end
90
-
91
- private
92
-
93
- @@hijack_new_classes = false
94
- def self.with_all_hijacked
95
- old_hijack_new_classes = @@hijack_new_classes
96
- begin
97
- @@hijack_new_classes = true
98
- subclasses_method = DbCharmer.rails3? ? :descendants : :subclasses
99
- ::ActiveRecord::Base.send(subclasses_method).each do |subclass|
100
- subclass.hijack_connection!
101
- end
102
- yield
103
- ensure
104
- @@hijack_new_classes = old_hijack_new_classes
105
- end
106
- end
107
72
  end
108
73
 
109
74
  #---------------------------------------------------------------------------------------------------
110
75
  # Print warning about the broken Rails 2.3.4
111
76
  puts "WARNING: Rails 3.2.4 is not officially supported by DbCharmer. Please upgrade." if DbCharmer.rails324?
112
77
 
78
+ #---------------------------------------------------------------------------------------------------
113
79
  # Add useful methods to global object
114
80
  require 'db_charmer/core_extensions'
115
81
 
116
82
  require 'db_charmer/connection_factory'
117
83
  require 'db_charmer/connection_proxy'
118
84
  require 'db_charmer/force_slave_reads'
85
+ require 'db_charmer/with_remapped_databases'
119
86
 
87
+ #---------------------------------------------------------------------------------------------------
120
88
  # Add our custom class-level attributes to AR models
121
89
  require 'db_charmer/active_record/class_attributes'
122
90
  require 'active_record'
123
91
  ActiveRecord::Base.extend(DbCharmer::ActiveRecord::ClassAttributes)
124
92
 
93
+ #---------------------------------------------------------------------------------------------------
125
94
  # Enable connections switching in AR
126
95
  require 'db_charmer/active_record/connection_switching'
127
96
  ActiveRecord::Base.extend(DbCharmer::ActiveRecord::ConnectionSwitching)
128
97
 
98
+ #---------------------------------------------------------------------------------------------------
129
99
  # Enable AR logging extensions
130
100
  if DbCharmer.rails3?
131
101
  require 'db_charmer/rails3/abstract_adapter/connection_name'
@@ -137,12 +107,14 @@ else
137
107
  ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, DbCharmer::AbstractAdapter::LogFormatting)
138
108
  end
139
109
 
110
+ #---------------------------------------------------------------------------------------------------
140
111
  # Enable connection proxy in AR
141
112
  require 'db_charmer/active_record/multi_db_proxy'
142
113
  ActiveRecord::Base.extend(DbCharmer::ActiveRecord::MultiDbProxy::ClassMethods)
143
114
  ActiveRecord::Base.extend(DbCharmer::ActiveRecord::MultiDbProxy::MasterSlaveClassMethods)
144
115
  ActiveRecord::Base.send(:include, DbCharmer::ActiveRecord::MultiDbProxy::InstanceMethods)
145
116
 
117
+ #---------------------------------------------------------------------------------------------------
146
118
  # Enable connection proxy for relations
147
119
  if DbCharmer.rails3?
148
120
  require 'db_charmer/rails3/active_record/relation_method'
@@ -151,15 +123,19 @@ if DbCharmer.rails3?
151
123
  ActiveRecord::Relation.send(:include, DbCharmer::ActiveRecord::Relation::ConnectionRouting)
152
124
  end
153
125
 
126
+ #---------------------------------------------------------------------------------------------------
154
127
  # Enable connection proxy for scopes (rails 2.x only)
155
128
  if DbCharmer.rails2?
156
129
  require 'db_charmer/rails2/active_record/named_scope/scope_proxy'
157
130
  ActiveRecord::NamedScope::Scope.send(:include, DbCharmer::ActiveRecord::NamedScope::ScopeProxy)
158
131
  end
159
132
 
133
+ #---------------------------------------------------------------------------------------------------
160
134
  # Enable connection proxy for associations
161
- # WARNING: Inject methods to association class right here (they proxy include calls somewhere else, so include does not work)
162
- association_proxy_class = DbCharmer.rails31? ? ActiveRecord::Associations::CollectionProxy : ActiveRecord::Associations::AssociationProxy
135
+ # WARNING: Inject methods to association class right here because they proxy +include+ calls
136
+ # somewhere else, which means we could not use +include+ method here
137
+ association_proxy_class = DbCharmer.rails31? ? ActiveRecord::Associations::CollectionProxy :
138
+ ActiveRecord::Associations::AssociationProxy
163
139
  association_proxy_class.class_eval do
164
140
  def proxy?
165
141
  true
@@ -194,6 +170,7 @@ association_proxy_class.class_eval do
194
170
  end
195
171
  end
196
172
 
173
+ #---------------------------------------------------------------------------------------------------
197
174
  # Enable multi-db migrations
198
175
  require 'db_charmer/active_record/migration/multi_db_migrations'
199
176
  ActiveRecord::Migration.send(:include, DbCharmer::ActiveRecord::Migration::MultiDbMigrations)
@@ -203,6 +180,7 @@ if DbCharmer.rails31?
203
180
  ActiveRecord::Migration::CommandRecorder.send(:include, DbCharmer::ActiveRecord::Migration::CommandRecorder)
204
181
  end
205
182
 
183
+ #---------------------------------------------------------------------------------------------------
206
184
  # Enable the magic
207
185
  if DbCharmer.rails3?
208
186
  require 'db_charmer/rails3/active_record/master_slave_routing'
@@ -214,6 +192,7 @@ require 'db_charmer/active_record/sharding'
214
192
  require 'db_charmer/active_record/db_magic'
215
193
  ActiveRecord::Base.extend(DbCharmer::ActiveRecord::DbMagic)
216
194
 
195
+ #---------------------------------------------------------------------------------------------------
217
196
  # Setup association preload magic
218
197
  if DbCharmer.rails31?
219
198
  require 'db_charmer/rails31/active_record/preloader/association'
@@ -227,17 +206,3 @@ else
227
206
  # Open up really useful API method
228
207
  ActiveRecord::AssociationPreload::ClassMethods.send(:public, :preload_associations)
229
208
  end
230
-
231
- #---------------------------------------------------------------------------------------------------
232
- # Hijack connection on all new AR classes when we're in a block with main AR connection remapped
233
- class ActiveRecord::Base
234
- class << self
235
- def inherited_with_hijacking(subclass)
236
- out = inherited_without_hijacking(subclass)
237
- hijack_connection! if DbCharmer.hijack_new_classes?
238
- out
239
- end
240
-
241
- alias_method_chain :inherited, :hijacking
242
- end
243
- end
@@ -10,17 +10,7 @@ module DbCharmer
10
10
  @@db_charmer_opts[self.name] || {}
11
11
  end
12
12
 
13
- #-----------------------------------------------------------------------------
14
- @@db_charmer_connection_proxies = {}
15
- def db_charmer_connection_proxy=(proxy)
16
- @@db_charmer_connection_proxies[self.name] = proxy
17
- end
18
-
19
- def db_charmer_connection_proxy
20
- @@db_charmer_connection_proxies[self.name]
21
- end
22
-
23
- #-----------------------------------------------------------------------------
13
+ #---------------------------------------------------------------------------------------------
24
14
  @@db_charmer_default_connections = {}
25
15
  def db_charmer_default_connection=(conn)
26
16
  @@db_charmer_default_connections[self.name] = conn
@@ -30,7 +20,7 @@ module DbCharmer
30
20
  @@db_charmer_default_connections[self.name]
31
21
  end
32
22
 
33
- #-----------------------------------------------------------------------------
23
+ #---------------------------------------------------------------------------------------------
34
24
  @@db_charmer_slaves = {}
35
25
  def db_charmer_slaves=(slaves)
36
26
  @@db_charmer_slaves[self.name] = slaves
@@ -40,19 +30,36 @@ module DbCharmer
40
30
  @@db_charmer_slaves[self.name] || []
41
31
  end
42
32
 
33
+ # Returns a random connection from the list of slaves configured for this AR class
43
34
  def db_charmer_random_slave
44
35
  return nil unless db_charmer_slaves.any?
45
36
  db_charmer_slaves[rand(db_charmer_slaves.size)]
46
37
  end
47
38
 
48
- #-----------------------------------------------------------------------------
49
- @@db_charmer_force_slave_reads = {}
39
+ #---------------------------------------------------------------------------------------------
40
+ def db_charmer_connection_proxies
41
+ Thread.current[:db_charmer_connection_proxies] ||= {}
42
+ end
43
+
44
+ def db_charmer_connection_proxy=(proxy)
45
+ db_charmer_connection_proxies[self.name] = proxy
46
+ end
47
+
48
+ def db_charmer_connection_proxy
49
+ db_charmer_connection_proxies[self.name]
50
+ end
51
+
52
+ #---------------------------------------------------------------------------------------------
53
+ def db_charmer_force_slave_reads_flags
54
+ Thread.current[:db_charmer_force_slave_reads] ||= {}
55
+ end
56
+
50
57
  def db_charmer_force_slave_reads=(force)
51
- @@db_charmer_force_slave_reads[self.name] = force
58
+ db_charmer_force_slave_reads_flags[self.name] = force
52
59
  end
53
60
 
54
61
  def db_charmer_force_slave_reads
55
- @@db_charmer_force_slave_reads[self.name]
62
+ db_charmer_force_slave_reads_flags[self.name]
56
63
  end
57
64
 
58
65
  # Slave reads are used in two cases:
@@ -62,39 +69,47 @@ module DbCharmer
62
69
  db_charmer_force_slave_reads || DbCharmer.force_slave_reads?
63
70
  end
64
71
 
65
- #-----------------------------------------------------------------------------
66
- @@db_charmer_connection_levels = Hash.new(0)
72
+ #---------------------------------------------------------------------------------------------
73
+ def db_charmer_connection_levels
74
+ Thread.current[:db_charmer_connection_levels] ||= Hash.new(0)
75
+ end
76
+
67
77
  def db_charmer_connection_level=(level)
68
- @@db_charmer_connection_levels[self.name] = level
78
+ db_charmer_connection_levels[self.name] = level
69
79
  end
70
80
 
71
81
  def db_charmer_connection_level
72
- @@db_charmer_connection_levels[self.name] || 0
82
+ db_charmer_connection_levels[self.name] || 0
73
83
  end
74
84
 
75
85
  def db_charmer_top_level_connection?
76
86
  db_charmer_connection_level.zero?
77
87
  end
78
88
 
79
- #-----------------------------------------------------------------------------
80
- @@db_charmer_database_remappings = Hash.new
89
+ #---------------------------------------------------------------------------------------------
81
90
  def db_charmer_remapped_connection
82
- return nil if (db_charmer_connection_level || 0) > 0
91
+ return nil unless db_charmer_top_level_connection?
83
92
  name = :master
84
- proxy = db_charmer_connection_proxy
93
+ proxy = db_charmer_model_connection_proxy
85
94
  name = proxy.db_charmer_connection_name.to_sym if proxy
86
95
 
87
- remapped = @@db_charmer_database_remappings[name]
96
+ remapped = db_charmer_database_remappings[name]
88
97
  remapped ? DbCharmer::ConnectionFactory.connect(remapped, true) : nil
89
98
  end
90
99
 
91
100
  def db_charmer_database_remappings
92
- @@db_charmer_database_remappings
101
+ Thread.current[:db_charmer_database_remappings] ||= Hash.new
93
102
  end
94
103
 
95
104
  def db_charmer_database_remappings=(mappings)
96
105
  raise "Mappings must be nil or respond to []" if mappings && (! mappings.respond_to?(:[]))
97
- @@db_charmer_database_remappings = mappings || { }
106
+ Thread.current[:db_charmer_database_remappings] = mappings || {}
107
+ end
108
+
109
+ #---------------------------------------------------------------------------------------------
110
+ # Returns model-specific connection proxy, ignoring any global connection remappings
111
+ def db_charmer_model_connection_proxy
112
+ db_charmer_connection_proxy || db_charmer_default_connection
98
113
  end
99
114
  end
100
115
  end
@@ -31,13 +31,13 @@ module DbCharmer
31
31
  class << self
32
32
  # Make sure we check our accessors before going to the default connection retrieval method
33
33
  def connection_with_magic
34
- db_charmer_remapped_connection || db_charmer_connection_proxy || connection_without_magic
34
+ db_charmer_remapped_connection || db_charmer_model_connection_proxy || connection_without_magic
35
35
  end
36
36
  alias_method_chain :connection, :magic
37
37
 
38
38
  def connection_pool_with_magic
39
- abstract_connection_class = connection.abstract_connection_class rescue nil # respond_to? doesn't work on connection_proxy...
40
- if abstract_connection_class
39
+ if connection.respond_to?(:abstract_connection_class)
40
+ abstract_connection_class = connection.abstract_connection_class
41
41
  connection_handler.retrieve_connection_pool(abstract_connection_class) || connection_pool_without_magic
42
42
  else
43
43
  connection_pool_without_magic
@@ -49,26 +49,31 @@ module DbCharmer
49
49
 
50
50
  #-----------------------------------------------------------------------------------------------------------------
51
51
  def coerce_to_connection_proxy(conn, should_exist = true)
52
+ # Return nil if given no connection specification
52
53
  return nil if conn.nil?
53
54
 
55
+ # For sharded proxies just use them as-is
56
+ return conn if conn.respond_to?(:set_real_connection)
57
+
58
+ # For connection proxies and objects that could be coerced into a proxy just call the coercion method
59
+ return conn.db_charmer_connection_proxy if conn.respond_to?(:db_charmer_connection_proxy)
60
+
61
+ # For plain AR connection adapters, just use them as-is
62
+ return conn if conn.kind_of?(::ActiveRecord::ConnectionAdapters::AbstractAdapter)
63
+
64
+ # For connection names, use connection factory to create new connections
54
65
  if conn.kind_of?(Symbol) || conn.kind_of?(String)
55
66
  return DbCharmer::ConnectionFactory.connect(conn, should_exist)
56
67
  end
57
68
 
69
+ # For connection configs (hashes), create connections
58
70
  if conn.kind_of?(Hash)
59
71
  conn = conn.symbolize_keys
60
72
  raise ArgumentError, "Missing required :connection_name parameter" unless conn[:connection_name]
61
73
  return DbCharmer::ConnectionFactory.connect_to_db(conn[:connection_name], conn)
62
74
  end
63
75
 
64
- if conn.respond_to?(:db_charmer_connection_proxy)
65
- return conn.db_charmer_connection_proxy
66
- end
67
-
68
- if conn.kind_of?(::ActiveRecord::ConnectionAdapters::AbstractAdapter) || conn.kind_of?(DbCharmer::Sharding::StubConnection)
69
- return conn
70
- end
71
-
76
+ # Fails for unsupported connection types
72
77
  raise "Unsupported connection type: #{conn.class}"
73
78
  end
74
79
 
@@ -76,14 +81,12 @@ module DbCharmer
76
81
  def switch_connection_to(conn, should_exist = true)
77
82
  new_conn = coerce_to_connection_proxy(conn, should_exist)
78
83
 
79
- if db_charmer_connection_proxy.is_a?(DbCharmer::Sharding::StubConnection)
84
+ if db_charmer_connection_proxy.respond_to?(:set_real_connection)
80
85
  db_charmer_connection_proxy.set_real_connection(new_conn)
81
86
  end
82
87
 
83
88
  self.db_charmer_connection_proxy = new_conn
84
89
  self.hijack_connection!
85
-
86
- # self.reset_column_information
87
90
  end
88
91
 
89
92
  end
@@ -64,8 +64,9 @@ module DbCharmer
64
64
  end
65
65
 
66
66
  def setup_connection_magic(conn, should_exist = true)
67
- switch_connection_to(conn, should_exist)
68
- self.db_charmer_default_connection = conn
67
+ conn_proxy = coerce_to_connection_proxy(conn, should_exist)
68
+ self.db_charmer_default_connection = conn_proxy
69
+ switch_connection_to(conn_proxy, should_exist)
69
70
  end
70
71
 
71
72
  def setup_slaves_magic(slaves, force_slave_reads, should_exist = true)
@@ -6,22 +6,28 @@
6
6
  #
7
7
  module DbCharmer
8
8
  module ConnectionFactory
9
- @@connection_classes = {}
9
+ def self.connection_classes
10
+ Thread.current[:db_charmer_generated_connection_classes] ||= {}
11
+ end
12
+
13
+ def self.connection_classes=(val)
14
+ Thread.current[:db_charmer_generated_connection_classes] = val
15
+ end
10
16
 
11
17
  def self.reset!
12
- @@connection_classes = {}
18
+ self.connection_classes = {}
13
19
  end
14
20
 
15
21
  # Establishes connection or return an existing one from cache
16
22
  def self.connect(connection_name, should_exist = true)
17
23
  connection_name = connection_name.to_s
18
- @@connection_classes[connection_name] ||= establish_connection(connection_name, should_exist)
24
+ connection_classes[connection_name] ||= establish_connection(connection_name, should_exist)
19
25
  end
20
26
 
21
27
  # Establishes connection or return an existing one from cache (not using AR database configs)
22
28
  def self.connect_to_db(connection_name, config)
23
29
  connection_name = connection_name.to_s
24
- @@connection_classes[connection_name] ||= establish_connection_to_db(connection_name, config)
30
+ connection_classes[connection_name] ||= establish_connection_to_db(connection_name, config)
25
31
  end
26
32
 
27
33
  # Establish connection with a specified name
@@ -70,7 +76,9 @@ module DbCharmer
70
76
 
71
77
  # Generates unique names for our abstract AR classes
72
78
  def self.abstract_connection_class_name(connection_name)
73
- "::AutoGeneratedAbstractConnectionClass#{connection_name.to_s.gsub(/\W+/, '_').camelize}"
79
+ conn_name_klass = connection_name.to_s.gsub(/\W+/, '_').camelize
80
+ thread = Thread.current.object_id.abs # need to make sure it is non-negative
81
+ "::AutoGeneratedAbstractConnectionClass#{conn_name_klass}ForThread#{thread}"
74
82
  end
75
83
  end
76
84
  end
@@ -20,8 +20,37 @@ module DbCharmer
20
20
  self
21
21
  end
22
22
 
23
+ def db_charmer_retrieve_connection
24
+ @abstract_connection_class.retrieve_connection
25
+ end
26
+
27
+ def nil?
28
+ false
29
+ end
30
+
31
+ #-----------------------------------------------------------------------------------------------
32
+ RESPOND_TO_METHODS = [
33
+ :abstract_connection_class,
34
+ :db_charmer_connection_name,
35
+ :db_charmer_connection_proxy,
36
+ :db_charmer_retrieve_connection,
37
+ :nil?
38
+ ].freeze
39
+
40
+ # Short-circuit some of the methods for which we know there is a separate check in coercion code
41
+ DOESNT_RESPOND_TO_METHODS = [
42
+ :set_real_connection
43
+ ].freeze
44
+
45
+ def respond_to?(method_name, include_all = false)
46
+ return true if RESPOND_TO_METHODS.include?(method_name)
47
+ return false if DOESNT_RESPOND_TO_METHODS.include?(method_name)
48
+ db_charmer_retrieve_connection.respond_to?(method_name, include_all)
49
+ end
50
+
51
+ #-----------------------------------------------------------------------------------------------
23
52
  def method_missing(meth, *args, &block)
24
- @abstract_connection_class.retrieve_connection.send(meth, *args, &block)
53
+ db_charmer_retrieve_connection.send(meth, *args, &block)
25
54
  end
26
55
  end
27
56
  end
@@ -1,12 +1,25 @@
1
1
  module DbCharmer
2
- @@current_controller = nil
3
- mattr_accessor :current_controller
2
+ def self.current_controller
3
+ Thread.current[:db_charmer_current_controller]
4
+ end
5
+
6
+ def self.current_controller=(val)
7
+ Thread.current[:db_charmer_current_controller] = val
8
+ end
9
+
10
+ #-------------------------------------------------------------------------------------------------
11
+ def self.forced_slave_reads_setting
12
+ Thread.current[:db_charmer_forced_slave_reads]
13
+ end
4
14
 
5
- @@forced_slave_reads = false
15
+ def self.forced_slave_reads_setting=(val)
16
+ Thread.current[:db_charmer_forced_slave_reads] = val
17
+ end
6
18
 
19
+ #-------------------------------------------------------------------------------------------------
7
20
  def self.force_slave_reads?
8
21
  # If global force slave reads is requested, do it
9
- return @@forced_slave_reads if @@forced_slave_reads
22
+ return true if Thread.current[:db_charmer_forced_slave_reads]
10
23
 
11
24
  # If not, try to use current controller to decide on this
12
25
  return false unless current_controller.respond_to?(:force_slave_reads?)
@@ -16,6 +29,7 @@ module DbCharmer
16
29
  return slave_reads
17
30
  end
18
31
 
32
+ #-------------------------------------------------------------------------------------------------
19
33
  def self.with_controller(controller)
20
34
  raise ArgumentError, "No block given" unless block_given?
21
35
  logger.debug("Setting current controller for db_charmer: #{controller.class.name}")
@@ -26,11 +40,16 @@ module DbCharmer
26
40
  self.current_controller = nil
27
41
  end
28
42
 
43
+ #-------------------------------------------------------------------------------------------------
44
+ # Force all reads in a block of code to go to a slave
29
45
  def self.force_slave_reads
30
46
  raise ArgumentError, "No block given" unless block_given?
31
- @@forced_slave_reads = true
32
- yield
33
- ensure
34
- @@forced_slave_reads = false
47
+ old_forced_slave_reads = self.forced_slave_reads_setting
48
+ begin
49
+ self.forced_slave_reads_setting = true
50
+ yield
51
+ ensure
52
+ self.forced_slave_reads_setting = old_forced_slave_reads
53
+ end
35
54
  end
36
55
  end