db-charmer 1.6.19 → 1.7.0.pre1

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