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
data/CHANGES CHANGED
@@ -1,3 +1,15 @@
1
+ 1.7.0 (not released yet):
2
+
3
+ Beta feature: Added force_slave_reads functionality. Now we could have models with slaves
4
+ that are not used by default, but could be turned on globally (per-controller or per-action).
5
+
6
+ Heavily reorganized the source code to match Rails code structure (class names, etc). This should
7
+ make it much easier for other contributors to work with the code.
8
+ Added smarter environment detection (using Rails.env, RAILS_ENV or RACK_ENV).
9
+
10
+ Bugfixes: Fix for N+1 queries when accessing shard_info for db_block_group_map sharding method.
11
+
12
+ ----------------------------------------------------------------------------------------
1
13
  1.6.17-19 (2011-04-25):
2
14
 
3
15
  Bugfixes: Do not touch database for sharded models until we really need to. Before 1.6.17
@@ -13,10 +25,10 @@ being used with db-charmer gem.
13
25
  ----------------------------------------------------------------------------------------
14
26
  1.6.13 (2010-08-17):
15
27
 
16
- Starting with this version we use Rails.env instead of RAILS_ENV to auto-detect rails
28
+ Starting with this version we use Rails.env instead of RAILS_ENV to auto-detect rails
17
29
  environment. If you use DbCharmer in non-rails project, please set DbCharmer.env manually.
18
30
 
19
- Bugfixes: Thanks to Eric Lindvall we now allow connection names that have symbols ruby
31
+ Bugfixes: Thanks to Eric Lindvall we now allow connection names that have symbols ruby
20
32
  wouldn't like for class names.
21
33
 
22
34
  ----------------------------------------------------------------------------------------
@@ -28,7 +28,7 @@ To install db-charmer as a Rails plugin use this:
28
28
  script/plugin install git://github.com/kovyrin/db-charmer.git
29
29
 
30
30
  _Notice_: If you use db-charmer in a non-rails project, you may need to set <tt>DbCharmer.env</tt> to a correct value
31
- before using any of its connection management methods. Correct value here is a valid <tt>database.yml</tt>
31
+ before using any of its connection management methods. Correct value here is a valid <tt>database.yml</tt>
32
32
  first-level section name.
33
33
 
34
34
 
@@ -52,6 +52,21 @@ Sample code:
52
52
  Foo.switch_connection_to(Baz.connection)
53
53
  Foo.switch_connection_to(nil)
54
54
 
55
+ Sample <tt>database.yml</tt> configuration:
56
+
57
+ production:
58
+ blah:
59
+ adapter: mysql
60
+ username: blah
61
+ host: blah.local
62
+ database: blah
63
+
64
+ foo:
65
+ adapter: mysql
66
+ username: foo
67
+ host: foo.local
68
+ database: foo
69
+
55
70
  The +switch_connection_to+ method has an optional second parameter +should_exist+ which is true
56
71
  by default. This parameter is used when the method is called with a string or a symbol connection
57
72
  name and there is no such connection configuration in the database.yml file. If this parameter
@@ -243,6 +258,65 @@ This behaviour is controlled by the <tt>DbCharmer.connections_should_exist</tt>
243
258
  configuration attribute which could be set from a rails initializer.
244
259
 
245
260
 
