db-charmer 1.8.4 → 1.9.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.
@@ -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