db-charmer 1.6.19 → 1.7.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/CHANGES +14 -2
  2. data/README.rdoc +89 -4
  3. data/Rakefile +4 -0
  4. data/db-charmer.gemspec +17 -15
  5. data/lib/db_charmer.rb +34 -43
  6. data/lib/db_charmer/{abstract_adapter_extensions.rb → abstract_adapter/log_formatting.rb} +5 -3
  7. data/lib/db_charmer/action_controller/force_slave_reads.rb +65 -0
  8. data/lib/db_charmer/{association_preload.rb → active_record/association_preload.rb} +2 -2
  9. data/lib/db_charmer/{active_record_extensions.rb → active_record/class_attributes.rb} +19 -40
  10. data/lib/db_charmer/active_record/connection_switching.rb +77 -0
  11. data/lib/db_charmer/{db_magic.rb → active_record/db_magic.rb} +18 -8
  12. data/lib/db_charmer/active_record/finder_overrides.rb +61 -0
  13. data/lib/db_charmer/active_record/migration/multi_db_migrations.rb +71 -0
  14. data/lib/db_charmer/active_record/multi_db_proxy.rb +65 -0
  15. data/lib/db_charmer/active_record/named_scope/scope_proxy.rb +26 -0
  16. data/lib/db_charmer/active_record/sharding.rb +40 -0
  17. data/lib/db_charmer/connection_factory.rb +1 -1
  18. data/lib/db_charmer/core_extensions.rb +10 -0
  19. data/lib/db_charmer/force_slave_reads.rb +36 -0
  20. data/lib/db_charmer/sharding.rb +0 -36
  21. data/lib/db_charmer/sharding/method/db_block_group_map.rb +3 -3
  22. data/lib/db_charmer/sharding/method/db_block_map.rb +3 -3
  23. data/lib/db_charmer/sharding/stub_connection.rb +4 -4
  24. data/lib/db_charmer/version.rb +10 -0
  25. data/lib/tasks/databases.rake +6 -6
  26. metadata +28 -21
  27. data/VERSION +0 -1
  28. data/lib/db_charmer/connection_switch.rb +0 -40
  29. data/lib/db_charmer/finder_overrides.rb +0 -56
  30. data/lib/db_charmer/multi_db_migrations.rb +0 -67
  31. data/lib/db_charmer/multi_db_proxy.rb +0 -63
  32. data/lib/db_charmer/scope_proxy.rb +0 -22
@@ -1,6 +1,6 @@
1
1
  module DbCharmer
2
- module AssociationPreload
3
- module ClassMethods
2
+ module ActiveRecord
3
+ module AssociationPreload
4
4
  ASSOCIATION_TYPES = [ :has_one, :has_many, :belongs_to, :has_and_belongs_to_many ]
5
5
 
6
6
  def self.extended(base)
@@ -1,32 +1,6 @@
1
1
  module DbCharmer
2
- module ActiveRecordExtensions
3
- module ClassMethods
4
-
5
- def establish_real_connection_if_exists(name, should_exist = false)
6
- name = name.to_s
7
-
8
- # Check environment name
9
- config = configurations[DbCharmer.env]
10
- unless config
11
- error = "Invalid environment name (does not exist in database.yml): #{DbCharmer.env}. Please set correct Rails.env or DbCharmer.env."
12
- raise ArgumentError, error
13
- end
14
-
15
- # Check connection name
16
- config = config[name]
17
- unless config
18
- if should_exist
19
- raise ArgumentError, "Invalid connection name (does not exist in database.yml): #{DbCharmer.env}/#{name}"
20
- end
21
- return # No need to establish connection - they do not want us to
22
- end
23
-
24
- # Pass connection name with config
25
- config[:connection_name] = name
26
- establish_connection(config)
27
- end
28
-
29
- #-----------------------------------------------------------------------------
2
+ module ActiveRecord
3
+ module ClassAttributes
30
4
  @@db_charmer_opts = {}
31
5
  def db_charmer_opts=(opts)
32
6
  @@db_charmer_opts[self.name] = opts
@@ -71,6 +45,23 @@ module DbCharmer
71
45
  db_charmer_slaves[rand(db_charmer_slaves.size)]
72
46
  end
73
47
 
48
+ #-----------------------------------------------------------------------------
49
+ @@db_charmer_force_slave_reads = {}
50
+ def db_charmer_force_slave_reads=(force)
51
+ @@db_charmer_force_slave_reads[self.name] = force
52
+ end
53
+
54
+ def db_charmer_force_slave_reads
55
+ @@db_charmer_force_slave_reads[self.name]
56
+ end
57
+
58
+ # Slave reads are used in two cases:
59
+ # - per-model slave reads are enabled (see db_magic method for more details)
60
+ # - global slave reads enforcing is enabled (in a controller action)
61
+ def db_charmer_force_slave_reads?
62
+ db_charmer_force_slave_reads || DbCharmer.force_slave_reads?
63
+ end
64
+
74
65
  #-----------------------------------------------------------------------------