261
+ === Forced Slave Reads
262
+
263
+ In some cases we could have models that are too important to be used in default "send all
264
+ reads to the slave" mode, but we still would like to be able to switch them to this mode
265
+ sometimes. For example, you could have +User+ model, which you would like to keep from
266
+ lagging with your slaves because users do not like to see outdated information about their
267
+ accounts. But in some cases (like logged-out profile page views, etc) it would be perfectly
268
+ reasonable to switch all reads to the slave.
269
+
270
+ For this use-case starting with +DbCharmer+ release 1.7.0 we have a feature called forced
271
+ slave reads. It consists of a few separate small features that together make it really
272
+ powerful:
273
+
274
+ 1) <tt>:force_slave_reads => false</tt> option for +ActiveRecord+'s <tt>db_magic</tt> method.
275
+ This option could be used to disable automated slave reads on your models so that you could
276
+ call <tt>on_slave</tt> or use other methods to enable slave reads when you need it. Example:
277
+
278
+ class User < ActiveRecord::Base
279
+ db_magic :slave => slave01, :forced_slave_reads => false
280
+ end
281
+
282
+ 2) <tt>force_slave_reads</tt> +ActionController+ class method. This method could be used to
283
+ enable per-controller (when called with no arguments), or per-action (<tt>:only</tt> and
284
+ <tt>:except</tt> params) forced reads from slaves. This is really useful for actions in
285
+ which you know you could tolerate some slave lag so all your models with slaves defined will
286
+ send their reads to slaves. Example:
287
+
288
+ class ProfilesController < Application
289
+ force_slave_reads :except => [ :login, :logout ]
290
+ ...
291
+ end
292
+
293
+ 3) <tt>force_slave_reads!</tt> +ActionController+ instance method, that could be used within
294
+ your actions or in controller filters to temporarily switch your models to forced slave reads
295
+ mode. This could be useful for cases when the same actions could be called by logged-in and
296
+ anonymous users. Then you could authorize users in <tt>before_filter</tt> and call
297
+ <tt>force_slave_reads!</tt> method for anonymous page views.
298
+
299
+ class ProfilesController < Application
300
+ before_filter do
301
+ force_slave_reads! unless current_user
302
+ end
303
+ ...
304
+ end
305
+
306
+ 4) <tt>DbCharmer.force_slave_reads</tt> method that could be used with a block of ruby code
307
+ and would enable forced slave reads mode until the end of the block execution. This is really
308
+ powerful feature allowing high granularity in your control of forced slave reads mode. Example:
309
+
310
+ DbCharmer.force_slave_reads do
311
+ ...
312
+ total_users = User.count
313
+ ...
314
+ end
315
+
316
+ Notice: At this point the feature considered beta and should be used with caution. It is fully covered
317
+ with tests, but there still could be unexpected issues when used in real-world applications.
318
+
319
+
246
320
  === Associations Connection Management
247
321
 
248
322
  ActiveRecord models can have an associations with each other and since every model has its
@@ -492,12 +566,23 @@ most of the parts of plugin's code.
492
566
 
493
567
  == What Ruby and Rails implementations does it work for?
494
568
 
495
- We have a continuous integration setups for this plugin on MRI 1.8.6 with Rails 2.2 and 2.3.
496
- We use the plugin in production on Scribd.com with MRI (rubyee) 1.8.6 and Rails 2.2.
569
+ We have a continuous integration setups for this plugin on MRI 1.8.7 with Rails 2.2 and 2.3.
570
+ We use the plugin in production on Scribd.com with MRI (rubyee) 1.8.7 and Rails 2.2, Rails 2.3,
571
+ Sinatra and plain Rack applications.
497
572
 
498
573
 
499
574
  == Who are the authors?
500
575
 
501
576
  This plugin has been created in Scribd.com for our internal use and then the sources were opened for
502
- other people to use. All the code in this package has been developed by Alexey Kovyrin for Scribd.com
577
+ other people to use. Most of the code in this package has been developed by Alexey Kovyrin for Scribd.com
503
578
  and is released under the MIT license. For more details, see the LICENSE file.
579
+
580
+ Other contributors who have helped with the development of this library are (alphabetically ordered):
581
+ * Allen Madsen
582
+ * Andrew Geweke
583
+ * Ashley Martens
584
+ * Dmytro Shteflyuk
585
+ * Eric Lindvall
586
+ * Gregory Man
587
+ * Michael Birk
588
+ * Tyler McMullen
data/Rakefile CHANGED
@@ -1,5 +1,7 @@
1
1
  begin
2
2
  require 'jeweler'
3
+ require './lib/db_charmer/version.rb'
4
+
3
5
  Jeweler::Tasks.new do |gemspec|
4
6
  gemspec.name = 'db-charmer'
5
7
  gemspec.summary = 'ActiveRecord Connections Magic'
@@ -8,6 +10,8 @@ begin
8
10
  gemspec.homepage = 'http://github.com/kovyrin/db-charmer'
9
11
  gemspec.authors = ['Alexey Kovyrin']
10
12
 
13
+ gemspec.version = DbCharmer::Version::STRING
14
+
11
15
  gemspec.add_dependency('rails', '~> 2.2')
12
16
  gemspec.add_dependency('blankslate', '>= 0')
13
17
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{db-charmer}
8
- s.version = "1.6.19"
8
+ s.version = "1.7.0.pre1"
9
9
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Alexey Kovyrin"]
12
- s.date = %q{2011-04-25}
12
+ s.date = %q{2011-05-16}
13
13
  s.description = %q{ActiveRecord Connections Magic (slaves, multiple connections, etc)}
14
14
  s.email = %q{alexey@kovyrin.net}
15
15
  s.extra_rdoc_files = [
@@ -23,22 +23,24 @@ Gem::Specification.new do |s|
23
23
  "Makefile",
24
24
  "README.rdoc",
25
25
  "Rakefile",
26
- "VERSION",
27
26
  "db-charmer.gemspec",
28
27
  "init.rb",
29
28
  "lib/db_charmer.rb",
30
- "lib/db_charmer/abstract_adapter_extensions.rb",
31
- "lib/db_charmer/active_record_extensions.rb",
32
- "lib/db_charmer/association_preload.rb",
29
+ "lib/db_charmer/abstract_adapter/log_formatting.rb",
30
+ "lib/db_charmer/action_controller/force_slave_reads.rb",
31
+ "lib/db_charmer/active_record/association_preload.rb",
32
+ "lib/db_charmer/active_record/class_attributes.rb",
33
+ "lib/db_charmer/active_record/connection_switching.rb",
34
+ "lib/db_charmer/active_record/db_magic.rb",
35
+ "lib/db_charmer/active_record/finder_overrides.rb",
36
+ "lib/db_charmer/active_record/migration/multi_db_migrations.rb",
37
+ "lib/db_charmer/active_record/multi_db_proxy.rb",
38
+ "lib/db_charmer/active_record/named_scope/scope_proxy.rb",
39
+ "lib/db_charmer/active_record/sharding.rb",
33
40
  "lib/db_charmer/connection_factory.rb",
34
41
  "lib/db_charmer/connection_proxy.rb",
35
- "lib/db_charmer/connection_switch.rb",
36
42
  "lib/db_charmer/core_extensions.rb",
37
- "lib/db_charmer/db_magic.rb",
38
- "lib/db_charmer/finder_overrides.rb",
39
- "lib/db_charmer/multi_db_migrations.rb",
40
- "lib/db_charmer/multi_db_proxy.rb",
41
- "lib/db_charmer/scope_proxy.rb",
43
+ "lib/db_charmer/force_slave_reads.rb",
42
44
  "lib/db_charmer/sharding.rb",
43
45
  "lib/db_charmer/sharding/connection.rb",
44
46
  "lib/db_charmer/sharding/method/db_block_group_map.rb",
@@ -46,16 +48,16 @@ Gem::Specification.new do |s|
46
48
  "lib/db_charmer/sharding/method/hash_map.rb",
47
49
  "lib/db_charmer/sharding/method/range.rb",
48
50
  "lib/db_charmer/sharding/stub_connection.rb",
51
+ "lib/db_charmer/version.rb",
49
52
  "lib/tasks/databases.rake"
50
53
  ]