75
66
  @@db_charmer_connection_levels = Hash.new(0)
76
67
  def db_charmer_connection_level=(level)
@@ -105,18 +96,6 @@ module DbCharmer
105
96
  raise "Mappings must be nil or respond to []" if mappings && (! mappings.respond_to?(:[]))
106
97
  @@db_charmer_database_remappings = mappings || { }
107
98
  end
108
-
109
- #-----------------------------------------------------------------------------
110
- def hijack_connection!
111
- return if self.respond_to?(:connection_with_magic)
112
- class << self
113
- def connection_with_magic
114
- db_charmer_remapped_connection || db_charmer_connection_proxy || connection_without_magic
115
- end
116
- alias_method_chain :connection, :magic
117
- end
118
- end
119
-
120
99
  end
121
100
  end
122
101
  end
@@ -0,0 +1,77 @@
1
+ module DbCharmer
2
+ module ActiveRecord
3
+ module ConnectionSwitching
4
+ def establish_real_connection_if_exists(name, should_exist = false)
5
+ name = name.to_s
6
+
7
+ # Check environment name
8
+ config = configurations[DbCharmer.env]
9
+ unless config
10
+ error = "Invalid environment name (does not exist in database.yml): #{DbCharmer.env}. Please set correct Rails.env or DbCharmer.env."
11
+ raise ArgumentError, error
12
+ end
13
+
14
+ # Check connection name
15
+ config = config[name]
16
+ unless config
17
+ if should_exist
18
+ raise ArgumentError, "Invalid connection name (does not exist in database.yml): #{DbCharmer.env}/#{name}"
19
+ end
20
+ return # No need to establish connection - they do not want us to
21
+ end
22
+
23
+ # Pass connection name with config
24
+ config[:connection_name] = name
25
+ establish_connection(config)
26
+ end
27
+
28
+ #-----------------------------------------------------------------------------------------------------------------
29
+ def hijack_connection!
30
+ return if self.respond_to?(:connection_with_magic)
31
+ class << self
32
+ def connection_with_magic
33
+ db_charmer_remapped_connection || db_charmer_connection_proxy || connection_without_magic
34
+ end
35
+ alias_method_chain :connection, :magic
36
+ end
37
+ end
38
+
39
+ #-----------------------------------------------------------------------------------------------------------------
40
+ def coerce_to_connection_proxy(conn, should_exist = true)
41
+ return nil if conn.nil?
42
+
43
+ if conn.kind_of?(Symbol) || conn.kind_of?(String)
44
+ return DbCharmer::ConnectionFactory.connect(conn, should_exist)
45
+ end
46
+
47
+ if conn.kind_of?(Hash)
48
+ conn = conn.symbolize_keys
49
+ raise ArgumentError, "Missing required :connection_name parameter" unless conn[:connection_name]
50
+ return DbCharmer::ConnectionFactory.connect_to_db(conn[:connection_name], conn)
51
+ end
52
+
53
+ if conn.respond_to?(:db_charmer_connection_proxy)
54
+ return conn.db_charmer_connection_proxy
55
+ end
56
+
57
+ if conn.kind_of?(::ActiveRecord::ConnectionAdapters::AbstractAdapter) || conn.kind_of?(DbCharmer::Sharding::StubConnection)
58
+ return conn
59
+ end
60
+
61
+ raise "Unsupported connection type: #{conn.class}"
62
+ end
63
+
64
+ #-----------------------------------------------------------------------------------------------------------------
65
+ def switch_connection_to(conn, require_config_to_exist = true)
66
+ new_conn = coerce_to_connection_proxy(conn, require_config_to_exist)
67
+
68
+ if db_charmer_connection_proxy.is_a?(DbCharmer::Sharding::StubConnection)
69
+ db_charmer_connection_proxy.set_real_connection(new_conn)
70
+ end
71
+
72
+ self.db_charmer_connection_proxy = new_conn
73
+ self.hijack_connection!
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,6 +1,7 @@
1
1
  module DbCharmer
2
- module DbMagic
3
- module ClassMethods
2
+ module ActiveRecord
3
+ module DbMagic
4
+
4
5
  def db_magic(opt = {})
5
6
  # Make sure we could use our connections management here
6
7
  hijack_connection!
@@ -13,8 +14,14 @@ module DbCharmer
13
14
 
14
15
  # Set up slaves pool
15
16
  opt[:slaves] ||= []