51
54
  s.homepage = %q{http://github.com/kovyrin/db-charmer}
52
55
  s.rdoc_options = ["--charset=UTF-8"]
53
56
  s.require_paths = ["lib"]
54
- s.rubygems_version = %q{1.3.7}
57
+ s.rubygems_version = %q{1.6.2}
55
58
  s.summary = %q{ActiveRecord Connections Magic}
56
59
 
57
60
  if s.respond_to? :specification_version then
58
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
59
61
  s.specification_version = 3
60
62
 
61
63
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
@@ -2,7 +2,16 @@ module DbCharmer
2
2
  @@connections_should_exist = true
3
3
  mattr_accessor :connections_should_exist
4
4
 
5
- @@env = defined?(Rails) ? Rails.env : 'development'
5
+ # Try to detect current environment or use development by default
6
+ if defined?(Rails)
7
+ @@env = Rails.env
8
+ elsif ENV['RAILS_ENV']
9
+ @@env = ENV['RAILS_ENV']
10
+ elsif ENV['RACK_ENV']
11
+ @@env = ENV['RACK_ENV']
12
+ else
13
+ @@env = 'development'
14
+ end
6
15
  mattr_accessor :env
7
16
 
8
17
  def self.connections_should_exist?
@@ -15,16 +24,16 @@ module DbCharmer
15
24
  end
16
25
 
17
26
  def self.with_remapped_databases(mappings, &proc)
18
- old_mappings = ActiveRecord::Base.db_charmer_database_remappings
27
+ old_mappings = ::ActiveRecord::Base.db_charmer_database_remappings
19
28
  begin
20
- ActiveRecord::Base.db_charmer_database_remappings = mappings
29
+ ::ActiveRecord::Base.db_charmer_database_remappings = mappings
21
30
  if mappings[:master] || mappings['master']
22
31
  with_all_hijacked(&proc)
23
32
  else
24
33
  proc.call
25
34
  end
26
35
  ensure
27
- ActiveRecord::Base.db_charmer_database_remappings = old_mappings
36
+ ::ActiveRecord::Base.db_charmer_database_remappings = old_mappings
28
37
  end
29
38
  end
30
39
 
@@ -39,7 +48,7 @@ private
39
48
  old_hijack_new_classes = @@hijack_new_classes
40
49
  begin
41
50
  @@hijack_new_classes = true
42
- ActiveRecord::Base.send(:subclasses).each do |subclass|
51
+ ::ActiveRecord::Base.send(:subclasses).each do |subclass|
43
52
  subclass.hijack_connection!
44
53
  end
45
54
  yield
@@ -49,45 +58,32 @@ private
49
58
  end
50
59
  end
51
60
 
52
- # These methods are added to all objects so we could call proxy? on anything
53
- # and figure if an object is a proxy w/o hitting method_missing or respond_to?
54
- class Object
55
- def self.proxy?
56
- false
57
- end
58
-
59
- def proxy?
60
- false
61
- end
62
- end
63
-
64
61
  # We need blankslate for all the proxies we have
65
62
  require 'blankslate'
66
63
 
67
- require 'db_charmer/active_record_extensions'
68
- require 'db_charmer/abstract_adapter_extensions'
64
+ # Add useful methods to global object
69
65
  require 'db_charmer/core_extensions'
70
66
 
71
67
  require 'db_charmer/connection_factory'
72
68
  require 'db_charmer/connection_proxy'
73
- require 'db_charmer/connection_switch'
74
- require 'db_charmer/scope_proxy'
75
- require 'db_charmer/multi_db_proxy'
69
+ require 'db_charmer/force_slave_reads'
76
70
 
77
- # Enable misc AR extensions
78
- ActiveRecord::Base.extend(DbCharmer::ActiveRecordExtensions::ClassMethods)
79
- ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, DbCharmer::AbstractAdapterExtensions::InstanceMethods)
71
+ # Add our custom class-level attributes to AR models
72
+ ActiveRecord::Base.extend(DbCharmer::ActiveRecord::ClassAttributes)
80
73
 
81
74
  # Enable connections switching in AR
82
- ActiveRecord::Base.extend(DbCharmer::ConnectionSwitch::ClassMethods)
75
+ ActiveRecord::Base.extend(DbCharmer::ActiveRecord::ConnectionSwitching)
76
+
77
+ # Enable misc AR extensions
78
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, DbCharmer::AbstractAdapter::LogFormatting)
83
79
 
84
80
  # Enable connection proxy in AR
85
- ActiveRecord::Base.extend(DbCharmer::MultiDbProxy::ClassMethods)
86
- ActiveRecord::Base.extend(DbCharmer::MultiDbProxy::MasterSlaveClassMethods)
87
- ActiveRecord::Base.send(:include, DbCharmer::MultiDbProxy::InstanceMethods)
81
+ ActiveRecord::Base.extend(DbCharmer::ActiveRecord::MultiDbProxy::ClassMethods)
82
+ ActiveRecord::Base.extend(DbCharmer::ActiveRecord::MultiDbProxy::MasterSlaveClassMethods)
83
+ ActiveRecord::Base.send(:include, DbCharmer::ActiveRecord::MultiDbProxy::InstanceMethods)
88
84
 
89
85
  # Enable connection proxy for scopes
90
- ActiveRecord::NamedScope::Scope.send(:include, DbCharmer::ScopeProxy::InstanceMethods)
86
+ ActiveRecord::NamedScope::Scope.send(:include, DbCharmer::ActiveRecord::NamedScope::ScopeProxy)
91
87
 
92
88
  # Enable connection proxy for associations
93
89
  # WARNING: Inject methods to association class right here (they proxy include calls somewhere else, so include does not work)
@@ -114,24 +110,14 @@ module ActiveRecord
114
110
  end
115
111
  end
116
112
 
117
- require 'db_charmer/db_magic'
118
- require 'db_charmer/finder_overrides'
119
- require 'db_charmer/association_preload'
120
- require 'db_charmer/multi_db_migrations'
121
- require 'db_charmer/multi_db_proxy'
122
-
123
- require 'db_charmer/sharding'
124
- require 'db_charmer/sharding/connection'
125
- require 'db_charmer/sharding/stub_connection'
126
-
127
113
  # Enable multi-db migrations