17
+ opt[:slaves] = [ opt[:slaves] ].flatten
16
18
  opt[:slaves] << opt[:slave] if opt[:slave]
17
- setup_slaves_magic(opt[:slaves], should_exist) if opt[:slaves].any?
19
+
20
+ # Forced reads are enabled for all models by default, could be disabled by the user
21
+ forced_slave_reads = opt.has_key?(:force_slave_reads) ? opt[:force_slave_reads] : true
22
+
23
+ # Setup all the slaves related magic if needed
24
+ setup_slaves_magic(opt[:slaves], forced_slave_reads, should_exist) if opt[:slaves].any?
18
25
 
19
26
  # Setup inheritance magic
20
27
  setup_children_magic(opt)
@@ -39,7 +46,7 @@ module DbCharmer
39
46
 
40
47
  def setup_sharding_magic(config)
41
48
  # Add sharding-specific methods
42
- self.extend(DbCharmer::Sharding::ClassMethods)
49
+ self.extend(DbCharmer::ActiveRecord::Sharding)
43
50
 
44
51
  # Get configuration
45
52
  name = config[:sharded_connection] or raise ArgumentError, "No :sharded_connection!"
@@ -55,15 +62,18 @@ module DbCharmer
55
62
  self.db_charmer_default_connection = conn
56
63
  end
57
64
 
58
- def setup_slaves_magic(slaves, should_exist = true)
65
+ def setup_slaves_magic(slaves, force_slave_reads, should_exist = true)
59
66
  self.db_charmer_slaves = slaves.collect do |slave|
60
67
  coerce_to_connection_proxy(slave, should_exist)
61
68
  end
62
69
 
63
- self.extend(DbCharmer::FinderOverrides::ClassMethods)
64
- self.send(:include, DbCharmer::FinderOverrides::InstanceMethods)
65
- self.extend(DbCharmer::MultiDbProxy::MasterSlaveClassMethods)
70
+ self.db_charmer_force_slave_reads = force_slave_reads
71
+
72
+ self.extend(DbCharmer::ActiveRecord::FinderOverrides::ClassMethods)
73
+ self.send(:include, DbCharmer::ActiveRecord::FinderOverrides::InstanceMethods)
74
+ self.extend(DbCharmer::ActiveRecord::MultiDbProxy::MasterSlaveClassMethods)
66
75
  end
76
+
67
77
  end
68
78
  end
69
79
  end
@@ -0,0 +1,61 @@
1
+ module DbCharmer
2
+ module ActiveRecord
3
+ module FinderOverrides
4
+
5
+ module ClassMethods
6
+ SLAVE_METHODS = [ :find_by_sql, :count_by_sql, :calculate ]
7
+ MASTER_METHODS = [ :update, :create, :delete, :destroy, :delete_all, :destroy_all, :update_all, :update_counters ]
8
+
9
+ SLAVE_METHODS.each do |slave_method|
10
+ class_eval <<-EOF, __FILE__, __LINE__ + 1
11
+ def #{slave_method}(*args, &block)
12
+ first_level_on_slave do
13
+ super(*args, &block)
14
+ end
15
+ end
16
+ EOF
17
+ end
18
+
19
+ MASTER_METHODS.each do |master_method|
20
+ class_eval <<-EOF, __FILE__, __LINE__ + 1
21
+ def #{master_method}(*args, &block)
22
+ on_master do
23
+ super(*args, &block)
24
+ end
25
+ end
26
+ EOF
27
+ end
28
+
29
+ def find(*args, &block)
30
+ options = args.last
31
+ if options.is_a?(Hash) && options[:lock]
32
+ on_master { super(*args, &block) }
33
+ else
34
+ super(*args, &block)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def first_level_on_slave
41
+ first_level = db_charmer_top_level_connection? && on_master.connection.open_transactions.zero?
42
+ if first_level && db_charmer_force_slave_reads?
43
+ on_slave { yield }
44
+ else
45
+ yield
46
+ end
47
+ end
48
+
49
+ end
50
+
51
+ module InstanceMethods
52
+ def reload(*args, &block)
53
+ self.class.on_master do
54
+ super(*args, &block)
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,71 @@
1
+ module DbCharmer
2
+ module ActiveRecord
3
+ module Migration
4
+ module MultiDbMigrations
5
+
6
+ def self.extended(base)
7
+ class << base
8
+ alias_method_chain :migrate, :db_wrapper
9
+ end
10
+ end
11
+
12
+ @@multi_db_names = {}
13
+ def multi_db_names
14
+ @@multi_db_names[self.name] || @@multi_db_names['ActiveRecord::Migration']
15
+ end
16
+
17
+ def multi_db_names=(names)
18
+ @@multi_db_names[self.name] = names
19
+ end
20
+
21
+ def migrate_with_db_wrapper(direction)
22
+ if names = multi_db_names
23
+ names.each do |multi_db_name|
24
+ on_db(multi_db_name) do
25
+ migrate_without_db_wrapper(direction)
26
+ end
27
+ end
28
+ else
29
+ migrate_without_db_wrapper(direction)
30
+ end
31
+ end
32
+
33
+ def on_db(db_name)
34
+ name = db_name.is_a?(Hash) ? db_name[:connection_name] : db_name.inspect
35
+ announce "Switching connection to #{name}"
36
+ # Switch connection
37
+ old_proxy = ::ActiveRecord::Base.db_charmer_connection_proxy
38
+ db_name = nil if db_name == :default
39
+ ::ActiveRecord::Base.switch_connection_to(db_name, DbCharmer.connections_should_exist?)
40
+ # Yield the block
41
+ yield
42
+ ensure
43
+ # Switch it back
44
+ ::ActiveRecord::Base.verify_active_connections!
45
+ announce "Switching connection back"
46
+ ::ActiveRecord::Base.switch_connection_to(old_proxy)
47
+ end
48
+
49
+ def db_magic(opts = {})
50
+ # Collect connections from all possible options
51
+ conns = [ opts[:connection], opts[:connections] ]
52
+ conns << shard_connections(opts[:sharded_connection]) if opts[:sharded_connection]
53
+
54
+ # Get a unique set of connections
55
+ conns = conns.flatten.compact.uniq
56
+ raise ArgumentError, "No connection name - no magic!" unless conns.any?
57
+
58
+ # Save connections
59
+ self.multi_db_names = conns
60
+ end
61
+
62
+ # Return a list of connections to shards in a sharded connection
63
+ def shard_connections(conn_name)
64
+ conn = DbCharmer::Sharding.sharded_connection(conn_name)
65
+ conn.shard_connections
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,65 @@
1
+ module DbCharmer
2
+ module ActiveRecord
3
+ module MultiDbProxy
4
+ # Simple proxy class that switches connections and then proxies all the calls
5
+ # This class is used to implement chained on_db calls
6
+ class OnDbProxy < BlankSlate
7
+ def initialize(proxy_target, slave)
8
+ @proxy_target = proxy_target
9
+ @slave = slave
10
+ end
11
+
12
+ private
13
+
14
+ def method_missing(meth, *args, &block)
15
+ # Switch connection and proxy the method call
16
+ @proxy_target.on_db(@slave) do |proxy_target|
17
+ res = proxy_target.__send__(meth, *args, &block)
18
+
19
+ # If result is a scope/association, return a new proxy for it, otherwise return the result itself
20
+ (res.proxy?) ? OnDbProxy.new(res, @slave) : res
21
+ end
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ def on_db(con, proxy_target = nil)
27
+ proxy_target ||= self
28
+
29
+ # Chain call
30
+ return OnDbProxy.new(proxy_target, con) unless block_given?
31
+
32
+ # Block call
33
+ begin
34
+ self.db_charmer_connection_level += 1
35
+ old_proxy = db_charmer_connection_proxy
36
+ switch_connection_to(con, DbCharmer.connections_should_exist?)
37
+ yield(proxy_target)
38
+ ensure
39
+ switch_connection_to(old_proxy)
40
+ self.db_charmer_connection_level -= 1
41
+ end
42
+ end
43
+ end
44
+
45
+ module InstanceMethods
46
+ def on_db(con, proxy_target = nil, &block)
47
+ proxy_target ||= self
48
+ self.class.on_db(con, proxy_target, &block)
49
+ end
50
+ end
51
+
52
+ module MasterSlaveClassMethods
53
+ def on_slave(con = nil, proxy_target = nil, &block)
54
+ con ||= db_charmer_random_slave
55
+ raise ArgumentError, "No slaves found in the class and no slave connection given" unless con
56
+ on_db(con, proxy_target, &block)
57
+ end
58
+
59
+ def on_master(proxy_target = nil, &block)
60
+ on_db(db_charmer_default_connection, proxy_target, &block)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,26 @@
1
+ module DbCharmer
2
+ module ActiveRecord
3
+ module NamedScope
4
+ module ScopeProxy
5
+
6
+ def proxy?
7
+ true
8
+ end
9
+
10
+ def on_db(con, proxy_target = nil, &block)
11
+ proxy_target ||= self
12
+ proxy_scope.on_db(con, proxy_target, &block)
13
+ end
14
+
15
+ def on_slave(con = nil, &block)
16
+ proxy_scope.on_slave(con, self, &block)
17
+ end
18
+
19
+ def on_master(&block)
20
+ proxy_scope.on_master(self, &block)
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+ end