128
- ActiveRecord::Migration.extend(DbCharmer::MultiDbMigrations)
114
+ ActiveRecord::Migration.extend(DbCharmer::ActiveRecord::Migration::MultiDbMigrations)
129
115
 
130
116
  # Enable the magic
131
- ActiveRecord::Base.extend(DbCharmer::DbMagic::ClassMethods)
117
+ ActiveRecord::Base.extend(DbCharmer::ActiveRecord::DbMagic)
132
118
 
133
119
  # Setup association preload magic
134
- ActiveRecord::Base.extend(DbCharmer::AssociationPreload::ClassMethods)
120
+ ActiveRecord::Base.extend(DbCharmer::ActiveRecord::AssociationPreload)
135
121
 
136
122
  # Open up really useful API method
137
123
  ActiveRecord::AssociationPreload::ClassMethods.send(:public, :preload_associations)
@@ -147,3 +133,8 @@ class ActiveRecord::Base
147
133
  alias_method_chain :inherited, :hijacking
148
134
  end
149
135
  end
136
+
137
+ #-----------------------------------------------------------------------------------------------------------------------
138
+ # Extend ActionController to support forcing slave reads
139
+ ActionController::Base.extend(DbCharmer::ActionController::ForceSlaveReads::ClassMethods)
140
+ ActionController::Base.send(:include, DbCharmer::ActionController::ForceSlaveReads::InstanceMethods)
@@ -1,6 +1,7 @@
1
1
  module DbCharmer
2
- module AbstractAdapterExtensions
3
- module InstanceMethods
2
+ module AbstractAdapter
3
+ module LogFormatting
4
+
4
5
  def self.included(base)
5
6
  base.alias_method_chain :format_log_entry, :connection_name
6
7
  end
@@ -12,9 +13,10 @@ module DbCharmer
12
13
 
13
14
  def format_log_entry_with_connection_name(message, dump = nil)
14
15
  msg = connection_name ? "[#{connection_name}] " : ''
15
- msg = " \e[0;34;1m#{msg}\e[0m" if connection_name && ActiveRecord::Base.colorize_logging
16
+ msg = " \e[0;34;1m#{msg}\e[0m" if connection_name && ::ActiveRecord::Base.colorize_logging
16
17
  msg << format_log_entry_without_connection_name(message, dump)
17
18
  end
19
+
18
20
  end
19
21
  end
20
22
  end
@@ -0,0 +1,65 @@
1
+ module DbCharmer
2
+ module ActionController
3
+ module ForceSlaveReads
4
+
5
+ module ClassMethods
6
+ @@db_charmer_force_slave_reads_actions = {}
7
+ def force_slave_reads(params)
8
+ @@db_charmer_force_slave_reads_actions[self.name] = {
9
+ :except => params[:except] ? [*params[:except]].map(&:to_s) : [],
10
+ :only => params[:only] ? [*params[:only]].map(&:to_s) : []
11
+ }
12
+ end
13
+
14
+ def force_slave_reads_options
15
+ @@db_charmer_force_slave_reads_actions[self.name]
16
+ end
17
+
18
+ def force_slave_reads_action?(name = nil)
19
+ name = name.to_s
20
+
21
+ options = force_slave_reads_options
22
+ # If no options were defined for this controller, all actions are not forced to use slaves
23
+ return false unless options
24
+
25
+ # Actions where force_slave_reads mode was turned off
26
+ return false if options[:except].include?(name)
27
+
28
+ # Only for these actions force_slave_reads was turned on
29
+ return options[:only].include?(name) if options[:only].any?
30
+
31
+ # If :except is not empty, we're done with the checks and rest of the actions are should force slave reads
32
+ # Otherwise, all the actions are not in force_slave_reads mode
33
+ options[:except].any?
34
+ end
35
+ end
36
+
37
+ module InstanceMethods
38
+ def self.included(base)
39
+ base.alias_method_chain :perform_action, :forced_slave_reads
40
+ end
41
+
42
+ def force_slave_reads!
43
+ @db_charmer_force_slave_reads = true
44
+ end
45
+
46
+ def dont_force_slave_reads!
47
+ @db_charmer_force_slave_reads = false
48
+ end
49
+
50
+ def force_slave_reads?
51
+ @db_charmer_force_slave_reads || self.class.force_slave_reads_action?(params[:action])
52
+ end
53
+
54
+ protected
55
+
56
+ def perform_action_with_forced_slave_reads
57
+ DbCharmer.with_controller(self) do
58
+ perform_action_without_forced_slave_reads
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+ end