Repository: kovyrin/db-charmer Branch: master Commit: 0c71563472ed Files: 160 Total size: 297.8 KB Directory structure: gitextract_9iqagwm9/ ├── .gitignore ├── .travis.yml ├── CHANGES ├── LICENSE ├── Makefile ├── README.rdoc ├── Rakefile ├── ci_build ├── db-charmer.gemspec ├── init.rb ├── issues/ │ └── issues-as-of-2014-11-14.json ├── lib/ │ ├── db_charmer/ │ │ ├── action_controller/ │ │ │ └── force_slave_reads.rb │ │ ├── active_record/ │ │ │ ├── association_preload.rb │ │ │ ├── class_attributes.rb │ │ │ ├── connection_switching.rb │ │ │ ├── db_magic.rb │ │ │ ├── migration/ │ │ │ │ └── multi_db_migrations.rb │ │ │ ├── multi_db_proxy.rb │ │ │ └── sharding.rb │ │ ├── connection_factory.rb │ │ ├── connection_proxy.rb │ │ ├── core_extensions.rb │ │ ├── force_slave_reads.rb │ │ ├── rails2/ │ │ │ ├── abstract_adapter/ │ │ │ │ └── log_formatting.rb │ │ │ └── active_record/ │ │ │ ├── master_slave_routing.rb │ │ │ └── named_scope/ │ │ │ └── scope_proxy.rb │ │ ├── rails3/ │ │ │ ├── abstract_adapter/ │ │ │ │ └── connection_name.rb │ │ │ └── active_record/ │ │ │ ├── log_subscriber.rb │ │ │ ├── master_slave_routing.rb │ │ │ ├── relation/ │ │ │ │ └── connection_routing.rb │ │ │ └── relation_method.rb │ │ ├── rails31/ │ │ │ └── active_record/ │ │ │ ├── migration/ │ │ │ │ └── command_recorder.rb │ │ │ └── preloader/ │ │ │ ├── association.rb │ │ │ └── has_and_belongs_to_many.rb │ │ ├── railtie.rb │ │ ├── sharding/ │ │ │ ├── connection.rb │ │ │ ├── method/ │ │ │ │ ├── db_block_group_map.rb │ │ │ │ ├── db_block_map.rb │ │ │ │ ├── hash_map.rb │ │ │ │ └── range.rb │ │ │ ├── method.rb │ │ │ └── stub_connection.rb │ │ ├── sharding.rb │ │ ├── tasks/ │ │ │ └── databases.rake │ │ ├── version.rb │ │ └── with_remapped_databases.rb │ └── db_charmer.rb ├── test-project/ │ ├── .gitignore │ ├── .rspec │ ├── Gemfile │ ├── Rakefile │ ├── TODO │ ├── app/ │ │ ├── controllers/ │ │ │ ├── application_controller.rb │ │ │ └── posts_controller.rb │ │ ├── helpers/ │ │ │ └── application_helper.rb │ │ ├── models/ │ │ │ ├── avatar.rb │ │ │ ├── car.rb │ │ │ ├── categories_posts.rb │ │ │ ├── category.rb │ │ │ ├── comment.rb │ │ │ ├── event.rb │ │ │ ├── ford.rb │ │ │ ├── house.rb │ │ │ ├── log_record.rb │ │ │ ├── post.rb │ │ │ ├── range_sharded_model.rb │ │ │ ├── toyota.rb │ │ │ └── user.rb │ │ └── views/ │ │ ├── layouts/ │ │ │ └── application.html.erb │ │ └── posts/ │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── config/ │ │ ├── application.rb │ │ ├── boot.rb │ │ ├── database.yml.example │ │ ├── environment.rb │ │ ├── environments/ │ │ │ └── test.rb │ │ ├── initializers/ │ │ │ ├── backtrace_silencers.rb │ │ │ ├── db_charmer.rb │ │ │ ├── secret_token.rb │ │ │ ├── session_store.rb │ │ │ └── sharding.rb │ │ ├── locales/ │ │ │ └── en.yml │ │ └── routes.rb │ ├── db/ │ │ ├── create_databases.sql │ │ ├── migrate/ │ │ │ ├── 20090810013829_create_log_records.rb │ │ │ ├── 20090810013922_create_posts.rb │ │ │ ├── 20090810221944_create_users.rb │ │ │ ├── 20100305234245_create_categories.rb │ │ │ ├── 20100305234340_create_categories_posts.rb │ │ │ ├── 20100305235831_create_avatars.rb │ │ │ ├── 20100328201317_create_sharding_map_tables.rb │ │ │ ├── 20100330180517_create_event_tables.rb │ │ │ ├── 20100817191548_create_cars.rb │ │ │ └── 20111005193941_create_comments.rb │ │ ├── seeds.rb │ │ └── sharding.sql │ └── spec/ │ ├── controllers/ │ │ └── posts_controller_spec.rb │ ├── fixtures/ │ │ ├── avatars.yml │ │ ├── categories.yml │ │ ├── categories_posts.yml │ │ ├── comments.yml │ │ ├── event_shards_info.yml │ │ ├── event_shards_map.yml │ │ ├── log_records.yml │ │ ├── posts.yml │ │ └── users.yml │ ├── integration/ │ │ └── multi_threading_spec.rb │ ├── models/ │ │ ├── avatar_spec.rb │ │ ├── cars_spec.rb │ │ ├── categories_posts_spec.rb │ │ ├── category_spec.rb │ │ ├── comment_spec.rb │ │ ├── event_spec.rb │ │ ├── log_record_spec.rb │ │ ├── post_spec.rb │ │ ├── range_sharded_model_spec.rb │ │ └── user_spec.rb │ ├── sharding/ │ │ ├── connection_spec.rb │ │ ├── method/ │ │ │ ├── db_block_map_spec.rb │ │ │ ├── hash_map_spec.rb │ │ │ └── range_spec.rb │ │ └── sharding_spec.rb │ ├── spec_helper.rb │ ├── support/ │ │ └── rails31_stub_connection.rb │ └── unit/ │ ├── abstract_adapter/ │ │ └── log_formatting_spec.rb │ ├── action_controller/ │ │ └── force_slave_reads_spec.rb │ ├── active_record/ │ │ ├── association_preload_spec.rb │ │ ├── association_proxy_spec.rb │ │ ├── class_attributes_spec.rb │ │ ├── connection_switching_spec.rb │ │ ├── db_magic_spec.rb │ │ ├── master_slave_routing_spec.rb │ │ ├── migration/ │ │ │ └── multi_db_migrations_spec.rb │ │ ├── named_scope/ │ │ │ └── named_scope_spec.rb │ │ └── relation_spec.rb │ ├── connection_factory_spec.rb │ ├── connection_proxy_spec.rb │ ├── db_charmer_spec.rb │ ├── multi_db_proxy_spec.rb │ └── with_remapped_databases_spec.rb └── test-project-2.x/ ├── Gemfile ├── Rakefile ├── config/ │ ├── boot.rb │ ├── database.yml.example │ ├── environment.rb │ ├── environments/ │ │ └── test.rb │ ├── initializers/ │ │ ├── backtrace_silencers.rb │ │ ├── db_charmer.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── new_rails_defaults.rb │ │ ├── session_store.rb │ │ └── sharding.rb │ ├── locales/ │ │ └── en.yml │ ├── preinitializer.rb │ └── routes.rb ├── script/ │ └── console └── spec/ ├── spec.opts └── spec_helper.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ doc pkg .DS_Store _site .idea ================================================ FILE: .travis.yml ================================================ language: ruby rvm: - 1.8.7 - 1.9.3 - 2.0.0 env: - RAILS_VERSION=2.x - RAILS_VERSION=3.0.20 - RAILS_VERSION=3.1.12 - RAILS_VERSION=3.2.3 - RAILS_VERSION=3.2.15 - RAILS_VERSION=3.2.15 DB_CHARMER_GEM=1.9.0 notifications: recipients: - alexey@kovyrin.net script: ./ci_build # Whitelist branches to test branches: only: - master - rails4 # Build matrix configuration matrix: exclude: # Do not run Rails 2.x tests on ruby 1.9 - rvm: 1.9.3 env: RAILS_VERSION=2.x # Do not run Rails 2.x tests on ruby 2.0 - rvm: 2.0.0 env: RAILS_VERSION=2.x # Do not run Rails 3.0 tests on ruby 2.0 - rvm: 2.0.0 env: RAILS_VERSION=3.0.20 # Do not run Rails 3.1 tests on ruby 2.0 - rvm: 2.0.0 env: RAILS_VERSION=3.1.12 # Do not run early Rails 3.2 tests on ruby 2.0 - rvm: 2.0.0 env: RAILS_VERSION=3.2.3 ================================================ FILE: CHANGES ================================================ 1.9.1 (2014-11-14): The project has been suspended. No updates will be provided and no Rails versions beyond 3.2.x will be supported. For more information please check out this blog post: http://kovyrin.net/2014/11/14/dbcharmer-suspended/ ---------------------------------------------------------------------------------------- 1.9.0 (2013-10-09): Most of the major changes in this version are related to our initial push towards making DbCharmer thread-safe and making sure it works correctly in multi-threaded environments. Please note, that even though we now test DbCharmer in multi-threaded environments, we still consider multi-threaded support experimental. Bug fix: Improved Rails environment detection (sometimes DbCharmer would use Rails-specific code while running in non-rails projects). Bug fix: Make sure on_db() method could restore original connection after an exception is raised from a DB driver during connection switching (Thanks to Dmytro Shteflyuk for finding the issue and helping with debugging). This is the first release that does not have a really strict constraint for point-releases within the Rails 3.2.x branch. ---------------------------------------------------------------------------------------- 1.8.4 (2013-03-18): Bumped up rails dependencies up to 3.2.13. ---------------------------------------------------------------------------------------- 1.8.3 (2013-02-11): Bumped up rails dependencies up to 3.2.12. ---------------------------------------------------------------------------------------- 1.8.2 (2013-01-11): Bumped up rails dependencies up to 3.2.11. ---------------------------------------------------------------------------------------- 1.8.1 (2013-01-02): Bumped up rails dependencies up to 3.2.10. ---------------------------------------------------------------------------------------- 1.8.0 (2012-11-12): Added support for Rails versions up to 3.2.9. Please note, that Rails 3.2.4 is not officially supported. Your code may work on that version, but no bug reports will be accepted about this version. Tests for DbCharmer have been moved to the gem repository to make Travis-CI integration more stable. We do not put test files into the gem inself so it should not be a problem for most of the users. If you still use db-charmer as a plugin, please switch to gem mode. ---------------------------------------------------------------------------------------- 1.7.1 (2012-04-22): Beta feature: Rails 3.1 and 3.2 support Thanks to the community (and Eugene Pimenov aka @libc in particular) we now have support for Rails versions up to 3.2.1, including new migrations classes. ---------------------------------------------------------------------------------------- 1.7.0 (2011-08-29): Beta feature: Rails 3 support Beta feature: Added force_slave_reads functionality. Now we could have models with slaves that are not used by default, but could be turned on globally (per-controller or per-action). Heavily reorganized the source code to match Rails code structure (class names, etc). This should make it much easier for other contributors to work with the code. Added smarter environment detection (using Rails.env, RAILS_ENV or RACK_ENV). Changed dependencies a bit: instead of depending on rails, we now require specific components (ActiveRecord, ActiveSupport, etc) + we do not require blankslate gem anymore. Bugfixes: Fix for N+1 queries when accessing shard_info for db_block_group_map sharding method. ---------------------------------------------------------------------------------------- 1.6.17-19 (2011-04-25): Bugfixes: Do not touch database for sharded models until we really need to. Before 1.6.17 if a database server was dead and there were any other connection issues, class loading in Ruby would be broken and sharded model class would not be initialized. ---------------------------------------------------------------------------------------- 1.6.14 (2011-01-09): Bugfixes: We do not support Rails 3, and now we prohibit any versions but 2.2 and 2.3 from being used with db-charmer gem. ---------------------------------------------------------------------------------------- 1.6.13 (2010-08-17): Starting with this version we use Rails.env instead of RAILS_ENV to auto-detect rails environment. If you use DbCharmer in non-rails project, please set DbCharmer.env manually. Bugfixes: Thanks to Eric Lindvall we now allow connection names that have symbols ruby wouldn't like for class names. ---------------------------------------------------------------------------------------- 1.6.12 (2010-05-09): Starting with this version we use Rails.cache (memcache or whatever you use in your project) to cache sharding blocks information. Bugfixes: Thanks to Allen Madsen (github user blatyo) we've fixed a few minor issues in database connections handling. ---------------------------------------------------------------------------------------- 1.6.11 (2010-04-16): Bugfix: Change the way we allocate sharding blocks in block map sharding method to prevent race-conditions from happening on block to shard assignments. Breaking change: We require connections to exist by default in all connection factory methods. If you need old behavior, pass should_exist=false explicitly. ---------------------------------------------------------------------------------------- 1.6.10 (2010-04-09): Multi-Db migrations changed. Now it is possible to call ActiveRecord::Migration.db_magic and specify default migration connection that would be used by all migrations without excplicitly switched connections. ---------------------------------------------------------------------------------------- 1.6.9 (2010-04-08): Bugfix release: now DbCharmer works without Rails. ---------------------------------------------------------------------------------------- 1.6.7 (2010-04-07): Changed the way we handle associations in on_db(:foo).find(:include) calls. Now we switch association's connection only if its default connection is the same as the master model's connection (not more "table does not exist" problems I hope). ---------------------------------------------------------------------------------------- 1.6.5 (2010-04-05): Bugfix release: Fixed :connection vs :slave in db_magic behaviour. Model.on_master should run queries on the master, not on AR's default connection. ---------------------------------------------------------------------------------------- 1.6.4 (2010-04-05): Default behaviour changed: DbCharmer.connections_should_exist is true in all environments by default. Old default behaviour was too misleading for many developers. ---------------------------------------------------------------------------------------- 1.6.3 (2010-04-03): Bugfix release: Modified stub connection initialization code to set default connections for sharded models using shards enumeration or default shard features of sharding methods. ---------------------------------------------------------------------------------------- 1.6.2 (2010-04-03): Bugfix release: Modified our stub connection used on sharded models to fail on db-calling methods only. Proxy the rest to a real shard connection. Another bug fixed in db_block_map sharding method: we didn't increment block counters when assigning blocks to shards. ---------------------------------------------------------------------------------------- 1.6.1 (2010-03-31): Breaking change from now on all connection-switching methods (both in migrations and in models) are controlled by a single option DbCharmer.connections_should_exist. This option is false by default in all non-production environments. Check out README for more details. ---------------------------------------------------------------------------------------- 1.6.0 (2010-03-31): The major (and arguably the only noticeable) change in this version is our simple database sharding support. The feature is still in alpha stage and should not be used in production without complete understanding of the principles of its work. ---------------------------------------------------------------------------------------- 1.5.5 (2010-03-15): Thanks to ngmoco.com (http://github.com/ngmoco) now DbCharmer supports one more use-case for multi-db migrations. Now you can run the same migration on many databases at once. For example, the following migration would create test_table on all three shard databases: class MultiDbTest < ActiveRecord::Migration db_magic :connections => [ :shard01, :shard02, :shard03 ] def self.up create_table :test_table do |t| t.string :test_string t.timestamps end end def self.down drop_table :test_table end end ---------------------------------------------------------------------------------------- 1.5.4 (2010-03-12): Added DbCharmer.with_remapped_databases, so that you can change the connection for many models simultaneously, and implicitly. Very useful for work where you want to use a particular slave for a whole range of database access. ---------------------------------------------------------------------------------------- 1.5.3 (2010-03-10): Few changes: * Colorized connection names in the logs for development mode * We do not log connection names when connection does not exist ---------------------------------------------------------------------------------------- 1.5.1 (2010-03-06): In this version we've added support for connection names logging in Rails queries log. New log records have [connection_name] prefix for all queries that are executed on non-standard connections: [logs] LogRecord Columns (1.1ms) SHOW FIELDS FROM `log_records` [logs] User Delete all (0.1ms) DELETE FROM `users` [slave01] User Load (0.2ms) SELECT * FROM `users` WHERE (`users`.`login` = 'foo') ---------------------------------------------------------------------------------------- 1.4.6 -> 1.5.0 (2010-03-05): Major change in this version of DbCharmer is association preload support. For example, let's say we have a schema: class Post < ActiveRecord::Base belongs_to :user end class User < ActiveRecord::Base has_many :posts end Now, if we have the following call in our code: User.on_db(:foo).all(:include => :posts) In 1.4.6 it would load the users from connection :foo and posts from the default connection, which is not what we would expect from this line of code. So, starting 1.5.0 all finder calls on models having :include parameter would switch associated models' connections to the same connection as the main model in the call. ================================================ FILE: LICENSE ================================================ The MIT License Copyright (c) 2011, Oleksiy Kovyrin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ doc/files/README_rdoc.html: README.rdoc rdoc README.rdoc ================================================ FILE: README.rdoc ================================================ = WARNING: The Project Has Been Suspended Please note, that this project has been suspended. No updates will be provided and no Rails versions beyond 3.2.x will be supported. For more information please check out this blog post: http://kovyrin.net/2014/11/14/dbcharmer-suspended/ = DB Charmer - ActiveRecord Connection Magic Plugin +DbCharmer+ is a simple yet powerful plugin for ActiveRecord that significantly extends its ability to work with multiple databases and/or database servers. The major features we add to ActiveRecord are: 1. Simple management for AR model connections (+switch_connection_to+ method) 2. Switching of default AR model connections to separate servers/databases 3. Ability to easily choose where your query should go (Model.on_* methods family) 4. Automated master/slave queries routing (selects go to a slave, updates handled by the master). 5. Multiple database migrations with very flexible query routing controls. 6. Simple database sharding functionality with multiple sharding methods (value, range, mapping table). For more information on the project, you can check out our web site at http://kovyrin.github.io/db-charmer/. == Installation There are two options when approaching +DbCharmer+ installation: * using the gem (recommended and the only way of using it with Rails 3.2+) * install as a Rails plugin (works in Rails 2.x only) To install as a gem, add this to your Gemfile: gem 'db-charmer', :require => 'db_charmer' To install +DbCharmer+ as a Rails plugin use the following command: ./script/plugin install git://github.com/kovyrin/db-charmer.git _Notice_: If you use +DbCharmer+ in a non-rails project, you may need to set DbCharmer.env to a correct value before using any of its connection management methods. Correct value here is a valid database.yml first-level section name. == Documentation/Questions For more information about the library, please visit our site at http://dbcharmer.net. If you need more defails on DbCharmer internals, please check out the source code. All the plugin's code is ~100% covered with tests. The project located in test-project directory has unit tests for all or, at least, the most actively used code paths. If you have any questions regarding this project, you could contact the author using the DbCharmer Users Group mailing list: - Group Info: http://groups.google.com/group/db-charmer - Subscribe using the info page or by sending an email to mailto:db-charmer-subscribe@googlegroups.com == What Ruby and Rails implementations does it work for? We have a continuous integration setup for this gem on with Rails 2.3, 3.0, 3.1 and 3.2 using a few different versions of Ruby. CI is running on TravisCI.org: https://travis-ci.org/kovyrin/db-charmer Build status is: {Build Status: Rails 3.x}[https://travis-ci.org/kovyrin/db-charmer] At the moment we have the following build matrix: * Rails versions: - 2.3 - 3.0 - 3.1 - 3.2 * Ruby versions: - 1.8.7 - 1.9.3 (Rails 3.0+ only) - 2.0.0 (Rails 3.2+ only) * Databases: - MySQL In addition to CI testing, this gem is used in production on Scribd.com (one of the largest RoR sites in the world) with Ruby Enterprise Edition and Rails 2.2, Rails 2.3, Sinatra and plain Rack applications. Starting with version 1.8.0 we support Rails versions 3.2.8 and higher. Please note, that Rails 3.2.4 is not officially supported. Your code may work on that version, but no bug reports will be accepted about this version. == Is it Thread-Safe? Starting with version 1.9.0 we have started working on making the code thread-safe and making sure DbCharmer works correctly in multi-threaded environments. At this moment we consider multi-threaded mode experimental. If you use it and it works for you - please let us know, if it does not - please make sure to file a ticket so that we could improve the code and make it work in your situation. == Who are the authors? This plugin has been created in Scribd.com for our internal use and then the sources were opened for other people to use. Most of the code in this package has been developed by Oleksiy Kovyrin for Scribd.com and is released under the MIT license. For more details, see the LICENSE file. Other contributors who have helped with the development of this library are (alphabetically ordered): * Allen Madsen * Andrew Geweke * Ashley Martens * Cauê Guerra * David Dai * Dmytro Shteflyuk * Eric Lindvall * Eugene Pimenov * Jonathan Viney * Gregory Man * Michael Birk * Tyler McMullen ================================================ FILE: Rakefile ================================================ require 'rake' require 'bundler' Bundler::GemHelper.install_tasks ================================================ FILE: ci_build ================================================ #!/bin/bash # Making the script more robust set -e # Exit on errors set -u # Exit on uninitialized variables RAILS_VERSION=${RAILS_VERSION:-} if [ "$RAILS_VERSION" == "" ]; then echo "Please specify rails version using RAILS_VERSION environment variable!" exit 1 fi # Change directory according to the rails version if [ "$RAILS_VERSION" == "2.x" ]; then # Downgrade rubygems because rails 2.3 does not work on 2.0+ gem update --system 1.8.25 cd test-project-2.x else cd test-project fi # Print version info echo "-----------------------------------------------------------------------------------------------------------------" echo " * Running specs for Rails version $RAILS_VERSION..." echo " * Ruby version: `ruby --version`" echo " * Rubygems version: `gem --version`" echo " * DbCharmer gem version: '${DB_CHARMER_GEM:-trunk}'" echo "-----------------------------------------------------------------------------------------------------------------" # Test environment export RAILS_ENV=test # Configure database access cp -f config/database.yml.example config/database.yml # Create databases and sharding tables mysql -u root < db/create_databases.sql mysql -u root db_charmer_sandbox_test < db/sharding.sql # Install gems rm -f Gemfile.lock bundle install # Run migrations bundle exec rake --trace db:migrate # Run the build and return its exit code if [ "$RAILS_VERSION" == "2.x" ]; then exec bundle exec spec -p '/*/**/*_spec.rb' -cbfs spec else exec bundle exec rspec -cbfs spec fi ================================================ FILE: db-charmer.gemspec ================================================ # -*- encoding: utf-8 -*- $:.push File.expand_path('../lib', __FILE__) require 'db_charmer/version' Gem::Specification.new do |s| s.name = 'db-charmer' s.version = DbCharmer::Version::STRING s.platform = Gem::Platform::RUBY s.authors = [ 'Oleksiy Kovyrin' ] s.email = 'alexey@kovyrin.net' s.homepage = 'http://kovyrin.github.io/db-charmer/' s.summary = 'ActiveRecord Connections Magic (slaves, multiple connections, etc)' s.description = 'DbCharmer is a Rails plugin (and gem) that could be used to manage AR model connections, implement master/slave query schemes, sharding and other magic features many high-scale applications need.' s.license = 'MIT' s.rdoc_options = [ '--charset=UTF-8' ] s.files = Dir['lib/**/*'] + Dir['*.rb'] s.files += %w[ README.rdoc LICENSE CHANGES ] s.require_paths = [ 'lib' ] s.extra_rdoc_files = [ 'LICENSE', 'README.rdoc' ] # Dependencies s.add_dependency 'activesupport', '< 4.0.0' s.add_dependency 'activerecord', '< 4.0.0' s.add_development_dependency 'rspec' s.add_development_dependency 'yard' s.add_development_dependency 'actionpack' end ================================================ FILE: init.rb ================================================ require 'db_charmer' ================================================ FILE: issues/issues-as-of-2014-11-14.json ================================================ [ { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/98", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/98/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/98/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/98/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/98", "id": 45164122, "number": 98, "title": "Rails 4 blocker update_all may ignore condition with db_charmer !!", "user": { "login": "AvnerCohen", "id": 1297254, "avatar_url": "https://avatars.githubusercontent.com/u/1297254?v=3", "gravatar_id": "", "url": "https://api.github.com/users/AvnerCohen", "html_url": "https://github.com/AvnerCohen", "followers_url": "https://api.github.com/users/AvnerCohen/followers", "following_url": "https://api.github.com/users/AvnerCohen/following{/other_user}", "gists_url": "https://api.github.com/users/AvnerCohen/gists{/gist_id}", "starred_url": "https://api.github.com/users/AvnerCohen/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/AvnerCohen/subscriptions", "organizations_url": "https://api.github.com/users/AvnerCohen/orgs", "repos_url": "https://api.github.com/users/AvnerCohen/repos", "events_url": "https://api.github.com/users/AvnerCohen/events{/privacy}", "received_events_url": "https://api.github.com/users/AvnerCohen/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 0, "created_at": "2014-10-07T20:42:29Z", "updated_at": "2014-10-07T20:42:29Z", "closed_at": null, "body": "First thing first, thanks for making this gem and putting the effort into it! :bow: \r\n...\r\n\r\nTo the issue, it might just be that this is a combination of an old code being upgraded from Rails 3 to Rails 4, But it is extremely dangerous.\r\n\r\nConsider the following console code:\r\n\r\n````ruby\r\n› rails c\r\nLoading development environment (Rails 4.1.6)\r\n2.1.2 :001 > Test.update_all({updated_at: Time.now}, {somevalue: 123})\r\nArgumentError: wrong number of arguments (2 for 1)\r\n from ./gems/activerecord-4.1.6/lib/active_record/relation.rb:316:in `update_all'\r\n from ./bundler/gems/db-charmer-cb40007c36e2/lib/db_charmer/rails3/active_record/relation/connection_routing.rb:128:in `block in update_all_with_db_charmer'\r\n from ./bundler/gems/db-charmer-cb40007c36e2/lib/db_charmer/rails3/active_record/relation/connection_routing.rb:103:in `block in switch_connection_for_method'\r\n from ./bundler/gems/db-charmer-cb40007c36e2/lib/db_charmer/active_record/multi_db_proxy.rb:37:in `on_db'\r\n from ./bundler/gems/db-charmer-cb40007c36e2/lib/db_charmer/rails3/active_record/relation/connection_routing.rb:102:in `switch_connection_for_method'\r\n from ./bundler/gems/db-charmer-cb40007c36e2/lib/db_charmer/rails3/active_record/relation/connection_routing.rb:127:in `update_all_with_db_charmer'\r\n from ./gems/activerecord-4.1.6/lib/active_record/querying.rb:8:in `update_all'\r\n from (irb):1\r\n from ./gems/railties-4.1.6/lib/rails/commands/console.rb:90:in `start'\r\n from ./gems/railties-4.1.6/lib/rails/commands/console.rb:9:in `start'\r\n from ./gems/railties-4.1.6/lib/rails/commands/commands_tasks.rb:69:in `console'\r\n from ./gems/railties-4.1.6/lib/rails/commands/commands_tasks.rb:40:in `run_command!'\r\n from ./gems/railties-4.1.6/lib/rails/commands.rb:17:in `'\r\n from bin/rails:8:in `require'\r\n from bin/rails:8:in `
'\r\n2.1.2 :002 > Test.update_all({updated_at: Time.now})\r\n SQL (0.5ms) UPDATE \"tests\" SET \"updated_at\" = '2014-10-07 20:34:07.694313'\r\n => 0\r\n2.1.2 :003 > Test.update_all({updated_at: Time.now}, {somevalue: 123})\r\n SQL (0.2ms) UPDATE \"tests\" SET \"updated_at\" = '2014-10-07 20:34:12.975116'\r\n => 0\r\n\r\n````\r\nGemfile is simply:\r\n\r\n`````ruby\r\nsource 'https://rubygems.org'\r\n\r\ngem 'rails', '4.1.6'\r\n\r\ngem 'sqlite3'\r\n\r\ngem 'db-charmer',\r\n :git => 'git@github.com:kovyrin/db-charmer.git',\r\n :branch => 'rails4', :ref => 'cb40007c36e2847850a37a490850cfd822016c5f', :require => 'db_charmer'\r\n````\r\n\r\n\r\n\r\nDescribing the issue:\r\n\r\n1. Calling update_all with wrong params (arity changed to 1 on rails 4) failes as expected.\r\n2. Enough to have a single update_all that passed succesfully.\r\n3. Next execution of wrong params (as [1] above) passes succesfully and arity check is ignored + conditions are totally ignored." }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/97", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/97/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/97/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/97/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/97", "id": 38187982, "number": 97, "title": "DB connections not closing properly", "user": { "login": "nchafai", "id": 436617, "avatar_url": "https://avatars.githubusercontent.com/u/436617?v=3", "gravatar_id": "", "url": "https://api.github.com/users/nchafai", "html_url": "https://github.com/nchafai", "followers_url": "https://api.github.com/users/nchafai/followers", "following_url": "https://api.github.com/users/nchafai/following{/other_user}", "gists_url": "https://api.github.com/users/nchafai/gists{/gist_id}", "starred_url": "https://api.github.com/users/nchafai/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/nchafai/subscriptions", "organizations_url": "https://api.github.com/users/nchafai/orgs", "repos_url": "https://api.github.com/users/nchafai/repos", "events_url": "https://api.github.com/users/nchafai/events{/privacy}", "received_events_url": "https://api.github.com/users/nchafai/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 2, "created_at": "2014-07-18T15:47:56Z", "updated_at": "2014-07-21T07:17:26Z", "closed_at": "2014-07-21T07:16:55Z", "body": "Hi,\r\n\r\nWe are using db-charmer to handle distributed data accross multiple databases. However, connections are not closing properly when requests are made on a sharded database : hundreds of connections remain opened. \r\n\r\nAfter investigations, this situation is due to `abstract_connection_class_name` used to reference connections in `ConnectionPool` list : as long as different threads occur, a new connection is added in the pool list since `abstract_connection_class_name` is using the Thread id : \r\n```ruby\r\ndef self.abstract_connection_class_name(connection_name)\r\n conn_name_klass = connection_name.to_s.gsub(/\\W+/, '_').camelize\r\n thread = Thread.current.object_id.abs # need to make sure it is non-negative\r\n \"::AutoGeneratedAbstractConnectionClass#{conn_name_klass}ForThread#{thread}\"\r\nend\r\n```\r\n\r\nIf we just use ` \"::AutoGeneratedAbstractConnectionClass#{conn_name_klass}ForThread\"` without the `#{thread}` suffix part, everything looks fine, and connections are properly closed. \r\n\r\nSo, what is the exact purpose of having introduce this `#{thread}` suffix ? (this suffix is not present in the previous versions of db-charmer)" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/96", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/96/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/96/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/96/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/96", "id": 35993889, "number": 96, "title": "[Rails 4] rake db:migrate:redo doesn't redo all migrations", "user": { "login": "akshetpandey", "id": 1213060, "avatar_url": "https://avatars.githubusercontent.com/u/1213060?v=3", "gravatar_id": "", "url": "https://api.github.com/users/akshetpandey", "html_url": "https://github.com/akshetpandey", "followers_url": "https://api.github.com/users/akshetpandey/followers", "following_url": "https://api.github.com/users/akshetpandey/following{/other_user}", "gists_url": "https://api.github.com/users/akshetpandey/gists{/gist_id}", "starred_url": "https://api.github.com/users/akshetpandey/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/akshetpandey/subscriptions", "organizations_url": "https://api.github.com/users/akshetpandey/orgs", "repos_url": "https://api.github.com/users/akshetpandey/repos", "events_url": "https://api.github.com/users/akshetpandey/events{/privacy}", "received_events_url": "https://api.github.com/users/akshetpandey/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 0, "created_at": "2014-06-18T14:58:18Z", "updated_at": "2014-07-08T00:00:58Z", "closed_at": "2014-07-08T00:00:58Z", "body": "I am using the rails 4 branch with rails 4. The migrations work fine but when I try to redo the migrations, only the last two migrations are picked for redo." }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/95", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/95/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/95/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/95/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/95", "id": 35756037, "number": 95, "title": "Rails 4.1+", "user": { "login": "richpeck", "id": 1104431, "avatar_url": "https://avatars.githubusercontent.com/u/1104431?v=3", "gravatar_id": "", "url": "https://api.github.com/users/richpeck", "html_url": "https://github.com/richpeck", "followers_url": "https://api.github.com/users/richpeck/followers", "following_url": "https://api.github.com/users/richpeck/following{/other_user}", "gists_url": "https://api.github.com/users/richpeck/gists{/gist_id}", "starred_url": "https://api.github.com/users/richpeck/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/richpeck/subscriptions", "organizations_url": "https://api.github.com/users/richpeck/orgs", "repos_url": "https://api.github.com/users/richpeck/repos", "events_url": "https://api.github.com/users/richpeck/events{/privacy}", "received_events_url": "https://api.github.com/users/richpeck/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 7, "created_at": "2014-06-15T19:23:32Z", "updated_at": "2014-11-03T15:25:13Z", "closed_at": null, "body": "Any chance we could get an updated ver to work with ActiveSupport 4.1+? \r\n\r\nIt's not compatible with the latest version of Rails otherwise :(" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/94", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/94/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/94/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/94/events", "html_url": "https://github.com/kovyrin/db-charmer/pull/94", "id": 28744882, "number": 94, "title": "Fix #93 Keep slave connections thread local.", "user": { "login": "gworley3", "id": 809976, "avatar_url": "https://avatars.githubusercontent.com/u/809976?v=3", "gravatar_id": "", "url": "https://api.github.com/users/gworley3", "html_url": "https://github.com/gworley3", "followers_url": "https://api.github.com/users/gworley3/followers", "following_url": "https://api.github.com/users/gworley3/following{/other_user}", "gists_url": "https://api.github.com/users/gworley3/gists{/gist_id}", "starred_url": "https://api.github.com/users/gworley3/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/gworley3/subscriptions", "organizations_url": "https://api.github.com/users/gworley3/orgs", "repos_url": "https://api.github.com/users/gworley3/repos", "events_url": "https://api.github.com/users/gworley3/events{/privacy}", "received_events_url": "https://api.github.com/users/gworley3/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 0, "created_at": "2014-03-04T21:57:32Z", "updated_at": "2014-03-04T21:57:32Z", "closed_at": null, "pull_request": { "url": "https://api.github.com/repos/kovyrin/db-charmer/pulls/94", "html_url": "https://github.com/kovyrin/db-charmer/pull/94", "diff_url": "https://github.com/kovyrin/db-charmer/pull/94.diff", "patch_url": "https://github.com/kovyrin/db-charmer/pull/94.patch" }, "body": "This is probably not an ideal fix for #93 but it seems to work." }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/93", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/93/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/93/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/93/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/93", "id": 28739552, "number": 93, "title": "Multi-threaded race conditions with Postgres", "user": { "login": "gworley3", "id": 809976, "avatar_url": "https://avatars.githubusercontent.com/u/809976?v=3", "gravatar_id": "", "url": "https://api.github.com/users/gworley3", "html_url": "https://github.com/gworley3", "followers_url": "https://api.github.com/users/gworley3/followers", "following_url": "https://api.github.com/users/gworley3/following{/other_user}", "gists_url": "https://api.github.com/users/gworley3/gists{/gist_id}", "starred_url": "https://api.github.com/users/gworley3/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/gworley3/subscriptions", "organizations_url": "https://api.github.com/users/gworley3/orgs", "repos_url": "https://api.github.com/users/gworley3/repos", "events_url": "https://api.github.com/users/gworley3/events{/privacy}", "received_events_url": "https://api.github.com/users/gworley3/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 0, "created_at": "2014-03-04T20:52:19Z", "updated_at": "2014-07-09T00:38:31Z", "closed_at": null, "body": "Trying to use db-charmer 1.9.0 with Postgres and rails 3.2.17. Db-charmer works fine with this setup when single threaded. Running multiple threads via Sidekiq, I get the following error:\r\n\r\n```\r\n2014-03-04T20:28:37Z 40445 TID-ox250opo8 WARN: undefined method `fields' for nil:NilClass\r\n2014-03-04T20:28:37Z 40445 TID-ox250opo8 WARN: /Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/connection_adapters/postgresql_adapter.rb:664:in `block in exec_query'\r\n```\r\n\r\nI believe I have traced this down to the way `PostgresqlAdapter` works in Rails. If you dig around in its code you'll find that it makes async calls to execute queries and expects no other query to execute on the same connection while the current query is executing. If another query comes along they end up in a race condition where one query gets a result (possibly the wrong one!) and the other gets nil.\r\n\r\nThis suggests db-charmer needs to do a better job of locking connections to particular threads so that a connection is not used by more than one thread at a time.\r\n\r\nFull stack trace for the curious:\r\n\r\n```\r\n2014-03-04T20:28:37Z 40445 TID-ox250opo8 WARN: {\"retry\"=>5, \"queue\"=>\"low_linkedin_metrics\", \"failures\"=>\"exhausted\", \"class\"=>\"MetricsWorker::Linkedin::ImportAdMetrics\", \"args\"=>[1, \"2013-02-09..2013-02-09\"], \"jid\"=>\"e968f2b5935d884385911a83\", \"enqueued_at\"=>1393960671.074003, \"error_message\"=>\"undefined method `fields' for nil:NilClass\", \"error_class\"=>\"NoMethodError\", \"failed_at\"=>1393964917.8832488, \"retry_count\"=>0}\r\n2014-03-04T20:28:37Z 40445 TID-ox250opo8 WARN: undefined method `fields' for nil:NilClass\r\n2014-03-04T20:28:37Z 40445 TID-ox250opo8 WARN: /Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/connection_adapters/postgresql_adapter.rb:664:in `block in exec_query'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/connection_adapters/abstract_adapter.rb:280:in `block in log'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activesupport-3.2.17/lib/active_support/notifications/instrumenter.rb:20:in `instrument'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/rails3/abstract_adapter/connection_name.rb:14:in `instrument'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/connection_adapters/abstract_adapter.rb:275:in `log'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/newrelic_rpm-3.6.4.122/lib/new_relic/agent/instrumentation/active_record.rb:46:in `block in log_with_newrelic_instrumentation'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/newrelic_rpm-3.6.4.122/lib/new_relic/agent/method_tracer.rb:271:in `trace_execution_scoped'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/newrelic_rpm-3.6.4.122/lib/new_relic/agent/instrumentation/active_record.rb:43:in `log_with_newrelic_instrumentation'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/connection_adapters/postgresql_adapter.rb:659:in `exec_query'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/connection_adapters/postgresql_adapter.rb:1263:in `select'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/connection_adapters/abstract/database_statements.rb:18:in `select_all'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/connection_adapters/abstract/query_cache.rb:63:in `select_all'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/querying.rb:38:in `block in find_by_sql'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/explain.rb:41:in `logging_query_plan'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/querying.rb:37:in `find_by_sql'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/rails3/active_record/master_slave_routing.rb:13:in `block in find_by_sql'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/active_record/multi_db_proxy.rb:71:in `first_level_on_slave'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/rails3/active_record/master_slave_routing.rb:12:in `find_by_sql'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/newrelic_rpm-3.6.4.122/lib/new_relic/agent/method_tracer.rb:521:in `block in find_by_sql_with_trace_ActiveRecord_self_name_find_by_sql'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/newrelic_rpm-3.6.4.122/lib/new_relic/agent/method_tracer.rb:271:in `trace_execution_scoped'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/newrelic_rpm-3.6.4.122/lib/new_relic/agent/method_tracer.rb:516:in `find_by_sql_with_trace_ActiveRecord_self_name_find_by_sql'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/relation.rb:171:in `exec_queries'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/relation.rb:160:in `block in to_a'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/explain.rb:34:in `logging_query_plan'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/relation.rb:159:in `to_a'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/rails3/active_record/relation/connection_routing.rb:123:in `block in to_a_with_db_charmer'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/rails3/active_record/relation/connection_routing.rb:113:in `block in switch_connection_for_method'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/active_record/multi_db_proxy.rb:69:in `block in first_level_on_slave'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/active_record/multi_db_proxy.rb:40:in `on_db'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/active_record/multi_db_proxy.rb:59:in `on_slave'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/active_record/multi_db_proxy.rb:69:in `first_level_on_slave'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/rails3/active_record/relation/connection_routing.rb:112:in `switch_connection_for_method'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/rails3/active_record/relation/connection_routing.rb:122:in `to_a_with_db_charmer'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/relation/finder_methods.rb:381:in `find_first'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/relation/finder_methods.rb:122:in `first'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/relation/finder_methods.rb:267:in `find_by_attributes'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/dynamic_matchers.rb:50:in `method_missing'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/relation/delegation.rb:14:in `block in find_by_id'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/relation.rb:241:in `block in scoping'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/scoping.rb:98:in `with_scope'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/relation.rb:241:in `scoping'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/activerecord-3.2.17/lib/active_record/relation/delegation.rb:14:in `find_by_id'\r\n/Users/gworley3/github/adstage/adstage-platform-v2/app/workers/metrics_worker/linkedin/import_ad_metrics.rb:9:in `block in perform'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/db-charmer-1.9.0/lib/db_charmer/force_slave_reads.rb:50:in `force_slave_reads'\r\n/Users/gworley3/github/adstage/adstage-platform-v2/app/workers/metrics_worker/linkedin/import_ad_metrics.rb:8:in `perform'\r\n(eval):3:in `block in perform_with_newrelic_transaction_trace'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/newrelic_rpm-3.6.4.122/lib/new_relic/agent/instrumentation/controller_instrumentation.rb:318:in `perform_action_with_newrelic_trace'\r\n(eval):2:in `perform_with_newrelic_transaction_trace'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/processor.rb:49:in `block (3 levels) in process'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/chain.rb:122:in `call'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/chain.rb:122:in `block in invoke'\r\n/Users/gworley3/github/adstage/adstage-platform-v2/config/initializers/sidekiq.rb:9:in `call'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/chain.rb:124:in `block in invoke'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/newrelic_rpm-3.6.4.122/lib/new_relic/agent/instrumentation/sidekiq.rb:25:in `block in call'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/newrelic_rpm-3.6.4.122/lib/new_relic/agent/instrumentation/controller_instrumentation.rb:318:in `perform_action_with_newrelic_trace'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/newrelic_rpm-3.6.4.122/lib/new_relic/agent/instrumentation/sidekiq.rb:21:in `call'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/chain.rb:124:in `block in invoke'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-unique-jobs-2.7.1/lib/sidekiq-unique-jobs/middleware/server/unique_jobs.rb:14:in `call'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/chain.rb:124:in `block in invoke'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-failures-0.3.0/lib/sidekiq/failures/middleware.rb:10:in `call'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/chain.rb:124:in `block in invoke'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-pro-1.4.3/lib/sidekiq/batch/middleware.rb:26:in `call'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/chain.rb:124:in `block in invoke'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/server/active_record.rb:6:in `call'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/chain.rb:124:in `block in invoke'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/server/retry_jobs.rb:62:in `call'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/chain.rb:124:in `block in invoke'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/server/logging.rb:11:in `block in call'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/logging.rb:22:in `with_context'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/server/logging.rb:7:in `call'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/chain.rb:124:in `block in invoke'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/chain.rb:127:in `call'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/middleware/chain.rb:127:in `invoke'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/processor.rb:48:in `block (2 levels) in process'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/processor.rb:105:in `stats'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/processor.rb:47:in `block in process'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/processor.rb:86:in `do_defer'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/sidekiq-2.17.6/lib/sidekiq/processor.rb:37:in `process'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/celluloid-0.15.2/lib/celluloid/calls.rb:25:in `public_send'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/celluloid-0.15.2/lib/celluloid/calls.rb:25:in `dispatch'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/celluloid-0.15.2/lib/celluloid/calls.rb:122:in `dispatch'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/celluloid-0.15.2/lib/celluloid/actor.rb:322:in `block in handle_message'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/celluloid-0.15.2/lib/celluloid/actor.rb:416:in `block in task'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/celluloid-0.15.2/lib/celluloid/tasks.rb:55:in `block in initialize'\r\n/Users/gworley3/.rvm/gems/ruby-2.0.0-p247@platform-v2/gems/celluloid-0.15.2/lib/celluloid/tasks/task_fiber.rb:13:in `block in create'\r\n```" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/92", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/92/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/92/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/92/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/92", "id": 28669704, "number": 92, "title": "habtm associations support broken in rails 4 branch.", "user": { "login": "bobbarjung", "id": 1243200, "avatar_url": "https://avatars.githubusercontent.com/u/1243200?v=3", "gravatar_id": "", "url": "https://api.github.com/users/bobbarjung", "html_url": "https://github.com/bobbarjung", "followers_url": "https://api.github.com/users/bobbarjung/followers", "following_url": "https://api.github.com/users/bobbarjung/following{/other_user}", "gists_url": "https://api.github.com/users/bobbarjung/gists{/gist_id}", "starred_url": "https://api.github.com/users/bobbarjung/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/bobbarjung/subscriptions", "organizations_url": "https://api.github.com/users/bobbarjung/orgs", "repos_url": "https://api.github.com/users/bobbarjung/repos", "events_url": "https://api.github.com/users/bobbarjung/events{/privacy}", "received_events_url": "https://api.github.com/users/bobbarjung/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 6, "created_at": "2014-03-04T00:18:47Z", "updated_at": "2014-07-29T14:40:22Z", "closed_at": null, "body": "Hi I have a connection to a recovery database and a habtm relationship between two models.\r\n\r\n```ruby\r\nclass Line\r\n has_and_belongs_to_many: nodes\r\nend\r\n\r\nclass Node\r\n has_and_belongs_to_many :lines\r\nend\r\n```\r\n\r\nI could access these relationships on the secondary database through db_charmer magic.\r\n\r\n```ruby\r\na = Node.on_db(:recovery).first\r\na.on_db(:recovery).lines.each {|b| < do stuff with b> }\r\n```\r\n\r\nBut with rails 4 branch of db_charmer gem (2.0.0.dev1)\r\n\r\nI simply get the following \r\n\r\n```\r\nirb(main):034:0> a.on_db(:recovery).lines\r\nirb(main):034:0># ActiveRecord::Associations::CollectionProxy []\r\n```\r\n\r\nAny interim way to get around this issue will also be appreciated." }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/91", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/91/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/91/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/91/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/91", "id": 28574942, "number": 91, "title": "can't query against the default db on_db(:default)", "user": { "login": "ohadpartuck", "id": 2236337, "avatar_url": "https://avatars.githubusercontent.com/u/2236337?v=3", "gravatar_id": "", "url": "https://api.github.com/users/ohadpartuck", "html_url": "https://github.com/ohadpartuck", "followers_url": "https://api.github.com/users/ohadpartuck/followers", "following_url": "https://api.github.com/users/ohadpartuck/following{/other_user}", "gists_url": "https://api.github.com/users/ohadpartuck/gists{/gist_id}", "starred_url": "https://api.github.com/users/ohadpartuck/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/ohadpartuck/subscriptions", "organizations_url": "https://api.github.com/users/ohadpartuck/orgs", "repos_url": "https://api.github.com/users/ohadpartuck/repos", "events_url": "https://api.github.com/users/ohadpartuck/events{/privacy}", "received_events_url": "https://api.github.com/users/ohadpartuck/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 1, "created_at": "2014-03-02T07:33:20Z", "updated_at": "2014-03-02T09:53:04Z", "closed_at": "2014-03-02T09:52:28Z", "body": "```ruby \r\ndevelopment: &development\r\n database: app_dev\r\n host: masterdb\r\n <<: *defaults\r\n\r\n slave:\r\n <<: *defaults\r\n host: slavedb01\r\n```\r\n\r\nthen trying to force a query against master\r\n``` MyModel.on_db(:default) ```\r\neven tried \r\n``` MyModel.on_master ```\r\n\r\nBut there is no specific querying against any db.\r\n" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/90", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/90/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/90/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/90/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/90", "id": 28014934, "number": 90, "title": "RuntimeError: can't add a new key into hash during iteration", "user": { "login": "mhfs", "id": 78422, "avatar_url": "https://avatars.githubusercontent.com/u/78422?v=3", "gravatar_id": "", "url": "https://api.github.com/users/mhfs", "html_url": "https://github.com/mhfs", "followers_url": "https://api.github.com/users/mhfs/followers", "following_url": "https://api.github.com/users/mhfs/following{/other_user}", "gists_url": "https://api.github.com/users/mhfs/gists{/gist_id}", "starred_url": "https://api.github.com/users/mhfs/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/mhfs/subscriptions", "organizations_url": "https://api.github.com/users/mhfs/orgs", "repos_url": "https://api.github.com/users/mhfs/repos", "events_url": "https://api.github.com/users/mhfs/events{/privacy}", "received_events_url": "https://api.github.com/users/mhfs/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 12, "created_at": "2014-02-21T02:38:17Z", "updated_at": "2014-06-03T23:26:05Z", "closed_at": null, "body": "Hey @kovyrin ,\r\n\r\nI think I might have found a multi-threading bug. Throwing it here to see it rings any bells.\r\n\r\nI'm processing a high volume of quick sidekiq jobs and part of it is performing a query like this:\r\n\r\n```ruby\r\nUser.on_db(:bbdb).find(user_id)\r\n```\r\n\r\nIm' seeing lot's of errors like this:\r\n\r\n```\r\nRuntimeError: can't add a new key into hash during iteration\r\n```\r\nI'm using ruby 2.0.0p353, rails 3.217 and db-charmer 1.9.0 as you can see in the full backtrace below.\r\n\r\nDoes that rings any bells for you?\r\n\r\nThanks in advance.\r\n\r\n```\r\n/vendor/ruby/2.0.0/gems/activerecord-3.2.17/lib/active_record/connection_adapters/abstract/connection_pool.rb:375 in \"[]=\"\r\n/vendor/ruby/2.0.0/gems/activerecord-3.2.17/lib/active_record/connection_adapters/abstract/connection_pool.rb:375 in \"establish_connection\"\r\n/vendor/ruby/2.0.0/gems/activerecord-3.2.17/lib/active_record/connection_adapters/abstract/connection_specification.rb:137 in \"establish_connection\"\r\n/vendor/ruby/2.0.0/gems/db-charmer-1.9.0/lib/db_charmer/active_record/connection_switching.rb:25 in \"establish_real_connection_if_exists\"\r\n/vendor/ruby/2.0.0/gems/db-charmer-1.9.0/lib/db_charmer/connection_factory.rb:51 in \"generate_abstract_class\"\r\n/vendor/ruby/2.0.0/gems/db-charmer-1.9.0/lib/db_charmer/connection_factory.rb:35 in \"establish_connection\"\r\n/vendor/ruby/2.0.0/gems/db-charmer-1.9.0/lib/db_charmer/connection_factory.rb:24 in \"connect\"\r\n/vendor/ruby/2.0.0/gems/db-charmer-1.9.0/lib/db_charmer/active_record/connection_switching.rb:66 in \"coerce_to_connection_proxy\"\r\n/vendor/ruby/2.0.0/gems/db-charmer-1.9.0/lib/db_charmer/active_record/connection_switching.rb:82 in \"switch_connection_to\"\r\n/vendor/ruby/2.0.0/gems/db-charmer-1.9.0/lib/db_charmer/active_record/multi_db_proxy.rb:39 in \"on_db\"\r\n/vendor/ruby/2.0.0/gems/db-charmer-1.9.0/lib/db_charmer/active_record/multi_db_proxy.rb:19 in \"method_missing\"\r\n/app/jobs/backfill_stats_job.rb:7 in \"perform\"\r\n```" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/89", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/89/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/89/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/89/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/89", "id": 27979122, "number": 89, "title": "force_slave_reads option ignored", "user": { "login": "gworley3", "id": 809976, "avatar_url": "https://avatars.githubusercontent.com/u/809976?v=3", "gravatar_id": "", "url": "https://api.github.com/users/gworley3", "html_url": "https://github.com/gworley3", "followers_url": "https://api.github.com/users/gworley3/followers", "following_url": "https://api.github.com/users/gworley3/following{/other_user}", "gists_url": "https://api.github.com/users/gworley3/gists{/gist_id}", "starred_url": "https://api.github.com/users/gworley3/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/gworley3/subscriptions", "organizations_url": "https://api.github.com/users/gworley3/orgs", "repos_url": "https://api.github.com/users/gworley3/repos", "events_url": "https://api.github.com/users/gworley3/events{/privacy}", "received_events_url": "https://api.github.com/users/gworley3/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 1, "created_at": "2014-02-20T17:19:06Z", "updated_at": "2014-02-20T17:41:46Z", "closed_at": "2014-02-20T17:41:46Z", "body": "I set `:force_slave_reads => false` in the `db_magic` opts but still reads from slaves by default." }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/88", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/88/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/88/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/88/events", "html_url": "https://github.com/kovyrin/db-charmer/pull/88", "id": 24576212, "number": 88, "title": "Skip HasAndBelongsToMany preloader for Rails 4.1", "user": { "login": "kmcbride", "id": 597435, "avatar_url": "https://avatars.githubusercontent.com/u/597435?v=3", "gravatar_id": "", "url": "https://api.github.com/users/kmcbride", "html_url": "https://github.com/kmcbride", "followers_url": "https://api.github.com/users/kmcbride/followers", "following_url": "https://api.github.com/users/kmcbride/following{/other_user}", "gists_url": "https://api.github.com/users/kmcbride/gists{/gist_id}", "starred_url": "https://api.github.com/users/kmcbride/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/kmcbride/subscriptions", "organizations_url": "https://api.github.com/users/kmcbride/orgs", "repos_url": "https://api.github.com/users/kmcbride/repos", "events_url": "https://api.github.com/users/kmcbride/events{/privacy}", "received_events_url": "https://api.github.com/users/kmcbride/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 0, "created_at": "2013-12-19T18:31:26Z", "updated_at": "2014-06-11T19:21:31Z", "closed_at": "2014-06-11T19:21:31Z", "pull_request": { "url": "https://api.github.com/repos/kovyrin/db-charmer/pulls/88", "html_url": "https://github.com/kovyrin/db-charmer/pull/88", "diff_url": "https://github.com/kovyrin/db-charmer/pull/88.diff", "patch_url": "https://github.com/kovyrin/db-charmer/pull/88.patch" }, "body": "See: https://github.com/rails/rails/commit/a03ea3ff97b43340d0904525083bf8bc7a1c6ebc" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/87", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/87/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/87/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/87/events", "html_url": "https://github.com/kovyrin/db-charmer/pull/87", "id": 24129460, "number": 87, "title": "Make database rake auto load on rails", "user": { "login": "arthurnn", "id": 833383, "avatar_url": "https://avatars.githubusercontent.com/u/833383?v=3", "gravatar_id": "", "url": "https://api.github.com/users/arthurnn", "html_url": "https://github.com/arthurnn", "followers_url": "https://api.github.com/users/arthurnn/followers", "following_url": "https://api.github.com/users/arthurnn/following{/other_user}", "gists_url": "https://api.github.com/users/arthurnn/gists{/gist_id}", "starred_url": "https://api.github.com/users/arthurnn/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/arthurnn/subscriptions", "organizations_url": "https://api.github.com/users/arthurnn/orgs", "repos_url": "https://api.github.com/users/arthurnn/repos", "events_url": "https://api.github.com/users/arthurnn/events{/privacy}", "received_events_url": "https://api.github.com/users/arthurnn/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 3, "created_at": "2013-12-11T19:02:34Z", "updated_at": "2013-12-12T23:09:55Z", "closed_at": "2013-12-12T23:08:46Z", "pull_request": { "url": "https://api.github.com/repos/kovyrin/db-charmer/pulls/87", "html_url": "https://github.com/kovyrin/db-charmer/pull/87", "diff_url": "https://github.com/kovyrin/db-charmer/pull/87.diff", "patch_url": "https://github.com/kovyrin/db-charmer/pull/87.patch" }, "body": "@kovyrin \r\n\r\nWe need to load the rake task on `Rails::Railtie`, otherwise they wont be available when just adding the Gem.\r\n\r\nlet me know what you think.\r\n\r\ncheers," }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/86", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/86/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/86/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/86/events", "html_url": "https://github.com/kovyrin/db-charmer/pull/86", "id": 23877630, "number": 86, "title": "Allow AR::B.db_magic m/s split to work with abstract AR classes.", "user": { "login": "perplexes", "id": 13812, "avatar_url": "https://avatars.githubusercontent.com/u/13812?v=3", "gravatar_id": "", "url": "https://api.github.com/users/perplexes", "html_url": "https://github.com/perplexes", "followers_url": "https://api.github.com/users/perplexes/followers", "following_url": "https://api.github.com/users/perplexes/following{/other_user}", "gists_url": "https://api.github.com/users/perplexes/gists{/gist_id}", "starred_url": "https://api.github.com/users/perplexes/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/perplexes/subscriptions", "organizations_url": "https://api.github.com/users/perplexes/orgs", "repos_url": "https://api.github.com/users/perplexes/repos", "events_url": "https://api.github.com/users/perplexes/events{/privacy}", "received_events_url": "https://api.github.com/users/perplexes/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 0, "created_at": "2013-12-06T18:56:28Z", "updated_at": "2013-12-06T18:56:28Z", "closed_at": null, "pull_request": { "url": "https://api.github.com/repos/kovyrin/db-charmer/pulls/86", "html_url": "https://github.com/kovyrin/db-charmer/pull/86", "diff_url": "https://github.com/kovyrin/db-charmer/pull/86.diff", "patch_url": "https://github.com/kovyrin/db-charmer/pull/86.patch" }, "body": "Hi, ran into a SystemStackError. We wanted to split all reads in all models to slaves. The easiest way to do this seemed like:\r\n\r\nIn config/initializers/db_charmer.rb:\r\n```ruby\r\nActiveRecord::Base.db_magic(slave: :main_slave)\r\n```\r\n\r\nBut we also have Rails Engines that have their own version of an AR::B-like class with a separate connection, called base:\r\n\r\n```ruby\r\nclass IsbnDb::Base < ActiveRecord::Base\r\n self.abstract_class = true\r\n db_magic connection: :isbn_master, slave: :isbn_slave\r\nend\r\n```\r\n\r\nThen models underneath that would inherit this connection:\r\n```ruby\r\nclass Isbn < IsbnDb::Base\r\nend\r\n```\r\n\r\nWhat we were seeing were SystemStackErrors:\r\n\r\n```ruby\r\n[3] pry(main)> Isbn.first\r\nSystemStackError: stack level too deep\r\n```\r\n\r\nAfter a lot of tracing (and set_trace_func), the culprit is that putting db_magic on ActiveRecord::Base overrides the default behavior of the master/slave automatic methods like reload, find_by_sql and count_by_sql. In models that directly inherit from AR::B (like User, say) this behavior is fine - but in level-2 inherited models (AR::B > Base > Isbn), is causes infinite recursion.\r\n\r\nThis pull request fixes this situation, but I don't quite know how to test it." }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/85", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/85/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/85/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/85/events", "html_url": "https://github.com/kovyrin/db-charmer/pull/85", "id": 22639819, "number": 85, "title": "Rails4 Support", "user": { "login": "kovyrin", "id": 3467, "avatar_url": "https://avatars.githubusercontent.com/u/3467?v=3", "gravatar_id": "", "url": "https://api.github.com/users/kovyrin", "html_url": "https://github.com/kovyrin", "followers_url": "https://api.github.com/users/kovyrin/followers", "following_url": "https://api.github.com/users/kovyrin/following{/other_user}", "gists_url": "https://api.github.com/users/kovyrin/gists{/gist_id}", "starred_url": "https://api.github.com/users/kovyrin/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/kovyrin/subscriptions", "organizations_url": "https://api.github.com/users/kovyrin/orgs", "repos_url": "https://api.github.com/users/kovyrin/repos", "events_url": "https://api.github.com/users/kovyrin/events{/privacy}", "received_events_url": "https://api.github.com/users/kovyrin/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 16, "created_at": "2013-11-14T02:58:39Z", "updated_at": "2014-10-22T09:35:58Z", "closed_at": null, "pull_request": { "url": "https://api.github.com/repos/kovyrin/db-charmer/pulls/85", "html_url": "https://github.com/kovyrin/db-charmer/pull/85", "diff_url": "https://github.com/kovyrin/db-charmer/pull/85.diff", "patch_url": "https://github.com/kovyrin/db-charmer/pull/85.patch" }, "body": "This PR is used to track the process of adding Rails 4 support to DbCharmer. It is not ready yet, but we are really close." }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/84", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/84/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/84/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/84/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/84", "id": 22639397, "number": 84, "title": "License missing from gemspec", "user": { "login": "bf4", "id": 142914, "avatar_url": "https://avatars.githubusercontent.com/u/142914?v=3", "gravatar_id": "", "url": "https://api.github.com/users/bf4", "html_url": "https://github.com/bf4", "followers_url": "https://api.github.com/users/bf4/followers", "following_url": "https://api.github.com/users/bf4/following{/other_user}", "gists_url": "https://api.github.com/users/bf4/gists{/gist_id}", "starred_url": "https://api.github.com/users/bf4/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/bf4/subscriptions", "organizations_url": "https://api.github.com/users/bf4/orgs", "repos_url": "https://api.github.com/users/bf4/repos", "events_url": "https://api.github.com/users/bf4/events{/privacy}", "received_events_url": "https://api.github.com/users/bf4/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 2, "created_at": "2013-11-14T02:46:06Z", "updated_at": "2013-11-14T04:18:04Z", "closed_at": "2013-11-14T02:57:03Z", "body": " RubyGems.org doesn't report a license for your gem. This is because it is not specified in the [gemspec](http://docs.rubygems.org/read/chapter/20#license) of your last release.\n\n via e.g.\n\n spec.license = 'MIT'\n # or\n spec.licenses = ['MIT', 'GPL-2']\n\n Including a license in your gemspec is an easy way for rubygems.org and other tools to check how your gem is licensed. As you can imagine, scanning your repository for a LICENSE file or parsing the README, and then attempting to identify the license or licenses is much more difficult and more error prone. So, even for projects that already specify a license, including a license in your gemspec is a good practice. See, for example, how [rubygems.org uses the gemspec to display the rails gem license](https://rubygems.org/gems/rails).\n\n There is even a [License Finder gem](https://github.com/pivotal/LicenseFinder) to help companies/individuals ensure all gems they use meet their licensing needs. This tool depends on license information being available in the gemspec. This is an important enough issue that *even Bundler now generates gems with a default 'MIT' license*.\n\n I hope you'll consider specifying a license in your gemspec. If not, please just close the issue with a nice message. In either case, I'll follow up. Thanks for your time!\n\n Appendix:\n\n If you need help choosing a [license](http://opensource.org/licenses) (sorry, I haven't checked your readme or looked for a license file), GitHub has created a [license picker tool](http://choosealicense.com/). Code without a license specified defaults to 'All rights reserved'-- denying others all rights to use of the code.\n Here's a [list of the license names I've found and their frequencies](https://github.com/bf4/gemproject/blob/master/license_usage.csv)\n\n p.s. In case you're wondering how I found you and why I made this issue, it's because I'm collecting stats on gems (I was originally looking for download data) and decided to collect license metadata,too, and [make issues for gemspecs not specifying a license as a public service :)](https://github.com/bf4/gemproject/issues/1). See the previous link or my [blog post about this project for more information](http://www.benjaminfleischer.com/2013/07/12/make-the-world-a-better-place-put-a-license-in-your-gemspec/).\n" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/83", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/83/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/83/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/83/events", "html_url": "https://github.com/kovyrin/db-charmer/pull/83", "id": 22158156, "number": 83, "title": "Gemspec updates", "user": { "login": "bradherman", "id": 384172, "avatar_url": "https://avatars.githubusercontent.com/u/384172?v=3", "gravatar_id": "", "url": "https://api.github.com/users/bradherman", "html_url": "https://github.com/bradherman", "followers_url": "https://api.github.com/users/bradherman/followers", "following_url": "https://api.github.com/users/bradherman/following{/other_user}", "gists_url": "https://api.github.com/users/bradherman/gists{/gist_id}", "starred_url": "https://api.github.com/users/bradherman/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/bradherman/subscriptions", "organizations_url": "https://api.github.com/users/bradherman/orgs", "repos_url": "https://api.github.com/users/bradherman/repos", "events_url": "https://api.github.com/users/bradherman/events{/privacy}", "received_events_url": "https://api.github.com/users/bradherman/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 1, "created_at": "2013-11-05T22:50:37Z", "updated_at": "2013-11-10T06:31:59Z", "closed_at": "2013-11-10T06:31:59Z", "pull_request": { "url": "https://api.github.com/repos/kovyrin/db-charmer/pulls/83", "html_url": "https://github.com/kovyrin/db-charmer/pull/83", "diff_url": "https://github.com/kovyrin/db-charmer/pull/83.diff", "patch_url": "https://github.com/kovyrin/db-charmer/pull/83.patch" }, "body": "" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/82", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/82/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/82/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/82/events", "html_url": "https://github.com/kovyrin/db-charmer/pull/82", "id": 22002182, "number": 82, "title": "Update db-charmer.gemspec", "user": { "login": "yhuang", "id": 154587, "avatar_url": "https://avatars.githubusercontent.com/u/154587?v=3", "gravatar_id": "", "url": "https://api.github.com/users/yhuang", "html_url": "https://github.com/yhuang", "followers_url": "https://api.github.com/users/yhuang/followers", "following_url": "https://api.github.com/users/yhuang/following{/other_user}", "gists_url": "https://api.github.com/users/yhuang/gists{/gist_id}", "starred_url": "https://api.github.com/users/yhuang/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/yhuang/subscriptions", "organizations_url": "https://api.github.com/users/yhuang/orgs", "repos_url": "https://api.github.com/users/yhuang/repos", "events_url": "https://api.github.com/users/yhuang/events{/privacy}", "received_events_url": "https://api.github.com/users/yhuang/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 1, "created_at": "2013-11-02T14:09:36Z", "updated_at": "2013-11-10T15:41:32Z", "closed_at": "2013-11-10T15:41:32Z", "pull_request": { "url": "https://api.github.com/repos/kovyrin/db-charmer/pulls/82", "html_url": "https://github.com/kovyrin/db-charmer/pull/82", "diff_url": "https://github.com/kovyrin/db-charmer/pull/82.diff", "patch_url": "https://github.com/kovyrin/db-charmer/pull/82.patch" }, "body": "Rails 4.0.1 has been released:\r\n\r\nhttp://weblog.rubyonrails.org/2013/11/1/Rails-4-0-1-has-been-released/" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/81", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/81/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/81/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/81/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/81", "id": 21460804, "number": 81, "title": "Sharded connection key parameter meaning?", "user": { "login": "fabn", "id": 324213, "avatar_url": "https://avatars.githubusercontent.com/u/324213?v=3", "gravatar_id": "", "url": "https://api.github.com/users/fabn", "html_url": "https://github.com/fabn", "followers_url": "https://api.github.com/users/fabn/followers", "following_url": "https://api.github.com/users/fabn/following{/other_user}", "gists_url": "https://api.github.com/users/fabn/gists{/gist_id}", "starred_url": "https://api.github.com/users/fabn/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/fabn/subscriptions", "organizations_url": "https://api.github.com/users/fabn/orgs", "repos_url": "https://api.github.com/users/fabn/repos", "events_url": "https://api.github.com/users/fabn/events{/privacy}", "received_events_url": "https://api.github.com/users/fabn/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 0, "created_at": "2013-10-23T15:14:48Z", "updated_at": "2013-10-23T15:14:48Z", "closed_at": null, "body": "In the readme there is this example for sharded connections\r\n\r\n```ruby\r\nclass Text < ActiveRecord::Base\r\n db_magic :sharded => {\r\n :key => :id,\r\n :sharded_connection => :texts\r\n }\r\nend\r\n```\r\n\r\nWhat is the meaning of `:key` parameter? I saw that it's not used at all in [setup_sharding_magic](https://github.com/kovyrin/db-charmer/blob/master/lib/db_charmer/active_record/db_magic.rb#L53) method\r\n\r\nI assumed that the usage of that field is to compute the shard before saving or when retrieving records, something like \r\n\r\n```ruby\r\nSHARDING_MAP = {\r\n 'US' => :us_users,\r\n 'CA' => :ca_users,\r\n :default => :other_users\r\n}\r\n\r\nDbCharmer::Sharding.register_connection(\r\n :name => :users,\r\n :method => :hash_map,\r\n :map => SHARDING_MAP\r\n)\r\n\r\nclass User < ActiveRecord::Base\r\n db_magic :sharded => {\r\n :key => :locale,\r\n :sharded_connection => :users\r\n }\r\nend\r\n```\r\n\r\nAnd then when creating users\r\n\r\n```ruby\r\nUser.new(locale: 'US').save # this goes to us_users shard\r\nUser.new(locale: 'CA').save # this goes to ca_users shard\r\n```\r\n\r\nBut this kind of code is not working at all, shard must be selected manually before any operation. Am I right? \r\n\r\nIn that case what is the meaning of sharding? The same effect can (almost) be achieved with normal `on_db` method calls?" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/80", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/80/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/80/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/80/events", "html_url": "https://github.com/kovyrin/db-charmer/pull/80", "id": 21106472, "number": 80, "title": "Bump rails to 3.2.15", "user": { "login": "arthurnn", "id": 833383, "avatar_url": "https://avatars.githubusercontent.com/u/833383?v=3", "gravatar_id": "", "url": "https://api.github.com/users/arthurnn", "html_url": "https://github.com/arthurnn", "followers_url": "https://api.github.com/users/arthurnn/followers", "following_url": "https://api.github.com/users/arthurnn/following{/other_user}", "gists_url": "https://api.github.com/users/arthurnn/gists{/gist_id}", "starred_url": "https://api.github.com/users/arthurnn/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/arthurnn/subscriptions", "organizations_url": "https://api.github.com/users/arthurnn/orgs", "repos_url": "https://api.github.com/users/arthurnn/repos", "events_url": "https://api.github.com/users/arthurnn/events{/privacy}", "received_events_url": "https://api.github.com/users/arthurnn/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 0, "created_at": "2013-10-16T20:13:02Z", "updated_at": "2013-10-16T20:13:33Z", "closed_at": "2013-10-16T20:13:33Z", "pull_request": { "url": "https://api.github.com/repos/kovyrin/db-charmer/pulls/80", "html_url": "https://github.com/kovyrin/db-charmer/pull/80", "diff_url": "https://github.com/kovyrin/db-charmer/pull/80.diff", "patch_url": "https://github.com/kovyrin/db-charmer/pull/80.patch" }, "body": "" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/79", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/79/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/79/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/79/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/79", "id": 19604764, "number": 79, "title": "Connection not switching connection consistantly.", "user": { "login": "tysliu", "id": 68217, "avatar_url": "https://avatars.githubusercontent.com/u/68217?v=3", "gravatar_id": "", "url": "https://api.github.com/users/tysliu", "html_url": "https://github.com/tysliu", "followers_url": "https://api.github.com/users/tysliu/followers", "following_url": "https://api.github.com/users/tysliu/following{/other_user}", "gists_url": "https://api.github.com/users/tysliu/gists{/gist_id}", "starred_url": "https://api.github.com/users/tysliu/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/tysliu/subscriptions", "organizations_url": "https://api.github.com/users/tysliu/orgs", "repos_url": "https://api.github.com/users/tysliu/repos", "events_url": "https://api.github.com/users/tysliu/events{/privacy}", "received_events_url": "https://api.github.com/users/tysliu/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 6, "created_at": "2013-09-17T09:49:32Z", "updated_at": "2013-11-14T03:45:53Z", "closed_at": null, "body": "Hi,\r\nI'm eager loading an association for a model. for example Model.where('criteria').includes(:some_association)\r\n\r\nThis association is from another database, I'm finding that if the query for Model.where('criteria') takes too long. our view will throw an error telling us ActionView::Template::Error (Mysql2::Error: Table 'database.some_association' doesn't exist.\r\n\r\nIt would be most appreciated if someone can shed some light on this issue!" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/78", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/78/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/78/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/78/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/78", "id": 19461002, "number": 78, "title": "Kill rails 2 support", "user": { "login": "arthurnn", "id": 833383, "avatar_url": "https://avatars.githubusercontent.com/u/833383?v=3", "gravatar_id": "", "url": "https://api.github.com/users/arthurnn", "html_url": "https://github.com/arthurnn", "followers_url": "https://api.github.com/users/arthurnn/followers", "following_url": "https://api.github.com/users/arthurnn/following{/other_user}", "gists_url": "https://api.github.com/users/arthurnn/gists{/gist_id}", "starred_url": "https://api.github.com/users/arthurnn/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/arthurnn/subscriptions", "organizations_url": "https://api.github.com/users/arthurnn/orgs", "repos_url": "https://api.github.com/users/arthurnn/repos", "events_url": "https://api.github.com/users/arthurnn/events{/privacy}", "received_events_url": "https://api.github.com/users/arthurnn/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 1, "created_at": "2013-09-13T15:59:17Z", "updated_at": "2013-11-10T15:44:43Z", "closed_at": "2013-11-10T15:44:43Z", "body": "If @kovyrin is ok with this idea, I can submit a PR to cleaning up code that is there only because of rails 2.\r\nThoughts on that?" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/77", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/77/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/77/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/77/events", "html_url": "https://github.com/kovyrin/db-charmer/pull/77", "id": 19411140, "number": 77, "title": "Rescue error on drop database on the main db", "user": { "login": "arthurnn", "id": 833383, "avatar_url": "https://avatars.githubusercontent.com/u/833383?v=3", "gravatar_id": "", "url": "https://api.github.com/users/arthurnn", "html_url": "https://github.com/arthurnn", "followers_url": "https://api.github.com/users/arthurnn/followers", "following_url": "https://api.github.com/users/arthurnn/following{/other_user}", "gists_url": "https://api.github.com/users/arthurnn/gists{/gist_id}", "starred_url": "https://api.github.com/users/arthurnn/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/arthurnn/subscriptions", "organizations_url": "https://api.github.com/users/arthurnn/orgs", "repos_url": "https://api.github.com/users/arthurnn/repos", "events_url": "https://api.github.com/users/arthurnn/events{/privacy}", "received_events_url": "https://api.github.com/users/arthurnn/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 1, "created_at": "2013-09-12T20:02:28Z", "updated_at": "2013-10-16T20:08:52Z", "closed_at": "2013-10-16T20:08:52Z", "pull_request": { "url": "https://api.github.com/repos/kovyrin/db-charmer/pulls/77", "html_url": "https://github.com/kovyrin/db-charmer/pull/77", "diff_url": "https://github.com/kovyrin/db-charmer/pull/77.diff", "patch_url": "https://github.com/kovyrin/db-charmer/pull/77.patch" }, "body": "we have this type of config:\r\n\r\n```ruby\r\ndevelopment: &development\r\n database: foo\r\n\r\nbenchmark: &benchmark\r\n database: foo\r\n```\r\n\r\nwhen dropping all it throws an exception as foo was dropped already. " }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/76", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/76/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/76/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/76/events", "html_url": "https://github.com/kovyrin/db-charmer/pull/76", "id": 19410987, "number": 76, "title": "Update .travis.yml", "user": { "login": "arthurnn", "id": 833383, "avatar_url": "https://avatars.githubusercontent.com/u/833383?v=3", "gravatar_id": "", "url": "https://api.github.com/users/arthurnn", "html_url": "https://github.com/arthurnn", "followers_url": "https://api.github.com/users/arthurnn/followers", "following_url": "https://api.github.com/users/arthurnn/following{/other_user}", "gists_url": "https://api.github.com/users/arthurnn/gists{/gist_id}", "starred_url": "https://api.github.com/users/arthurnn/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/arthurnn/subscriptions", "organizations_url": "https://api.github.com/users/arthurnn/orgs", "repos_url": "https://api.github.com/users/arthurnn/repos", "events_url": "https://api.github.com/users/arthurnn/events{/privacy}", "received_events_url": "https://api.github.com/users/arthurnn/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 0, "created_at": "2013-09-12T19:59:31Z", "updated_at": "2013-09-12T20:23:12Z", "closed_at": "2013-09-12T20:22:48Z", "pull_request": { "url": "https://api.github.com/repos/kovyrin/db-charmer/pulls/76", "html_url": "https://github.com/kovyrin/db-charmer/pull/76", "diff_url": "https://github.com/kovyrin/db-charmer/pull/76.diff", "patch_url": "https://github.com/kovyrin/db-charmer/pull/76.patch" }, "body": "use RAILS_VERSION=3.2.14 on travis." }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/75", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/75/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/75/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/75/events", "html_url": "https://github.com/kovyrin/db-charmer/pull/75", "id": 19130612, "number": 75, "title": "Update rails version limits to 3.2.14", "user": { "login": "beedub", "id": 102646, "avatar_url": "https://avatars.githubusercontent.com/u/102646?v=3", "gravatar_id": "", "url": "https://api.github.com/users/beedub", "html_url": "https://github.com/beedub", "followers_url": "https://api.github.com/users/beedub/followers", "following_url": "https://api.github.com/users/beedub/following{/other_user}", "gists_url": "https://api.github.com/users/beedub/gists{/gist_id}", "starred_url": "https://api.github.com/users/beedub/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/beedub/subscriptions", "organizations_url": "https://api.github.com/users/beedub/orgs", "repos_url": "https://api.github.com/users/beedub/repos", "events_url": "https://api.github.com/users/beedub/events{/privacy}", "received_events_url": "https://api.github.com/users/beedub/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 1, "created_at": "2013-09-06T21:56:18Z", "updated_at": "2013-09-06T21:58:53Z", "closed_at": "2013-09-06T21:58:48Z", "pull_request": { "url": "https://api.github.com/repos/kovyrin/db-charmer/pulls/75", "html_url": "https://github.com/kovyrin/db-charmer/pull/75", "diff_url": "https://github.com/kovyrin/db-charmer/pull/75.diff", "patch_url": "https://github.com/kovyrin/db-charmer/pull/75.patch" }, "body": "" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/74", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/74/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/74/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/74/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/74", "id": 16779471, "number": 74, "title": "hasone relationship does not seem to work with db-charmer", "user": { "login": "bobbarjung", "id": 1243200, "avatar_url": "https://avatars.githubusercontent.com/u/1243200?v=3", "gravatar_id": "", "url": "https://api.github.com/users/bobbarjung", "html_url": "https://github.com/bobbarjung", "followers_url": "https://api.github.com/users/bobbarjung/followers", "following_url": "https://api.github.com/users/bobbarjung/following{/other_user}", "gists_url": "https://api.github.com/users/bobbarjung/gists{/gist_id}", "starred_url": "https://api.github.com/users/bobbarjung/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/bobbarjung/subscriptions", "organizations_url": "https://api.github.com/users/bobbarjung/orgs", "repos_url": "https://api.github.com/users/bobbarjung/repos", "events_url": "https://api.github.com/users/bobbarjung/events{/privacy}", "received_events_url": "https://api.github.com/users/bobbarjung/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 0, "created_at": "2013-07-15T21:16:41Z", "updated_at": "2014-06-11T21:06:07Z", "closed_at": null, "body": "I have a has_one relationship between A and B\r\n\r\n```ruby\r\nclass A < ActiveRecord::Base\r\n has_one b\r\nend\r\n\r\nclass B < ActiveRecord::Base\r\n belongs_to a\r\nend\r\n```\r\n\r\nI want to access the associations on a secondary db. (Called recovery) using db-charmer.\r\n\r\n```ruby\r\nb = B.on_db(:recovery).find(b_id) # works\r\nb.on_db(:recovery).a # works. a is not nil.\r\n\r\na = A.on_db(:recovery).find(a_id) # works.\r\na.on_db(:recovery).b # does not work,\r\n```\r\n...as it tries to lookup the main db and not the recovery database for the reverse lookup. I am guessing has_many also will not work? I am using db-charmer version 1.8.4.\r\n\r\nThank you for your help." }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/73", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/73/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/73/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/73/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/73", "id": 16121766, "number": 73, "title": "Multi-Threading: with_remapped_databases fails to remap classes", "user": { "login": "chadrem", "id": 22150, "avatar_url": "https://avatars.githubusercontent.com/u/22150?v=3", "gravatar_id": "", "url": "https://api.github.com/users/chadrem", "html_url": "https://github.com/chadrem", "followers_url": "https://api.github.com/users/chadrem/followers", "following_url": "https://api.github.com/users/chadrem/following{/other_user}", "gists_url": "https://api.github.com/users/chadrem/gists{/gist_id}", "starred_url": "https://api.github.com/users/chadrem/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/chadrem/subscriptions", "organizations_url": "https://api.github.com/users/chadrem/orgs", "repos_url": "https://api.github.com/users/chadrem/repos", "events_url": "https://api.github.com/users/chadrem/events{/privacy}", "received_events_url": "https://api.github.com/users/chadrem/received_events", "type": "User", "site_admin": false }, "labels": [ { "url": "https://api.github.com/repos/kovyrin/db-charmer/labels/reproducible-issue", "name": "reproducible-issue", "color": "e10c02" } ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 4, "created_at": "2013-06-28T00:27:00Z", "updated_at": "2014-06-11T21:01:35Z", "closed_at": "2014-06-11T21:01:35Z", "body": "I started digging into the problem and noticed that new threads are unable to find the map. Because of this, no classes get re-mapped and thus look for data in the wrong database.\r\n\r\n#### Main thread works correctly (it finds the map).\r\n>> puts ::ActiveRecord::Base.db_charmer_database_remappings.inspect\r\n{:limbo=>:realm_0}\r\n\r\n#### New threads don't work (unable to find the above map).\r\n>> Thread.new { puts ::ActiveRecord::Base.db_charmer_database_remappings.inspect }\r\n{}" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/72", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/72/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/72/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/72/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/72", "id": 15789793, "number": 72, "title": "Difference in schemas between master and slave affects whole app.", "user": { "login": "andriytyurnikov", "id": 3668, "avatar_url": "https://avatars.githubusercontent.com/u/3668?v=3", "gravatar_id": "", "url": "https://api.github.com/users/andriytyurnikov", "html_url": "https://github.com/andriytyurnikov", "followers_url": "https://api.github.com/users/andriytyurnikov/followers", "following_url": "https://api.github.com/users/andriytyurnikov/following{/other_user}", "gists_url": "https://api.github.com/users/andriytyurnikov/gists{/gist_id}", "starred_url": "https://api.github.com/users/andriytyurnikov/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/andriytyurnikov/subscriptions", "organizations_url": "https://api.github.com/users/andriytyurnikov/orgs", "repos_url": "https://api.github.com/users/andriytyurnikov/repos", "events_url": "https://api.github.com/users/andriytyurnikov/events{/privacy}", "received_events_url": "https://api.github.com/users/andriytyurnikov/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 1, "created_at": "2013-06-20T09:39:08Z", "updated_at": "2013-11-10T15:49:36Z", "closed_at": "2013-11-10T15:49:36Z", "body": "We had long-running migration on db master (lhm gem), and as one might expect there was some delay between master and slave.\r\nSo when migration finished on master, but was in progress on slave - we had different schemas.\r\nUnexpected surprise is that this difference affected parts of the app which do not use slave connection.\r\n\r\nMysql2::Error: Unknown column 'referrer' in 'field list': INSERT INTO `purchases` (`coupon_code`, `created_at`, `customer_id`, `dont_redeem`, `email`, `encrypted_email`, `ip_address`, `order_date`, `order_number`, `referrer`, `site_id`, `subtotal`, `updated_at`, `visitor_id`) VALUES ('CODE42', '2013-06-19 12:19:51', NULL, 0, 'email@gmail.com', NULL, '127.0.0.1', '2013-06-19 12:19:51', '100379070', NULL, 1887, 123.2, '2013-06-19 12:19:51', 26179393)\r\n\r\nAt the moment of the exception raise referrer column was present on master, but was not present on slave. Unexpected surprise here, is that controller, which raised this exception does not use db-charmer slave. Somehow db-charmer slave connection affects operations on master connection (schema info cached somewhere by activerecord?)" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/71", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/71/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/71/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/71/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/71", "id": 15777336, "number": 71, "title": "Forcing writes to master in bang methods (create!, etc)", "user": { "login": "kpumuk", "id": 10163, "avatar_url": "https://avatars.githubusercontent.com/u/10163?v=3", "gravatar_id": "", "url": "https://api.github.com/users/kpumuk", "html_url": "https://github.com/kpumuk", "followers_url": "https://api.github.com/users/kpumuk/followers", "following_url": "https://api.github.com/users/kpumuk/following{/other_user}", "gists_url": "https://api.github.com/users/kpumuk/gists{/gist_id}", "starred_url": "https://api.github.com/users/kpumuk/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/kpumuk/subscriptions", "organizations_url": "https://api.github.com/users/kpumuk/orgs", "repos_url": "https://api.github.com/users/kpumuk/repos", "events_url": "https://api.github.com/users/kpumuk/events{/privacy}", "received_events_url": "https://api.github.com/users/kpumuk/received_events", "type": "User", "site_admin": false }, "labels": [ { "url": "https://api.github.com/repos/kovyrin/db-charmer/labels/reproducible-issue", "name": "reproducible-issue", "color": "e10c02" } ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 1, "created_at": "2013-06-20T01:27:59Z", "updated_at": "2013-06-20T01:31:16Z", "closed_at": null, "body": "Currently db-charmer forces writes to master for `create`, `update`, `delete`, and some other ActiveRecord methods. Unfortunately, `create!` does not get forced to use master:\r\n\r\n ree-1.8.7-2012.02 >> TestModel.on_db(:slave).create\r\n SQL (0.2ms) BEGIN\r\n \r\n ree-1.8.7-2012.02 >> TestModel.on_db(:slave).create!\r\n [slave] SQL (0.1ms) BEGIN\r\n\r\nIt seems like problematic code is in `master_slave_routing.rb` for Rails 2.x" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/70", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/70/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/70/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/70/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/70", "id": 15004735, "number": 70, "title": "Support for sharing a connection to same server / switching databases as necessary", "user": { "login": "boourns", "id": 699550, "avatar_url": "https://avatars.githubusercontent.com/u/699550?v=3", "gravatar_id": "", "url": "https://api.github.com/users/boourns", "html_url": "https://github.com/boourns", "followers_url": "https://api.github.com/users/boourns/followers", "following_url": "https://api.github.com/users/boourns/following{/other_user}", "gists_url": "https://api.github.com/users/boourns/gists{/gist_id}", "starred_url": "https://api.github.com/users/boourns/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/boourns/subscriptions", "organizations_url": "https://api.github.com/users/boourns/orgs", "repos_url": "https://api.github.com/users/boourns/repos", "events_url": "https://api.github.com/users/boourns/events{/privacy}", "received_events_url": "https://api.github.com/users/boourns/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "open", "locked": false, "assignee": null, "milestone": null, "comments": 2, "created_at": "2013-05-31T17:41:34Z", "updated_at": "2013-05-31T18:48:02Z", "closed_at": null, "body": "We're considering putting multiple logical databases on the same physical server. \r\n\r\nIf we do that we will hit an issue where every app worker will have many connections to the same mysql server and reach the point of having context switching issues from the mysql perspective.\r\n\r\nWhat do you think about reusing db_charmer connections when there is multiple logical databases on the same physical server?\r\n\r\nIf this is not something you can add we may be doing it at Shopify and would be interested in your view on this & what you would consider to be the best approach.\r\n\r\nThanks\r\nTom" }, { "url": "https://api.github.com/repos/kovyrin/db-charmer/issues/69", "labels_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/69/labels{/name}", "comments_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/69/comments", "events_url": "https://api.github.com/repos/kovyrin/db-charmer/issues/69/events", "html_url": "https://github.com/kovyrin/db-charmer/issues/69", "id": 14541052, "number": 69, "title": "execution expired - timing out getting connections - multi-threading issue", "user": { "login": "dashbitla", "id": 114106, "avatar_url": "https://avatars.githubusercontent.com/u/114106?v=3", "gravatar_id": "", "url": "https://api.github.com/users/dashbitla", "html_url": "https://github.com/dashbitla", "followers_url": "https://api.github.com/users/dashbitla/followers", "following_url": "https://api.github.com/users/dashbitla/following{/other_user}", "gists_url": "https://api.github.com/users/dashbitla/gists{/gist_id}", "starred_url": "https://api.github.com/users/dashbitla/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/dashbitla/subscriptions", "organizations_url": "https://api.github.com/users/dashbitla/orgs", "repos_url": "https://api.github.com/users/dashbitla/repos", "events_url": "https://api.github.com/users/dashbitla/events{/privacy}", "received_events_url": "https://api.github.com/users/dashbitla/received_events", "type": "User", "site_admin": false }, "labels": [ ], "state": "closed", "locked": false, "assignee": null, "milestone": null, "comments": 2, "created_at": "2013-05-20T21:43:35Z", "updated_at": "2013-11-10T15:52:05Z", "closed_at": "2013-11-10T15:52:05Z", "body": "2013-05-20T21:29:11Z 6058 TID-osusvs4o8 WARN: execution expired\r\n2013-05-20T21:29:11Z 6058 TID-osusvs4o8 WARN: /home/deployer/.rvm/rubies/ruby-1.9.3-p429/lib/ruby/1.9.1/monitor.rb:185:in `lock'\r\n/home/deployer/.rvm/rubies/ruby-1.9.3-p429/lib/ruby/1.9.1/monitor.rb:185:in `mon_enter'\r\n/home/deployer/.rvm/rubies/ruby-1.9.3-p429/lib/ruby/1.9.1/monitor.rb:209:in `mon_synchronize'\r\n/home/deployer/apps/testapp.com/shared/bundle/ruby/1.9.1/gems/activerecord-3.2.8/lib/active_record/connection_adapters/abstract/connection_pool.rb:95:in `connection'\r\n/home/deployer/apps/testapp.com/shared/bundle/ruby/1.9.1/gems/activerecord-3.2.8/lib/active_record/connection_adapters/abstract/connection_pool.rb:404:in `retrieve_connection'\r\n/home/deployer/apps/testapp.com/shared/bundle/ruby/1.9.1/gems/activerecord-3.2.8/lib/active_record/connection_adapters/abstract/connection_specification.rb:170:in `retrieve_connection'\r\n/home/deployer/apps/testapp.com/shared/bundle/ruby/1.9.1/gems/activerecord-3.2.8/lib/active_record/connection_adapters/abstract/connection_specification.rb:144:in `connection'\r\n/home/deployer/apps/testapp.com/releases/20130519223524/vendor/gems/db-charmer-1.9.0/lib/db_charmer/active_record/connection_switching.rb:34:in `connection_with_magic'\r\n/home/deployer/apps/testapp.com/releases/20130519223524/vendor/gems/db-charmer-1.9.0/lib/db_charmer/rails3/active_record/relation_method.rb:15:in `block in relation_with_db_charmer'\r\n/home/deployer/apps/testapp.com/releases/20130519223524/vendor/gems/db-charmer-1.9.0/lib/db_charmer/rails3/active_record/relation_method.rb:14:in `tap'\r\n/home/deployer/apps/testapp.com/releases/20130519223524/vendor/gems/db-charmer-1.9.0/lib/db_charmer/rails3/active_record/relation_method.rb:14:in `relation_with_db_charmer'\r\n/home/deployer/apps/testapp.com/shared/bundle/ruby/1.9.1/gems/activerecord-3.2.8/lib/active_record/scoping/named.rb:37:in `scoped'\r\n/home/deployer/apps/testapp.com/shared/bundle/ruby/1.9.1/gems/activerecord-3.2.8/lib/active_record/querying.rb:9:in `select'\r\n/home/deployer/apps/testapp.com/releases/20130519223524/app/models/api_callback.rb:12:in `push'\r\n/home/deployer/apps/testapp.com/releases/20130519223524/app/workers/kiq_agent_callback.rb:9:in `perform'\r\n/home/deployer/apps/testapp.com/shared/bundle/ruby/1.9.1/gems/sidekiq-2.9.0/lib/sidekiq/processor.rb:49:in `block (3 levels)\r\n\r\n" } ] ================================================ FILE: lib/db_charmer/action_controller/force_slave_reads.rb ================================================ module DbCharmer module ActionController module ForceSlaveReads module ClassMethods @@db_charmer_force_slave_reads_actions = {} def force_slave_reads(params = {}) @@db_charmer_force_slave_reads_actions[self.name] = { :except => params[:except] ? [*params[:except]].map(&:to_s) : [], :only => params[:only] ? [*params[:only]].map(&:to_s) : [] } end def force_slave_reads_options @@db_charmer_force_slave_reads_actions[self.name] end def force_slave_reads_action?(name = nil) name = name.to_s options = force_slave_reads_options # If no options were defined for this controller, all actions are not forced to use slaves return false unless options # Actions where force_slave_reads mode was turned off return false if options[:except].include?(name) # Only for these actions force_slave_reads was turned on return options[:only].include?(name) if options[:only].any? # If :except is not empty, we're done with the checks and rest of the actions are should force slave reads # Otherwise, all the actions are not in force_slave_reads mode options[:except].any? end end module InstanceMethods DISPATCH_METHOD = (DbCharmer.rails3?) ? :process_action : :perform_action def self.included(base) base.alias_method_chain DISPATCH_METHOD, :forced_slave_reads end def force_slave_reads! @db_charmer_force_slave_reads = true end def dont_force_slave_reads! @db_charmer_force_slave_reads = false end def force_slave_reads? @db_charmer_force_slave_reads || self.class.force_slave_reads_action?(params[:action]) end protected class_eval <<-EOF, __FILE__, __LINE__+1 def #{DISPATCH_METHOD}_with_forced_slave_reads(*args, &block) DbCharmer.with_controller(self) do #{DISPATCH_METHOD}_without_forced_slave_reads(*args, &block) end end EOF end end end end ================================================ FILE: lib/db_charmer/active_record/association_preload.rb ================================================ module DbCharmer module ActiveRecord module AssociationPreload ASSOCIATION_TYPES = [ :has_one, :has_many, :belongs_to, :has_and_belongs_to_many ] def self.extended(base) ASSOCIATION_TYPES.each do |association_type| base.class_eval <<-EOF, __FILE__, __LINE__ + 1 def self.preload_#{association_type}_association(records, reflection, preload_options = {}) if self.db_charmer_top_level_connection? || reflection.options[:polymorphic] || self.db_charmer_default_connection != reflection.klass.db_charmer_default_connection return super(records, reflection, preload_options) end reflection.klass.on_db(self) do super(records, reflection, preload_options) end end EOF end end end end end ================================================ FILE: lib/db_charmer/active_record/class_attributes.rb ================================================ module DbCharmer module ActiveRecord module ClassAttributes @@db_charmer_opts = {} def db_charmer_opts=(opts) @@db_charmer_opts[self.name] = opts end def db_charmer_opts @@db_charmer_opts[self.name] || {} end #--------------------------------------------------------------------------------------------- @@db_charmer_default_connections = {} def db_charmer_default_connection=(conn) @@db_charmer_default_connections[self.name] = conn end def db_charmer_default_connection @@db_charmer_default_connections[self.name] end #--------------------------------------------------------------------------------------------- @@db_charmer_slaves = {} def db_charmer_slaves=(slaves) @@db_charmer_slaves[self.name] = slaves end def db_charmer_slaves @@db_charmer_slaves[self.name] || [] end # Returns a random connection from the list of slaves configured for this AR class def db_charmer_random_slave return nil unless db_charmer_slaves.any? db_charmer_slaves[rand(db_charmer_slaves.size)] end #--------------------------------------------------------------------------------------------- def db_charmer_connection_proxies Thread.current[:db_charmer_connection_proxies] ||= {} end def db_charmer_connection_proxy=(proxy) db_charmer_connection_proxies[self.name] = proxy end def db_charmer_connection_proxy db_charmer_connection_proxies[self.name] end #--------------------------------------------------------------------------------------------- def db_charmer_force_slave_reads_flags Thread.current[:db_charmer_force_slave_reads] ||= {} end def db_charmer_force_slave_reads=(force) db_charmer_force_slave_reads_flags[self.name] = force end def db_charmer_force_slave_reads db_charmer_force_slave_reads_flags[self.name] end # Slave reads are used in two cases: # - per-model slave reads are enabled (see db_magic method for more details) # - global slave reads enforcing is enabled (in a controller action) def db_charmer_force_slave_reads? db_charmer_force_slave_reads || DbCharmer.force_slave_reads? end #--------------------------------------------------------------------------------------------- def db_charmer_connection_levels Thread.current[:db_charmer_connection_levels] ||= Hash.new(0) end def db_charmer_connection_level=(level) db_charmer_connection_levels[self.name] = level end def db_charmer_connection_level db_charmer_connection_levels[self.name] || 0 end def db_charmer_top_level_connection? db_charmer_connection_level.zero? end #--------------------------------------------------------------------------------------------- def db_charmer_remapped_connection return nil unless db_charmer_top_level_connection? name = :master proxy = db_charmer_model_connection_proxy name = proxy.db_charmer_connection_name.to_sym if proxy remapped = db_charmer_database_remappings[name] remapped ? DbCharmer::ConnectionFactory.connect(remapped, true) : nil end def db_charmer_database_remappings Thread.current[:db_charmer_database_remappings] ||= Hash.new end def db_charmer_database_remappings=(mappings) raise "Mappings must be nil or respond to []" if mappings && (! mappings.respond_to?(:[])) Thread.current[:db_charmer_database_remappings] = mappings || {} end #--------------------------------------------------------------------------------------------- # Returns model-specific connection proxy, ignoring any global connection remappings def db_charmer_model_connection_proxy db_charmer_connection_proxy || db_charmer_default_connection end end end end ================================================ FILE: lib/db_charmer/active_record/connection_switching.rb ================================================ module DbCharmer module ActiveRecord module ConnectionSwitching def establish_real_connection_if_exists(name, should_exist = false) name = name.to_s # Check environment name config = configurations[DbCharmer.env] unless config error = "Invalid environment name (does not exist in database.yml): #{DbCharmer.env}. Please set correct Rails.env or DbCharmer.env." raise ArgumentError, error end # Check connection name config = config[name] unless config if should_exist raise ArgumentError, "Invalid connection name (does not exist in database.yml): #{DbCharmer.env}/#{name}" end return # No need to establish connection - they do not want us to end # Pass connection name with config config[:connection_name] = name establish_connection(config) end #----------------------------------------------------------------------------------------------------------------- def hijack_connection! return if self.respond_to?(:connection_with_magic) class << self # Make sure we check our accessors before going to the default connection retrieval method def connection_with_magic db_charmer_remapped_connection || db_charmer_model_connection_proxy || connection_without_magic end alias_method_chain :connection, :magic def connection_pool_with_magic if connection.respond_to?(:abstract_connection_class) abstract_connection_class = connection.abstract_connection_class connection_handler.retrieve_connection_pool(abstract_connection_class) || connection_pool_without_magic else connection_pool_without_magic end end alias_method_chain :connection_pool, :magic end end #----------------------------------------------------------------------------------------------------------------- def coerce_to_connection_proxy(conn, should_exist = true) # Return nil if given no connection specification return nil if conn.nil? # For sharded proxies just use them as-is return conn if conn.respond_to?(:set_real_connection) # For connection proxies and objects that could be coerced into a proxy just call the coercion method return conn.db_charmer_connection_proxy if conn.respond_to?(:db_charmer_connection_proxy) # For plain AR connection adapters, just use them as-is return conn if conn.kind_of?(::ActiveRecord::ConnectionAdapters::AbstractAdapter) # For connection names, use connection factory to create new connections if conn.kind_of?(Symbol) || conn.kind_of?(String) return DbCharmer::ConnectionFactory.connect(conn, should_exist) end # For connection configs (hashes), create connections if conn.kind_of?(Hash) conn = conn.symbolize_keys raise ArgumentError, "Missing required :connection_name parameter" unless conn[:connection_name] return DbCharmer::ConnectionFactory.connect_to_db(conn[:connection_name], conn) end # Fails for unsupported connection types raise "Unsupported connection type: #{conn.class}" end #----------------------------------------------------------------------------------------------------------------- def switch_connection_to(conn, should_exist = true) new_conn = coerce_to_connection_proxy(conn, should_exist) if db_charmer_connection_proxy.respond_to?(:set_real_connection) db_charmer_connection_proxy.set_real_connection(new_conn) end self.db_charmer_connection_proxy = new_conn self.hijack_connection! end end end end ================================================ FILE: lib/db_charmer/active_record/db_magic.rb ================================================ module DbCharmer module ActiveRecord module DbMagic def db_magic(opt = {}) # Make sure we could use our connections management here hijack_connection! # Should requested connections exist in the config? should_exist = opt.has_key?(:should_exist) ? opt[:should_exist] : DbCharmer.connections_should_exist? # Main connection management setup_connection_magic(opt[:connection], should_exist) # Set up slaves pool opt[:slaves] ||= [] opt[:slaves] = [ opt[:slaves] ].flatten opt[:slaves] << opt[:slave] if opt[:slave] # Forced reads are enabled for all models by default, could be disabled by the user forced_slave_reads = opt.has_key?(:force_slave_reads) ? opt[:force_slave_reads] : true # Setup all the slaves related magic if needed setup_slaves_magic(opt[:slaves], forced_slave_reads, should_exist) # Setup inheritance magic setup_children_magic(opt) # Setup sharding if needed if opt[:sharded] raise ArgumentError, "Can't use sharding on a model with slaves!" if opt[:slaves].any? setup_sharding_magic(opt[:sharded]) end end private def setup_children_magic(opt) self.db_charmer_opts = opt.clone unless self.respond_to?(:inherited_with_db_magic) class << self def inherited_with_db_magic(child) o = inherited_without_db_magic(child) child.db_magic(self.db_charmer_opts) o end alias_method_chain :inherited, :db_magic end end end def setup_sharding_magic(config) # Add sharding-specific methods self.extend(DbCharmer::ActiveRecord::Sharding) # Get configuration name = config[:sharded_connection] or raise ArgumentError, "No :sharded_connection!" # Assign sharded connection self.sharded_connection = DbCharmer::Sharding.sharded_connection(name) # Setup model default connection setup_connection_magic(sharded_connection.default_connection) end def setup_connection_magic(conn, should_exist = true) conn_proxy = coerce_to_connection_proxy(conn, should_exist) self.db_charmer_default_connection = conn_proxy switch_connection_to(conn_proxy, should_exist) end def setup_slaves_magic(slaves, force_slave_reads, should_exist = true) self.db_charmer_force_slave_reads = force_slave_reads # Initialize the slave connections list self.db_charmer_slaves = slaves.collect do |slave| coerce_to_connection_proxy(slave, should_exist) end return if db_charmer_slaves.empty? # Enable on_slave/on_master methods self.extend(DbCharmer::ActiveRecord::MultiDbProxy::MasterSlaveClassMethods) # Enable automatic master/slave queries routing (we have specialized versions on those modules for rails2/3) self.extend(DbCharmer::ActiveRecord::MasterSlaveRouting::ClassMethods) self.send(:include, DbCharmer::ActiveRecord::MasterSlaveRouting::InstanceMethods) end end end end ================================================ FILE: lib/db_charmer/active_record/migration/multi_db_migrations.rb ================================================ module DbCharmer module ActiveRecord module Migration module MultiDbMigrations def self.append_features(base) return false if base < self super base.extend const_get("ClassMethods") if const_defined?("ClassMethods") base.class_eval do if DbCharmer.rails31? alias_method_chain :migrate, :db_wrapper else class << self alias_method_chain :migrate, :db_wrapper end end end end module ClassMethods @@multi_db_names = {} def multi_db_names @@multi_db_names[self.name] || @@multi_db_names['ActiveRecord::Migration'] end def multi_db_names=(names) @@multi_db_names[self.name] = names end unless DbCharmer.rails31? def migrate_with_db_wrapper(direction) if names = multi_db_names names.each do |multi_db_name| on_db(multi_db_name) do migrate_without_db_wrapper(direction) end end else migrate_without_db_wrapper(direction) end end def on_db(db_name) name = db_name.is_a?(Hash) ? db_name[:connection_name] : db_name.inspect announce "Switching connection to #{name}" # Switch connection old_proxy = ::ActiveRecord::Base.db_charmer_connection_proxy db_name = nil if db_name == :default ::ActiveRecord::Base.switch_connection_to(db_name, DbCharmer.connections_should_exist?) # Yield the block yield ensure # Switch it back ::ActiveRecord::Base.verify_active_connections! announce "Switching connection back" ::ActiveRecord::Base.switch_connection_to(old_proxy) end end def db_magic(opts = {}) # Collect connections from all possible options conns = [ opts[:connection], opts[:connections] ] conns << shard_connections(opts[:sharded_connection]) if opts[:sharded_connection] # Get a unique set of connections conns = conns.flatten.compact.uniq raise ArgumentError, "No connection name - no magic!" unless conns.any? # Save connections self.multi_db_names = conns end # Return a list of connections to shards in a sharded connection def shard_connections(conn_name) conn = DbCharmer::Sharding.sharded_connection(conn_name) conn.shard_connections end end def migrate_with_db_wrapper(direction) if names = self.class.multi_db_names names.each do |multi_db_name| on_db(multi_db_name) do migrate_without_db_wrapper(direction) end end else migrate_without_db_wrapper(direction) end end def record_on_db(db_name, block) recorder = ::ActiveRecord::Migration::CommandRecorder.new(DbCharmer::ConnectionFactory.connect(db_name)) old_recorder, @connection = @connection, recorder block.call old_recorder.record :on_db, [db_name, @connection] @connection = old_recorder end def replay_commands_on_db(name, commands) on_db(name) do commands.each do |cmd, args| send(cmd, *args) end end end def on_db(db_name, &block) if @connection.is_a?(::ActiveRecord::Migration::CommandRecorder) record_on_db(db_name, block) return end name = db_name.is_a?(Hash) ? db_name[:connection_name] : db_name.inspect announce "Switching connection to #{name}" # Switch connection old_connection, old_proxy = @connection, ::ActiveRecord::Base.db_charmer_connection_proxy db_name = nil if db_name == :default ::ActiveRecord::Base.switch_connection_to(db_name, DbCharmer.connections_should_exist?) # Yield the block ::ActiveRecord::Base.connection_pool.with_connection do |conn| @connection = conn yield end ensure @connection = old_connection # Switch it back ::ActiveRecord::Base.verify_active_connections! announce "Switching connection back" ::ActiveRecord::Base.switch_connection_to(old_proxy) end end end end end ================================================ FILE: lib/db_charmer/active_record/multi_db_proxy.rb ================================================ module DbCharmer module ActiveRecord module MultiDbProxy # Simple proxy class that switches connections and then proxies all the calls # This class is used to implement chained on_db calls class OnDbProxy < ActiveSupport::BasicObject # We need to do this because in Rails 2.3 BasicObject does not remove object_id method, which is stupid undef_method(:object_id) if instance_methods.member?('object_id') def initialize(proxy_target, slave) @proxy_target = proxy_target @slave = slave end private def method_missing(meth, *args, &block) # Switch connection and proxy the method call @proxy_target.on_db(@slave) do |proxy_target| res = proxy_target.__send__(meth, *args, &block) # If result is a scope/association, return a new proxy for it, otherwise return the result itself (res.proxy?) ? OnDbProxy.new(res, @slave) : res end end end module ClassMethods def on_db(con, proxy_target = nil) proxy_target ||= self # Chain call return OnDbProxy.new(proxy_target, con) unless block_given? # Block call begin self.db_charmer_connection_level += 1 old_proxy = db_charmer_connection_proxy switch_connection_to(con, DbCharmer.connections_should_exist?) yield(proxy_target) ensure switch_connection_to(old_proxy) self.db_charmer_connection_level -= 1 end end end module InstanceMethods def on_db(con, proxy_target = nil, &block) proxy_target ||= self self.class.on_db(con, proxy_target, &block) end end module MasterSlaveClassMethods def on_slave(con = nil, proxy_target = nil, &block) con ||= db_charmer_random_slave raise ArgumentError, "No slaves found in the class and no slave connection given" unless con on_db(con, proxy_target, &block) end def on_master(proxy_target = nil, &block) on_db(db_charmer_default_connection, proxy_target, &block) end def first_level_on_slave first_level = db_charmer_top_level_connection? && on_master.connection.open_transactions.zero? if first_level && db_charmer_force_slave_reads? && db_charmer_slaves.any? on_slave { yield } else yield end end end end end end ================================================ FILE: lib/db_charmer/active_record/sharding.rb ================================================ module DbCharmer module ActiveRecord module Sharding def self.extended(model) model.cattr_accessor(:sharded_connection) end def shard_for(key, proxy_target = nil, &block) raise ArgumentError, "No sharded connection configured!" unless sharded_connection conn = sharded_connection.sharder.shard_for_key(key) on_db(conn, proxy_target, &block) end # Run on default shard (if supported by the sharding method) def on_default_shard(proxy_target = nil, &block) raise ArgumentError, "No sharded connection configured!" unless sharded_connection if sharded_connection.support_default_shard? shard_for(:default, proxy_target, &block) else raise ArgumentError, "This model's sharding method does not support default shard" end end # Enumerate shards def on_each_shard(proxy_target = nil, &block) raise ArgumentError, "No sharded connection configured!" unless sharded_connection conns = sharded_connection.shard_connections raise ArgumentError, "This model's sharding method does not support shards enumeration" unless conns conns.each do |conn| on_db(conn, proxy_target, &block) end end end end end ================================================ FILE: lib/db_charmer/connection_factory.rb ================================================ # # This class is used to automatically generate small abstract ActiveRecord classes # that would then be used as a source of database connections for DbCharmer magic. # This way we do not need to re-implement all the connection establishing code # that ActiveRecord already has and we make our code less dependant on Rails versions. # module DbCharmer module ConnectionFactory def self.connection_classes Thread.current[:db_charmer_generated_connection_classes] ||= {} end def self.connection_classes=(val) Thread.current[:db_charmer_generated_connection_classes] = val end def self.reset! self.connection_classes = {} end # Establishes connection or return an existing one from cache def self.connect(connection_name, should_exist = true) connection_name = connection_name.to_s connection_classes[connection_name] ||= establish_connection(connection_name, should_exist) end # Establishes connection or return an existing one from cache (not using AR database configs) def self.connect_to_db(connection_name, config) connection_name = connection_name.to_s connection_classes[connection_name] ||= establish_connection_to_db(connection_name, config) end # Establish connection with a specified name def self.establish_connection(connection_name, should_exist = true) abstract_class = generate_abstract_class(connection_name, should_exist) DbCharmer::ConnectionProxy.new(abstract_class, connection_name) end # Establish connection with a specified name (not using AR database configs) def self.establish_connection_to_db(connection_name, config) abstract_class = generate_abstract_class_for_db(connection_name, config) DbCharmer::ConnectionProxy.new(abstract_class, connection_name) end # Generate an abstract AR class with specified connection established def self.generate_abstract_class(connection_name, should_exist = true) # Generate class klass = generate_empty_abstract_ar_class(abstract_connection_class_name(connection_name)) # Establish connection klass.establish_real_connection_if_exists(connection_name.to_sym, !!should_exist) # Return the class return klass end # Generate an abstract AR class with specified connection established (not using AR database configs) def self.generate_abstract_class_for_db(connection_name, config) # Generate class klass = generate_empty_abstract_ar_class(abstract_connection_class_name(connection_name)) # Establish connection klass.establish_connection(config) # Return the class return klass end def self.generate_empty_abstract_ar_class(klass) # Define class module_eval "class #{klass} < ::ActiveRecord::Base; self.abstract_class = true; end" # Return class klass.constantize end # Generates unique names for our abstract AR classes def self.abstract_connection_class_name(connection_name) conn_name_klass = connection_name.to_s.gsub(/\W+/, '_').camelize thread = Thread.current.object_id.abs # need to make sure it is non-negative "::AutoGeneratedAbstractConnectionClass#{conn_name_klass}ForThread#{thread}" end end end ================================================ FILE: lib/db_charmer/connection_proxy.rb ================================================ # Simple proxy that sends all method calls to a real database connection module DbCharmer class ConnectionProxy < ActiveSupport::BasicObject # We need to do this because in Rails 2.3 BasicObject does not remove object_id method, which is stupid undef_method(:object_id) if instance_methods.member?('object_id') # We use this to get a connection class from the proxy attr_accessor :abstract_connection_class def initialize(abstract_class, db_name) @abstract_connection_class = abstract_class @db_name = db_name end def db_charmer_connection_name @db_name end def db_charmer_connection_proxy self end def db_charmer_retrieve_connection @abstract_connection_class.retrieve_connection end def nil? false end #----------------------------------------------------------------------------------------------- RESPOND_TO_METHODS = [ :abstract_connection_class, :db_charmer_connection_name, :db_charmer_connection_proxy, :db_charmer_retrieve_connection, :nil? ].freeze # Short-circuit some of the methods for which we know there is a separate check in coercion code DOESNT_RESPOND_TO_METHODS = [ :set_real_connection ].freeze def respond_to?(method_name, include_all = false) return true if RESPOND_TO_METHODS.include?(method_name) return false if DOESNT_RESPOND_TO_METHODS.include?(method_name) db_charmer_retrieve_connection.respond_to?(method_name, include_all) end #----------------------------------------------------------------------------------------------- def method_missing(meth, *args, &block) db_charmer_retrieve_connection.send(meth, *args, &block) end end end ================================================ FILE: lib/db_charmer/core_extensions.rb ================================================ class Object unless defined?(try) def try(method, *options, &block) send(method, *options, &block) end end # These methods are added to all objects so we could call proxy? on anything # and figure if an object is a proxy w/o hitting method_missing or respond_to? def self.proxy? false end def proxy? false end end class NilClass def try(*args) nil end end ================================================ FILE: lib/db_charmer/force_slave_reads.rb ================================================ module DbCharmer def self.current_controller Thread.current[:db_charmer_current_controller] end def self.current_controller=(val) Thread.current[:db_charmer_current_controller] = val end #------------------------------------------------------------------------------------------------- def self.forced_slave_reads_setting Thread.current[:db_charmer_forced_slave_reads] end def self.forced_slave_reads_setting=(val) Thread.current[:db_charmer_forced_slave_reads] = val end #------------------------------------------------------------------------------------------------- def self.force_slave_reads? # If global force slave reads is requested, do it return true if Thread.current[:db_charmer_forced_slave_reads] # If not, try to use current controller to decide on this return false unless current_controller.respond_to?(:force_slave_reads?) slave_reads = current_controller.force_slave_reads? logger.debug("Using controller to figure out if slave reads should be forced: #{slave_reads}") return slave_reads end #------------------------------------------------------------------------------------------------- def self.with_controller(controller) raise ArgumentError, "No block given" unless block_given? logger.debug("Setting current controller for db_charmer: #{controller.class.name}") self.current_controller = controller yield ensure logger.debug('Clearing current controller for db_charmer') self.current_controller = nil end #------------------------------------------------------------------------------------------------- # Force all reads in a block of code to go to a slave def self.force_slave_reads raise ArgumentError, "No block given" unless block_given? old_forced_slave_reads = self.forced_slave_reads_setting begin self.forced_slave_reads_setting = true yield ensure self.forced_slave_reads_setting = old_forced_slave_reads end end end ================================================ FILE: lib/db_charmer/rails2/abstract_adapter/log_formatting.rb ================================================ module DbCharmer module AbstractAdapter module LogFormatting def self.included(base) base.alias_method_chain :format_log_entry, :connection_name end def connection_name raise "Can't find connection configuration!" unless @config @config[:connection_name] end # Rails 2.X specific logging method def format_log_entry_with_connection_name(message, dump = nil) msg = connection_name ? "[#{connection_name}] " : '' msg = " \e[0;34;1m#{msg}\e[0m" if connection_name && ::ActiveRecord::Base.colorize_logging msg << format_log_entry_without_connection_name(message, dump) end end end end ================================================ FILE: lib/db_charmer/rails2/active_record/master_slave_routing.rb ================================================ module DbCharmer module ActiveRecord module MasterSlaveRouting module ClassMethods SLAVE_METHODS = [ :find_by_sql, :count_by_sql, :calculate ] MASTER_METHODS = [ :update, :create, :delete, :destroy, :delete_all, :destroy_all, :update_all, :update_counters ] SLAVE_METHODS.each do |slave_method| class_eval <<-EOF, __FILE__, __LINE__ + 1 def #{slave_method}(*args, &block) first_level_on_slave do super(*args, &block) end end EOF end MASTER_METHODS.each do |master_method| class_eval <<-EOF, __FILE__, __LINE__ + 1 def #{master_method}(*args, &block) on_master do super(*args, &block) end end EOF end def find(*args, &block) options = args.last if options.is_a?(Hash) && options[:lock] on_master { super(*args, &block) } else super(*args, &block) end end end module InstanceMethods def reload(*args, &block) self.class.on_master do super(*args, &block) end end end end end end ================================================ FILE: lib/db_charmer/rails2/active_record/named_scope/scope_proxy.rb ================================================ module DbCharmer module ActiveRecord module NamedScope module ScopeProxy def proxy? true end def on_db(con, proxy_target = nil, &block) proxy_target ||= self proxy_scope.on_db(con, proxy_target, &block) end def on_slave(con = nil, &block) proxy_scope.on_slave(con, self, &block) end def on_master(&block) proxy_scope.on_master(self, &block) end end end end end ================================================ FILE: lib/db_charmer/rails3/abstract_adapter/connection_name.rb ================================================ module DbCharmer module AbstractAdapter module ConnectionName # We use this proxy to push connection name down to instrumenters w/o monkey-patching the log method itself class InstrumenterDecorator < ActiveSupport::BasicObject def initialize(adapter, instrumenter) @adapter = adapter @instrumenter = instrumenter end def instrument(name, payload = {}, &block) payload[:connection_name] ||= @adapter.connection_name @instrumenter.instrument(name, payload, &block) end def method_missing(meth, *args, &block) @instrumenter.send(meth, *args, &block) end end def self.included(base) base.alias_method_chain :initialize, :connection_name end def connection_name raise "Can't find connection configuration!" unless @config @config[:connection_name] end def initialize_with_connection_name(*args) initialize_without_connection_name(*args) @instrumenter = InstrumenterDecorator.new(self, @instrumenter) end end end end ================================================ FILE: lib/db_charmer/rails3/active_record/log_subscriber.rb ================================================ module DbCharmer module ActiveRecord module LogSubscriber def self.included(base) base.send(:attr_accessor, :connection_name) base.alias_method_chain :sql, :connection_name base.alias_method_chain :debug, :connection_name end def sql_with_connection_name(event) self.connection_name = event.payload[:connection_name] sql_without_connection_name(event) end def debug_with_connection_name(msg) conn = connection_name ? color(" [#{connection_name}]", ActiveSupport::LogSubscriber::BLUE, true) : '' debug_without_connection_name(conn + msg) end end end end ================================================ FILE: lib/db_charmer/rails3/active_record/master_slave_routing.rb ================================================ module DbCharmer module ActiveRecord module MasterSlaveRouting module ClassMethods SLAVE_METHODS = [ :find_by_sql, :count_by_sql ] MASTER_METHODS = [ ] # I don't know any methods in AR::Base that change data directly w/o going to the relation object SLAVE_METHODS.each do |slave_method| class_eval <<-EOF, __FILE__, __LINE__ + 1 def #{slave_method}(*args, &block) first_level_on_slave do super(*args, &block) end end EOF end MASTER_METHODS.each do |master_method| class_eval <<-EOF, __FILE__, __LINE__ + 1 def #{master_method}(*args, &block) on_master do super(*args, &block) end end EOF end end module InstanceMethods MASTER_METHODS = [ :reload ] MASTER_METHODS.each do |master_method| class_eval <<-EOF, __FILE__, __LINE__ + 1 def #{master_method}(*args, &block) self.class.on_master do super(*args, &block) end end EOF end end end end end ================================================ FILE: lib/db_charmer/rails3/active_record/relation/connection_routing.rb ================================================ module DbCharmer module ActiveRecord module Relation module ConnectionRouting # All the methods that could be querying the database SLAVE_METHODS = [ :calculate, :exists? ] MASTER_METHODS = [ :delete, :delete_all, :destroy, :destroy_all, :reload, :update, :update_all ] ALL_METHODS = SLAVE_METHODS + MASTER_METHODS DB_CHARMER_ATTRIBUTES = [ :db_charmer_connection, :db_charmer_connection_is_forced, :db_charmer_enable_slaves ] # Define the default relation connection + override all the query methods here def self.included(base) init_attributes(base) init_routing(base) end # Define our attributes + spawn methods shit needs to be changed to make sure our accessors are copied over to the new instances def self.init_attributes(base) DB_CHARMER_ATTRIBUTES.each do |attr| base.send(:attr_accessor, attr) end # Override spawn methods base.alias_method_chain :except, :db_charmer base.alias_method_chain :only, :db_charmer end # Override all query methods def self.init_routing(base) ALL_METHODS.each do |meth| base.alias_method_chain meth, :db_charmer end # Special case: for normal selects we go to the slave, but for selects with a lock we should use master base.alias_method_chain :to_a, :db_charmer end # Copy db_charmer attributes in addition to what they're copying def except_with_db_charmer(*args) except_without_db_charmer(*args).tap do |result| copy_db_charmer_options(self, result) end end # Copy db_charmer attributes in addition to what they're copying def only_with_db_charmer(*args) only_without_db_charmer(*args).tap do |result| copy_db_charmer_options(self, result) end end # Copy our accessors from one instance to another def copy_db_charmer_options(src, dst) DB_CHARMER_ATTRIBUTES.each do |attr| dst.send("#{attr}=".to_sym, src.send(attr)) end end # Connection switching (changes the default relation connection) def on_db(con, &block) if block_given? @klass.on_db(con, &block) else clone.tap do |result| result.db_charmer_connection = con result.db_charmer_connection_is_forced = true end end end # Make sure we get the right connection here def connection @klass.on_db(db_charmer_connection).connection end # Selects preferred destination (master/slave/default) for a query def select_destination(method, recommendation = :default) # If this relation was created within a forced connection block (e.g Model.on_db(:foo).relation) # Then we should use that connection everywhere except cases when a model is slave-enabled # in those cases DML queries go to the master if db_charmer_connection_is_forced return :master if db_charmer_enable_slaves && MASTER_METHODS.member?(method) return :default end # If this relation is created from a slave-enabled model, let's do the routing if possible if db_charmer_enable_slaves return :slave if SLAVE_METHODS.member?(method) return :master if MASTER_METHODS.member?(method) else # Make sure we do not use recommended destination recommendation = :default end # If nothing else came up, let's use the default or recommended connection return recommendation end # Switch the model to default relation connection def switch_connection_for_method(method, recommendation = nil) # Choose where to send the query destination ||= select_destination(method, recommendation) # What method to use on_db_method = [ :on_db, db_charmer_connection ] on_db_method = :on_master if destination == :master on_db_method = :first_level_on_slave if destination == :slave # Perform the query @klass.send(*on_db_method) do yield end end # For normal selects we go to the slave, but for selects with a lock we should use master def to_a_with_db_charmer(*args, &block) preferred_destination = :slave preferred_destination = :master if lock_value switch_connection_for_method(:to_a, preferred_destination) do to_a_without_db_charmer(*args, &block) end end # Need this to mimick alias_method_chain name generation (exists? => exists_with_db_charmer?) def self.aliased_method_name(target, with) aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 "#{aliased_target}_#{with}_db_charmer#{punctuation}" end # Override all the query methods here ALL_METHODS.each do |method| class_eval <<-EOF, __FILE__, __LINE__ + 1 def #{aliased_method_name method, :with}(*args, &block) switch_connection_for_method(:#{method.to_s}) do #{aliased_method_name method, :without}(*args, &block) end end EOF end end end end end ================================================ FILE: lib/db_charmer/rails3/active_record/relation_method.rb ================================================ module DbCharmer module ActiveRecord module RelationMethod def self.extended(base) class << base alias_method_chain :relation, :db_charmer alias_method_chain :arel_engine, :db_charmer end end # Create a relation object and initialize its default connection def relation_with_db_charmer(*args, &block) relation_without_db_charmer(*args, &block).tap do |rel| rel.db_charmer_connection = self.connection rel.db_charmer_enable_slaves = self.db_charmer_slaves.any? rel.db_charmer_connection_is_forced = !db_charmer_top_level_connection? end end # Use the model itself an engine for Arel, do not fall back to AR::Base def arel_engine_with_db_charmer(*) self end end end end ================================================ FILE: lib/db_charmer/rails31/active_record/migration/command_recorder.rb ================================================ module DbCharmer module ActiveRecord module Migration module CommandRecorder def invert_on_db(args) [:replay_commands_on_db, [args.first, args[1].inverse]] end end end end end ================================================ FILE: lib/db_charmer/rails31/active_record/preloader/association.rb ================================================ module DbCharmer module ActiveRecord module Preloader module Association extend ActiveSupport::Concern included do alias_method_chain :build_scope, :db_magic end def build_scope_with_db_magic if model.db_charmer_top_level_connection? || reflection.options[:polymorphic] || model.db_charmer_default_connection != klass.db_charmer_default_connection build_scope_without_db_magic else build_scope_without_db_magic.on_db(model) end end end end end end ================================================ FILE: lib/db_charmer/rails31/active_record/preloader/has_and_belongs_to_many.rb ================================================ module DbCharmer module ActiveRecord module Preloader module HasAndBelongsToMany extend ActiveSupport::Concern included do alias_method_chain :records_for, :db_magic end def records_for_with_db_magic(ids) if model.db_charmer_top_level_connection? || reflection.options[:polymorphic] || model.db_charmer_default_connection != klass.db_charmer_default_connection records_for_without_db_magic(ids) else klass.on_db(model) do records_for_without_db_magic(ids) end end end end end end end ================================================ FILE: lib/db_charmer/railtie.rb ================================================ module DbCharmer class Railtie < Rails::Railtie rake_tasks do load "db_charmer/tasks/databases.rake" end end end ================================================ FILE: lib/db_charmer/sharding/connection.rb ================================================ module DbCharmer module Sharding class Connection attr_accessor :config, :sharder def initialize(config) @config = config @sharder = self.instantiate_sharder end def instantiate_sharder raise ArgumentError, "No :method passed!" unless config[:method] sharder_class_name = "DbCharmer::Sharding::Method::#{config[:method].to_s.classify}" sharder_class = sharder_class_name.constantize sharder_class.new(config) end def shard_connections sharder.respond_to?(:shard_connections) ? sharder.shard_connections : nil end def support_default_shard? sharder.respond_to?(:support_default_shard?) && sharder.support_default_shard? end def default_connection @default_connection ||= DbCharmer::Sharding::StubConnection.new(self) end end end end ================================================ FILE: lib/db_charmer/sharding/method/db_block_group_map.rb ================================================ # This is a more sophisticated sharding method based on a two layer database-backed # blocks map that holds block-shard associations. Record blocks are mapped to tablegroups # and groups are mapped to shards. # # It automatically creates new blocks for new keys and assigns them to existing groups. # Warning: make sure to create at least one shard and one group before inserting any records. # module DbCharmer module Sharding module Method class DbBlockGroupMap # Shard connection info model class Shard < ::ActiveRecord::Base validates_presence_of :db_host validates_presence_of :db_port validates_presence_of :db_user validates_presence_of :db_pass validates_presence_of :db_name_prefix has_many :groups, :class_name => 'DbCharmer::Sharding::Method::DbBlockGroupMap::Group' end # Table group info model class Group < ::ActiveRecord::Base validates_presence_of :shard_id belongs_to :shard, :class_name => 'DbCharmer::Sharding::Method::DbBlockGroupMap::Shard' end #--------------------------------------------------------------------------------------------------------------- # Sharder name attr_accessor :name # Mapping db connection attr_accessor :connection, :connection_name # Mapping table name attr_accessor :map_table # Tablegroups table name attr_accessor :groups_table # Shards table name attr_accessor :shards_table # Sharding keys block size attr_accessor :block_size def initialize(config) @name = config[:name] or raise(ArgumentError, "Missing required :name parameter!") @connection = DbCharmer::ConnectionFactory.connect(config[:connection], true) @block_size = (config[:block_size] || 10000).to_i @map_table = config[:map_table] or raise(ArgumentError, "Missing required :map_table parameter!") @groups_table = config[:groups_table] or raise(ArgumentError, "Missing required :groups_table parameter!") @shards_table = config[:shards_table] or raise(ArgumentError, "Missing required :shards_table parameter!") # Local caches @shard_info_cache = {} @group_info_cache = {} @blocks_cache = Rails.cache @blocks_cache_prefix = config[:blocks_cache_prefix] || "#{@name}_block:" end #--------------------------------------------------------------------------------------------------------------- def shard_for_key(key) block = block_for_key(key) # Auto-allocate new blocks block ||= allocate_new_block_for_key(key) raise ArgumentError, "Invalid key value, no shards found for this key and could not create a new block!" unless block # Load shard group_id = block['group_id'].to_i shard_info = shard_info_by_group_id(group_id) # Get config shard_connection_config(shard_info, group_id) end #--------------------------------------------------------------------------------------------------------------- # Returns a block for a key def block_for_key(key, cache = true) # Cleanup the cache if asked to key_range = [ block_start_for_key(key), block_end_for_key(key) ] block_cache_key = "%d-%d" % key_range if cache cached_block = get_cached_block(block_cache_key) return cached_block if cached_block end # Fetch cached value or load from db block = begin sql = "SELECT * FROM #{map_table} WHERE start_id = #{key_range.first} AND end_id = #{key_range.last} LIMIT 1" connection.select_one(sql, 'Find a shard block') end set_cached_block(block_cache_key, block) return block end #--------------------------------------------------------------------------------------------------------------- def get_cached_block(block_cache_key) @blocks_cache.read("#{@blocks_cache_prefix}#{block_cache_key}") end def set_cached_block(block_cache_key, block) @blocks_cache.write("#{@blocks_cache_prefix}#{block_cache_key}", block) end #--------------------------------------------------------------------------------------------------------------- # Load group info def group_info_by_id(group_id, cache = true) # Cleanup the cache if asked to @group_info_cache[group_id] = nil unless cache # Either load from cache or from db @group_info_cache[group_id] ||= begin prepare_shard_models Group.find_by_id(group_id) end end # Load shard info def shard_info_by_id(shard_id, cache = true) # Cleanup the cache if asked to @shard_info_cache[shard_id] = nil unless cache # Either load from cache or from db @shard_info_cache[shard_id] ||= begin prepare_shard_models Shard.find_by_id(shard_id) end end # Load shard info using mapping info for a group def shard_info_by_group_id(group_id) # Load group group_info = group_info_by_id(group_id) raise ArgumentError, "Invalid group_id: #{group_id}" unless group_info shard_info = shard_info_by_id(group_info.shard_id) raise ArgumentError, "Invalid shard_id: #{group_info.shard_id}" unless shard_info return shard_info end #--------------------------------------------------------------------------------------------------------------- def allocate_new_block_for_key(key) # Can't find any groups to use for blocks allocation! return nil unless group = least_loaded_group # Figure out block limits start_id = block_start_for_key(key) end_id = block_end_for_key(key) # Try to insert a new mapping (ignore duplicate key errors) sql = <<-SQL INSERT IGNORE INTO #{map_table} SET start_id = #{start_id}, end_id = #{end_id}, group_id = #{group.id}, block_size = #{block_size}, created_at = NOW(), updated_at = NOW() SQL connection.execute(sql, "Allocate new block") # Increment the blocks counter on the shard Group.update_counters(group.id, :blocks_count => +1) # Retry block search after creation block_for_key(key) end def least_loaded_group prepare_shard_models # Select group group = Group.first(:conditions => { :enabled => true, :open => true }, :order => 'blocks_count ASC') raise "Can't find any tablegroups to use for blocks allocation!" unless group return group end #--------------------------------------------------------------------------------------------------------------- def block_start_for_key(key) block_size.to_i * (key.to_i / block_size.to_i) end def block_end_for_key(key) block_size.to_i + block_start_for_key(key) end #--------------------------------------------------------------------------------------------------------------- # Create configuration (use mapping connection as a template) def shard_connection_config(shard, group_id) # Format connection name shard_name = "db_charmer_db_block_group_map_#{name}_s%d_g%d" % [ shard.id, group_id] # Here we get the mapping connection's configuration # They do not expose configs so we hack in and get the instance var # FIXME: Find a better way, maybe move config method to our ar extenstions connection.instance_variable_get(:@config).clone.merge( # Name for the connection factory :connection_name => shard_name, # Connection params :host => shard.db_host, :port => shard.db_port, :username => shard.db_user, :password => shard.db_pass, :database => group_database_name(shard, group_id) ) end def group_database_name(shard, group_id) "%s_%05d" % [ shard.db_name_prefix, group_id ] end #--------------------------------------------------------------------------------------------------------------- def create_shard(params) params = params.symbolize_keys [ :db_host, :db_port, :db_user, :db_pass, :db_name_prefix ].each do |arg| raise ArgumentError, "Missing required parameter: #{arg}" unless params[arg] end # Prepare model prepare_shard_models # Create the record Shard.create! do |shard| shard.db_host = params[:db_host] shard.db_port = params[:db_port] shard.db_user = params[:db_user] shard.db_pass = params[:db_pass] shard.db_name_prefix = params[:db_name_prefix] end end def shard_connections # Find all groups prepare_shard_models groups = Group.all(:conditions => { :enabled => true }, :include => :shard) # Map them to shards groups.map { |group| shard_connection_config(group.shard, group.id) } end # Prepare model for working with our shards table def prepare_shard_models Shard.set_table_name(shards_table) Shard.switch_connection_to(connection) Group.set_table_name(groups_table) Group.switch_connection_to(connection) end end end end end ================================================ FILE: lib/db_charmer/sharding/method/db_block_map.rb ================================================ # This is a more sophisticated sharding method based on a database-backed # blocks map that holds block-shard associations. It automatically # creates new blocks for new keys and assigns them to shards. # module DbCharmer module Sharding module Method class DbBlockMap # Sharder name attr_accessor :name # Mapping db connection attr_accessor :connection, :connection_name # Mapping table name attr_accessor :map_table # Shards table name attr_accessor :shards_table # Sharding keys block size attr_accessor :block_size def initialize(config) @name = config[:name] or raise(ArgumentError, "Missing required :name parameter!") @connection = DbCharmer::ConnectionFactory.connect(config[:connection], true) @block_size = (config[:block_size] || 10000).to_i @map_table = config[:map_table] or raise(ArgumentError, "Missing required :map_table parameter!") @shards_table = config[:shards_table] or raise(ArgumentError, "Missing required :shards_table parameter!") # Local caches @shard_info_cache = {} @blocks_cache = Rails.cache @blocks_cache_prefix = config[:blocks_cache_prefix] || "#{@name}_block:" end def shard_for_key(key) block = block_for_key(key) begin # Auto-allocate new blocks block ||= allocate_new_block_for_key(key) rescue ::ActiveRecord::StatementInvalid => e raise unless e.message.include?('Duplicate entry') block = block_for_key(key) end raise ArgumentError, "Invalid key value, no shards found for this key and could not create a new block!" unless block # Bail if no shard found shard_id = block['shard_id'].to_i shard_info = shard_info_by_id(shard_id) raise ArgumentError, "Invalid shard_id: #{shard_id}" unless shard_info # Get config shard_connection_config(shard_info) end class ShardInfo < ::ActiveRecord::Base validates_presence_of :db_host validates_presence_of :db_port validates_presence_of :db_user validates_presence_of :db_pass validates_presence_of :db_name end # Returns a block for a key def block_for_key(key, cache = true) # Cleanup the cache if asked to key_range = [ block_start_for_key(key), block_end_for_key(key) ] block_cache_key = "%d-%d" % key_range if cache cached_block = get_cached_block(block_cache_key) return cached_block if cached_block end # Fetch cached value or load from db block = begin sql = "SELECT * FROM #{map_table} WHERE start_id = #{key_range.first} AND end_id = #{key_range.last} LIMIT 1" connection.select_one(sql, 'Find a shard block') end set_cached_block(block_cache_key, block) return block end def get_cached_block(block_cache_key) @blocks_cache.read("#{@blocks_cache_prefix}#{block_cache_key}") end def set_cached_block(block_cache_key, block) @blocks_cache.write("#{@blocks_cache_prefix}#{block_cache_key}", block) end # Load shard info def shard_info_by_id(shard_id, cache = true) # Cleanup the cache if asked to @shard_info_cache[shard_id] = nil unless cache # Either load from cache or from db @shard_info_cache[shard_id] ||= begin prepare_shard_model ShardInfo.find_by_id(shard_id) end end def allocate_new_block_for_key(key) # Can't find any shards to use for blocks allocation! return nil unless shard = least_loaded_shard # Figure out block limits start_id = block_start_for_key(key) end_id = block_end_for_key(key) # Try to insert a new mapping (ignore duplicate key errors) sql = <<-SQL INSERT INTO #{map_table} SET start_id = #{start_id}, end_id = #{end_id}, shard_id = #{shard.id}, block_size = #{block_size}, created_at = NOW(), updated_at = NOW() SQL connection.execute(sql, "Allocate new block") # Increment the blocks counter on the shard ShardInfo.update_counters(shard.id, :blocks_count => +1) # Retry block search after creation block_for_key(key) end def least_loaded_shard prepare_shard_model # Select shard shard = ShardInfo.all(:conditions => { :enabled => true, :open => true }, :order => 'blocks_count ASC', :limit => 1).first raise "Can't find any shards to use for blocks allocation!" unless shard return shard end def block_start_for_key(key) block_size.to_i * (key.to_i / block_size.to_i) end def block_end_for_key(key) block_size.to_i + block_start_for_key(key) end # Create configuration (use mapping connection as a template) def shard_connection_config(shard) # Format connection name shard_name = "db_charmer_db_block_map_#{name}_shard_%05d" % shard.id # Here we get the mapping connection's configuration # They do not expose configs so we hack in and get the instance var # FIXME: Find a better way, maybe move config method to our ar extenstions connection.instance_variable_get(:@config).clone.merge( # Name for the connection factory :connection_name => shard_name, # Connection params :host => shard.db_host, :port => shard.db_port, :username => shard.db_user, :password => shard.db_pass, :database => shard.db_name ) end def create_shard(params) params = params.symbolize_keys [ :db_host, :db_port, :db_user, :db_pass, :db_name ].each do |arg| raise ArgumentError, "Missing required parameter: #{arg}" unless params[arg] end # Prepare model prepare_shard_model # Create the record ShardInfo.create! do |shard| shard.db_host = params[:db_host] shard.db_port = params[:db_port] shard.db_user = params[:db_user] shard.db_pass = params[:db_pass] shard.db_name = params[:db_name] end end def shard_connections # Find all shards prepare_shard_model shards = ShardInfo.all(:conditions => { :enabled => true }) # Map them to connections shards.map { |shard| shard_connection_config(shard) } end # Prepare model for working with our shards table def prepare_shard_model ShardInfo.table_name = shards_table ShardInfo.switch_connection_to(connection) end end end end end ================================================ FILE: lib/db_charmer/sharding/method/hash_map.rb ================================================ module DbCharmer module Sharding module Method class HashMap attr_accessor :map def initialize(config) @map = config[:map].clone or raise ArgumentError, "No :map defined!" end def shard_for_key(key) res = map[key] || map[:default] raise ArgumentError, "Invalid key value, no shards found for this key!" unless res return res end def support_default_shard? map.has_key?(:default) end end end end end ================================================ FILE: lib/db_charmer/sharding/method/range.rb ================================================ module DbCharmer module Sharding module Method class Range attr_accessor :ranges def initialize(config) @ranges = config[:ranges] ? config[:ranges].clone : raise(ArgumentError, "No :ranges defined!") end def shard_for_key(key) return ranges[:default] if key == :default ranges.each do |range, shard| next if range == :default return shard if range.member?(key.to_i) end return ranges[:default] if ranges[:default] raise ArgumentError, "Invalid key value, no shards found for this key!" end def support_default_shard? ranges.has_key?(:default) end def shard_connections ranges.values.uniq end end end end end ================================================ FILE: lib/db_charmer/sharding/method.rb ================================================ module DbCharmer module Sharding module Method autoload :Range, 'db_charmer/sharding/method/range' autoload :HashMap, 'db_charmer/sharding/method/hash_map' autoload :DbBlockMap, 'db_charmer/sharding/method/db_block_map' autoload :DbBlockGroupMap, 'db_charmer/sharding/method/db_block_group_map' end end end ================================================ FILE: lib/db_charmer/sharding/stub_connection.rb ================================================ # This is a simple proxy class used as a default connection on sharded models # # The idea is to proxy all utility method calls to a real connection (set by # the +set_real_connection+ method when we switch shards) and fail on real # database querying calls forcing users to switch shard connections. # module DbCharmer module Sharding class StubConnection attr_accessor :sharded_connection def initialize(sharded_connection) @sharded_connection = sharded_connection @real_conn = nil end def set_real_connection(real_conn) @real_conn = real_conn end def db_charmer_connection_name "StubConnection" end def real_connection # Return memoized real connection return @real_conn if @real_conn # If sharded connection supports shards enumeration, get the first shard conn = sharded_connection.shard_connections.try(:first) # If we do not have real connection yet, try to use the default one (if it is supported by the sharder) conn ||= sharded_connection.sharder.shard_for_key(:default) if sharded_connection.support_default_shard? # Get connection proxy for our real connection return nil unless conn @real_conn = ::ActiveRecord::Base.coerce_to_connection_proxy(conn, DbCharmer.connections_should_exist?) end def respond_to?(method_name, include_all = false) return true if super return false if real_connection.object_id == self.object_id real_connection.respond_to?(method_name, include_all) end def method_missing(meth, *args, &block) # Fail on database statements if ::ActiveRecord::ConnectionAdapters::DatabaseStatements.instance_methods.member?(meth.to_s) raise ::ActiveRecord::ConnectionNotEstablished, "You have to switch connection on your model before using it!" end # Fail if no connection has been established yet unless real_connection raise ::ActiveRecord::ConnectionNotEstablished, "No real connection to proxy this method to!" end if real_connection.kind_of?(DbCharmer::Sharding::StubConnection) raise ::ActiveRecord::ConnectionNotEstablished, "You have to switch connection on your model before using it!" end # Proxy the call to our real connection target real_connection.__send__(meth, *args, &block) end end end end ================================================ FILE: lib/db_charmer/sharding.rb ================================================ module DbCharmer module Sharding autoload :Connection, 'db_charmer/sharding/connection' autoload :StubConnection, 'db_charmer/sharding/stub_connection' autoload :Method, 'db_charmer/sharding/method' @@sharded_connections = {} def self.register_connection(config) name = config[:name] or raise ArgumentError, "No :name in connection!" @@sharded_connections[name] = DbCharmer::Sharding::Connection.new(config) end def self.sharded_connection(name) @@sharded_connections[name] or raise ArgumentError, "Invalid sharded connection name!" end end end ================================================ FILE: lib/db_charmer/tasks/databases.rake ================================================ namespace :db_charmer do namespace :create do desc 'Create all the local databases defined in config/database.yml' task :all => "db:load_config" do ::ActiveRecord::Base.configurations.each_value do |config| # Skip entries that don't have a database key, such as the first entry here: # # defaults: &defaults # adapter: mysql # username: root # password: # host: localhost # # development: # database: blog_development # <<: *defaults next unless config['database'] # Only connect to local databases local_database?(config) { create_core_and_sub_database(config) } end end end desc 'Create the databases defined in config/database.yml for the current RAILS_ENV' task :create => "db:load_config" do create_core_and_sub_database(ActiveRecord::Base.configurations[RAILS_ENV]) end def create_core_and_sub_database(config) create_database(config) config.each_value do | sub_config | next unless sub_config.is_a?(Hash) next unless sub_config['database'] create_database(sub_config) end end namespace :drop do desc 'Drops all the local databases defined in config/database.yml' task :all => "db:load_config" do ::ActiveRecord::Base.configurations.each_value do |config| # Skip entries that don't have a database key next unless config['database'] # Only connect to local databases local_database?(config) { drop_core_and_sub_database(config) } end end end desc 'Drops the database for the current RAILS_ENV' task :drop => "db:load_config" do config = ::ActiveRecord::Base.configurations[RAILS_ENV || 'development'] begin drop_core_and_sub_database(config) rescue Exception => e puts "Couldn't drop #{config['database']} : #{e.inspect}" end end def local_database?(config, &block) if %w( 127.0.0.1 localhost ).include?(config['host']) || config['host'].blank? yield else puts "This task only modifies local databases. #{config['database']} is on a remote host." end end end def drop_core_and_sub_database(config) begin drop_database(config) rescue $stderr.puts "#{config['database']} not exists" end config.each_value do | sub_config | next unless sub_config.is_a?(Hash) next unless sub_config['database'] begin drop_database(sub_config) rescue $stderr.puts "#{config['database']} not exists" end end end ================================================ FILE: lib/db_charmer/version.rb ================================================ module DbCharmer module Version MAJOR = 1 MINOR = 9 PATCH = 1 BUILD = nil STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.') end end ================================================ FILE: lib/db_charmer/with_remapped_databases.rb ================================================ module DbCharmer def self.with_remapped_databases(mappings, &proc) old_mappings = ::ActiveRecord::Base.db_charmer_database_remappings begin ::ActiveRecord::Base.db_charmer_database_remappings = mappings if mappings[:master] || mappings['master'] with_all_hijacked(&proc) else proc.call end ensure ::ActiveRecord::Base.db_charmer_database_remappings = old_mappings end end def self.hijack_new_classes? !! Thread.current[:db_charmer_hijack_new_classes] end private def self.with_all_hijacked old_hijack_new_classes = Thread.current[:db_charmer_hijack_new_classes] begin Thread.current[:db_charmer_hijack_new_classes] = true subclasses_method = DbCharmer.rails3? ? :descendants : :subclasses ::ActiveRecord::Base.send(subclasses_method).each do |subclass| subclass.hijack_connection! end yield ensure Thread.current[:db_charmer_hijack_new_classes] = old_hijack_new_classes end end end #--------------------------------------------------------------------------------------------------- # Hijack connection on all new AR classes when we're in a block with main AR connection remapped class ActiveRecord::Base class << self def inherited_with_hijacking(subclass) out = inherited_without_hijacking(subclass) hijack_connection! if DbCharmer.hijack_new_classes? out end alias_method_chain :inherited, :hijacking end end ================================================ FILE: lib/db_charmer.rb ================================================ # In Rails 2.2 they did not add it to the autoload so it won't work w/o this require require 'active_record/version' unless defined?(::ActiveRecord::VERSION::MAJOR) require 'active_support/core_ext' #--------------------------------------------------------------------------------------------------- module DbCharmer # Configure autoload autoload :Sharding, 'db_charmer/sharding' autoload :Version, 'db_charmer/version' module ActionController autoload :ForceSlaveReads, 'db_charmer/action_controller/force_slave_reads' end #------------------------------------------------------------------------------------------------- # Used in all Rails3-specific places def self.rails3? ::ActiveRecord::VERSION::MAJOR > 2 end # Used in all Rails3.1-specific places def self.rails31? rails3? && ::ActiveRecord::VERSION::MINOR >= 1 end # Used in all Rails2-specific places def self.rails2? ::ActiveRecord::VERSION::MAJOR == 2 end # Detect broken Rails version def self.rails324? ActiveRecord::VERSION::STRING == '3.2.4' end #------------------------------------------------------------------------------------------------- # Returns true if we're running within a Rails project def self.running_with_rails? defined?(Rails) && Rails.respond_to?(:env) end # Returns current environment name based on Rails or Rack environment variables def self.detect_environment return Rails.env if running_with_rails? ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'default' end # Try to detect current environment or use development by default @@env = DbCharmer.detect_environment mattr_accessor :env #------------------------------------------------------------------------------------------------- # Accessors @@connections_should_exist = true mattr_accessor :connections_should_exist def self.connections_should_exist? !! connections_should_exist end #------------------------------------------------------------------------------------------------- def self.logger return Rails.logger if running_with_rails? @@logger ||= Logger.new(STDERR) end #------------------------------------------------------------------------------------------------- # Extend ActionController to support forcing slave reads def self.enable_controller_magic! ::ActionController::Base.extend(DbCharmer::ActionController::ForceSlaveReads::ClassMethods) ::ActionController::Base.send(:include, DbCharmer::ActionController::ForceSlaveReads::InstanceMethods) end end #--------------------------------------------------------------------------------------------------- # Print warning about the broken Rails 2.3.4 puts "WARNING: Rails 3.2.4 is not officially supported by DbCharmer. Please upgrade." if DbCharmer.rails324? #--------------------------------------------------------------------------------------------------- # Add useful methods to global object require 'db_charmer/core_extensions' require 'db_charmer/connection_factory' require 'db_charmer/connection_proxy' require 'db_charmer/force_slave_reads' require 'db_charmer/with_remapped_databases' if DbCharmer.rails3? require "db_charmer/railtie" end #--------------------------------------------------------------------------------------------------- # Add our custom class-level attributes to AR models require 'db_charmer/active_record/class_attributes' require 'active_record' ActiveRecord::Base.extend(DbCharmer::ActiveRecord::ClassAttributes) #--------------------------------------------------------------------------------------------------- # Enable connections switching in AR require 'db_charmer/active_record/connection_switching' ActiveRecord::Base.extend(DbCharmer::ActiveRecord::ConnectionSwitching) #--------------------------------------------------------------------------------------------------- # Enable AR logging extensions if DbCharmer.rails3? require 'db_charmer/rails3/abstract_adapter/connection_name' require 'db_charmer/rails3/active_record/log_subscriber' ActiveRecord::LogSubscriber.send(:include, DbCharmer::ActiveRecord::LogSubscriber) ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, DbCharmer::AbstractAdapter::ConnectionName) else require 'db_charmer/rails2/abstract_adapter/log_formatting' ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, DbCharmer::AbstractAdapter::LogFormatting) end #--------------------------------------------------------------------------------------------------- # Enable connection proxy in AR require 'db_charmer/active_record/multi_db_proxy' ActiveRecord::Base.extend(DbCharmer::ActiveRecord::MultiDbProxy::ClassMethods) ActiveRecord::Base.extend(DbCharmer::ActiveRecord::MultiDbProxy::MasterSlaveClassMethods) ActiveRecord::Base.send(:include, DbCharmer::ActiveRecord::MultiDbProxy::InstanceMethods) #--------------------------------------------------------------------------------------------------- # Enable connection proxy for relations if DbCharmer.rails3? require 'db_charmer/rails3/active_record/relation_method' require 'db_charmer/rails3/active_record/relation/connection_routing' ActiveRecord::Base.extend(DbCharmer::ActiveRecord::RelationMethod) ActiveRecord::Relation.send(:include, DbCharmer::ActiveRecord::Relation::ConnectionRouting) end #--------------------------------------------------------------------------------------------------- # Enable connection proxy for scopes (rails 2.x only) if DbCharmer.rails2? require 'db_charmer/rails2/active_record/named_scope/scope_proxy' ActiveRecord::NamedScope::Scope.send(:include, DbCharmer::ActiveRecord::NamedScope::ScopeProxy) end #--------------------------------------------------------------------------------------------------- # Enable connection proxy for associations # WARNING: Inject methods to association class right here because they proxy +include+ calls # somewhere else, which means we could not use +include+ method here association_proxy_class = DbCharmer.rails31? ? ActiveRecord::Associations::CollectionProxy : ActiveRecord::Associations::AssociationProxy association_proxy_class.class_eval do def proxy? true end if DbCharmer.rails31? def on_db(con, proxy_target = nil, &block) proxy_target ||= self @association.klass.on_db(con, proxy_target, &block) end def on_slave(con = nil, &block) @association.klass.on_slave(con, self, &block) end def on_master(&block) @association.klass.on_master(self, &block) end else def on_db(con, proxy_target = nil, &block) proxy_target ||= self @reflection.klass.on_db(con, proxy_target, &block) end def on_slave(con = nil, &block) @reflection.klass.on_slave(con, self, &block) end def on_master(&block) @reflection.klass.on_master(self, &block) end end end #--------------------------------------------------------------------------------------------------- # Enable multi-db migrations require 'db_charmer/active_record/migration/multi_db_migrations' ActiveRecord::Migration.send(:include, DbCharmer::ActiveRecord::Migration::MultiDbMigrations) if DbCharmer.rails31? require 'db_charmer/rails31/active_record/migration/command_recorder' ActiveRecord::Migration::CommandRecorder.send(:include, DbCharmer::ActiveRecord::Migration::CommandRecorder) end #--------------------------------------------------------------------------------------------------- # Enable the magic if DbCharmer.rails3? require 'db_charmer/rails3/active_record/master_slave_routing' else require 'db_charmer/rails2/active_record/master_slave_routing' end require 'db_charmer/active_record/sharding' require 'db_charmer/active_record/db_magic' ActiveRecord::Base.extend(DbCharmer::ActiveRecord::DbMagic) #--------------------------------------------------------------------------------------------------- # Setup association preload magic if DbCharmer.rails31? require 'db_charmer/rails31/active_record/preloader/association' ActiveRecord::Associations::Preloader::Association.send(:include, DbCharmer::ActiveRecord::Preloader::Association) require 'db_charmer/rails31/active_record/preloader/has_and_belongs_to_many' ActiveRecord::Associations::Preloader::HasAndBelongsToMany.send(:include, DbCharmer::ActiveRecord::Preloader::HasAndBelongsToMany) else require 'db_charmer/active_record/association_preload' ActiveRecord::Base.extend(DbCharmer::ActiveRecord::AssociationPreload) # Open up really useful API method ActiveRecord::AssociationPreload::ClassMethods.send(:public, :preload_associations) end ================================================ FILE: test-project/.gitignore ================================================ log/*.log db/schema.* .idea TAGS config/database.yml .bundle tmp .DS_Store vendor Gemfile.lock doc ================================================ FILE: test-project/.rspec ================================================ --colour --format documentation ================================================ FILE: test-project/Gemfile ================================================ source 'http://rubygems.org' gem 'rake', "0.9.2.2" gem 'mysql', "2.8.1" gem 'rspec', '< 3.0' gem 'rspec-core', '< 3.0' gem 'rspec-rails', '< 3.0' # Load DbCharmer as a gem if ENV['DB_CHARMER_GEM'].to_s == '' gem_path = File.expand_path(File.dirname(File.dirname(__FILE__))) puts "Using on-disk db-charmer code from '#{gem_path}'..." gem 'db-charmer', :path => gem_path, :require => 'db_charmer' else puts "Using db-charmer gem: #{ENV['DB_CHARMER_GEM']}..." gem 'db-charmer', ENV['DB_CHARMER_GEM'], :require => 'db_charmer' end # Detect Rails version we need to use rails_version_file = File.expand_path("../.rails-version", __FILE__) version = File.exists?(rails_version_file) && File.read(rails_version_file).chomp version ||= ENV['RAILS_VERSION'] version ||= '3-2-stable' # Require gems for selected rails version case version when /master/ gem "rails", :git => "git://github.com/rails/rails.git" gem "arel", :git => "git://github.com/rails/arel.git" gem "journey", :git => "git://github.com/rails/journey.git" when /3-0-stable/ gem "rails", :git => "git://github.com/rails/rails.git", :branch => "3-0-stable" gem "arel", :git => "git://github.com/rails/arel.git", :branch => "2-0-stable" when /3-1-stable/ gem "rails", :git => "git://github.com/rails/rails.git", :branch => "3-1-stable" when /3-2-stable/ gem "rails", :git => "git://github.com/rails/rails.git", :branch => "3-2-stable" else gem "rails", version end ================================================ FILE: test-project/Rakefile ================================================ ENV['RAILS_ENV'] = 'test' # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require File.expand_path('../config/application', __FILE__) require 'rake' DbCharmerSandbox::Application.load_tasks ================================================ FILE: test-project/TODO ================================================ Functionality: - Add a controller wrapper to force all queries to the master (thanks mascohism for the idea) Docs: - Document (make more obvious) multi-db migrations code with migration_connections_should_exist - Document the fact, that all queries in a transaction go to the master ================================================ FILE: test-project/app/controllers/application_controller.rb ================================================ class ApplicationController < ActionController::Base protect_from_forgery end ================================================ FILE: test-project/app/controllers/posts_controller.rb ================================================ class PostsController < ApplicationController force_slave_reads :only => [ :index, :show, :new ], :except => :new # We'll use this to make sure count query would be sent to a proper server before_filter do Post.count end def index @posts = Post.all end def show @post = Post.find(params[:id]) end def new @post = Post.new end def create post = Post.create!(params[:post]) redirect_to(post_url(post)) end def destroy Post.delete(params[:id]) redirect_to(:action => :index) end end ================================================ FILE: test-project/app/helpers/application_helper.rb ================================================ module ApplicationHelper end ================================================ FILE: test-project/app/models/avatar.rb ================================================ class Avatar < ActiveRecord::Base end ================================================ FILE: test-project/app/models/car.rb ================================================ class Car < ActiveRecord::Base db_magic :slave => :slave01 end ================================================ FILE: test-project/app/models/categories_posts.rb ================================================ class CategoriesPosts < ActiveRecord::Base belongs_to :category belongs_to :post end ================================================ FILE: test-project/app/models/category.rb ================================================ class Category < ActiveRecord::Base has_and_belongs_to_many :posts end ================================================ FILE: test-project/app/models/comment.rb ================================================ class Comment < ActiveRecord::Base belongs_to :commentable, :polymorphic => true end ================================================ FILE: test-project/app/models/event.rb ================================================ class Event < ActiveRecord::Base self.table_name = :timeline_events db_magic :sharded => { :key => :to_uid, :sharded_connection => :social } end ================================================ FILE: test-project/app/models/ford.rb ================================================ class Ford < Car end ================================================ FILE: test-project/app/models/house.rb ================================================ class House < ActiveRecord::Base db_magic :slave => :slave01, :force_slave_reads => false end ================================================ FILE: test-project/app/models/log_record.rb ================================================ class LogRecord < ActiveRecord::Base db_magic :connection => :logs belongs_to :user end ================================================ FILE: test-project/app/models/post.rb ================================================ class Post < ActiveRecord::Base DB_MAGIC_DEFAULT_PARAMS = { :slave => :slave01, :force_slave_reads => false } db_magic DB_MAGIC_DEFAULT_PARAMS belongs_to :user has_and_belongs_to_many :categories def self.define_scope(*args, &block) if DbCharmer.rails3? scope(*args, &block) else named_scope(*args, &block) end end define_scope :windows_posts, :conditions => "title like '%win%'" define_scope :dummy_scope, :conditions => '1' end ================================================ FILE: test-project/app/models/range_sharded_model.rb ================================================ class RangeShardedModel < ActiveRecord::Base db_magic :sharded => { :key => :id, :sharded_connection => :texts } end ================================================ FILE: test-project/app/models/toyota.rb ================================================ class Toyota < Car end ================================================ FILE: test-project/app/models/user.rb ================================================ class User < ActiveRecord::Base has_many :posts has_many :log_records has_one :avatar end ================================================ FILE: test-project/app/views/layouts/application.html.erb ================================================ DbCharmerSandbox <%= stylesheet_link_tag :all %> <%= javascript_include_tag :defaults %> <%= csrf_meta_tag %> <%= yield %> ================================================ FILE: test-project/app/views/posts/index.html.erb ================================================

Posts


<% @posts.each do |post| %>

Post #<%= post.id %>

<%= post.inspect %>

<% end %> ================================================ FILE: test-project/app/views/posts/new.html.erb ================================================

Posts#new

Find me in app/views/posts/new.html.erb

================================================ FILE: test-project/app/views/posts/show.html.erb ================================================

Posts#show

Find me in app/views/posts/show.html.erb

================================================ FILE: test-project/config/application.rb ================================================ require File.expand_path('../boot', __FILE__) require 'rails/all' # If you have a Gemfile, require the gems listed there, including any gems # you've limited to :test, :development, or :production. Bundler.require(:default, Rails.env) if defined?(Bundler) module DbCharmerSandbox class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. # config.autoload_paths += %W(#{config.root}/extras) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. # config.plugins = [ :exception_notification, :ssl_requirement, :all ] # Activate observers that should always be running. # config.active_record.observers = :cacher, :garbage_collector, :forum_observer # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # config.time_zone = 'Central Time (US & Canada)' # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de # JavaScript files you want as :defaults (application.js is always included). # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] end end ================================================ FILE: test-project/config/boot.rb ================================================ require 'rubygems' # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) ================================================ FILE: test-project/config/database.yml.example ================================================ common: &common adapter: mysql encoding: utf8 reconnect: false pool: 10 username: root password: #---------------------------------------------------------------- test: <<: *common database: db_charmer_sandbox_test # logs database logs: <<: *common database: db_charmer_logs_test # slave database slave01: <<: *common username: db_charmer_ro database: db_charmer_sandbox_test user_master: <<: *common database: db_charmer_sandbox_test # shard mapping db social_shard_info: <<: *common database: db_charmer_sandbox_test # for migrations only social_shard01: <<: *common database: db_charmer_events_test_shard01 # for migrations only social_shard02: <<: *common database: db_charmer_events_test_shard02 #---------------------------------------------------------------- test22: <<: *common database: db_charmer_sandbox22_test # logs database logs: <<: *common database: db_charmer_logs22_test # slave database slave01: <<: *common username: db_charmer_ro database: db_charmer_sandbox22_test user_master: <<: *common database: db_charmer_sandbox22_test # shard mapping db social_shard_info: <<: *common database: db_charmer_sandbox22_test # for migrations only social_shard01: <<: *common database: db_charmer_events22_test_shard01 # for migrations only social_shard02: <<: *common database: db_charmer_events22_test_shard02 ================================================ FILE: test-project/config/environment.rb ================================================ # Load the rails application require File.expand_path('../application', __FILE__) # Initialize the rails application DbCharmerSandbox::Application.initialize! ================================================ FILE: test-project/config/environments/test.rb ================================================ DbCharmerSandbox::Application.configure do # Settings specified here will take precedence over those in config/application.rb # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false # Raise exceptions instead of rendering exception templates config.action_dispatch.show_exceptions = false # Disable request forgery protection in test environment config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test # Use SQL instead of Active Record's schema dumper when creating the test database. # This is necessary if your schema can't be completely dumped by the schema dumper, # like if you have constraints or database-specific column types # config.active_record.schema_format = :sql # Print deprecation notices to the stderr config.active_support.deprecation = :stderr end ================================================ FILE: test-project/config/initializers/backtrace_silencers.rb ================================================ # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. # Rails.backtrace_cleaner.remove_silencers! ================================================ FILE: test-project/config/initializers/db_charmer.rb ================================================ DbCharmer.connections_should_exist = false # Since we are not in production DbCharmer.enable_controller_magic! ================================================ FILE: test-project/config/initializers/secret_token.rb ================================================ # Be sure to restart your server when you modify this file. # Your secret key for verifying the integrity of signed cookies. # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. DbCharmerSandbox::Application.config.secret_token = 'bf10223f7ec7f4b2f2c6f98545ee0d172d5fe052deeba6e416e34e0a7534bc2f5a9983f331b5a799ac6544bf99d906c2a5a3bee8260d4cb985f2c096527aa3ad' ================================================ FILE: test-project/config/initializers/session_store.rb ================================================ # Be sure to restart your server when you modify this file. DbCharmerSandbox::Application.config.session_store :cookie_store, :key => '_db-charmer-sandbox_session' # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information # (create the session table with "rails generate session_migration") # DbCharmerSandbox::Application.config.session_store :active_record_store ================================================ FILE: test-project/config/initializers/sharding.rb ================================================ # Range-based shards for testing TEXTS_SHARDING_RANGES = { 0...100 => :shard1, 100..200 => :shard2, :default => :shard3 } DbCharmer::Sharding.register_connection( :name => :texts, :method => :range, :ranges => TEXTS_SHARDING_RANGES ) #------------------------------------------------ # Db blocks map sharding for testing SOCIAL_SHARDING = DbCharmer::Sharding.register_connection( :name => :social, :method => :db_block_map, :block_size => 10, :map_table => :event_shards_map, :shards_table => :event_shards_info, :connection => :social_shard_info ) ================================================ FILE: test-project/config/locales/en.yml ================================================ # Sample localization file for English. Add more files in this directory for other locales. # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. en: hello: "Hello world" ================================================ FILE: test-project/config/routes.rb ================================================ DbCharmerSandbox::Application.routes.draw do # The priority is based upon order of creation: # first created -> highest priority. # Resource routes resources :posts resources :cars # Sample of regular route: # match 'products/:id' => 'catalog#view' # Keep in mind you can assign values other than :controller and :action # Sample of named route: # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase # This route can be invoked with purchase_url(:id => product.id) # Sample resource route (maps HTTP verbs to controller actions automatically): # resources :products # Sample resource route with options: # resources :products do # member do # get 'short' # post 'toggle' # end # # collection do # get 'sold' # end # end # Sample resource route with sub-resources: # resources :products do # resources :comments, :sales # resource :seller # end # Sample resource route with more complex sub-resources # resources :products do # resources :comments # resources :sales do # get 'recent', :on => :collection # end # end # Sample resource route within a namespace: # namespace :admin do # # Directs /admin/products/* to Admin::ProductsController # # (app/controllers/admin/products_controller.rb) # resources :products # end # You can have the root of your site routed with "root" # just remember to delete public/index.html. # root :to => "welcome#index" # See how all your routes lay out with "rake routes" # This is a legacy wild controller route that's not recommended for RESTful applications. # Note: This route will make all actions in every controller accessible via GET requests. match ':controller(/:action(/:id(.:format)))' end ================================================ FILE: test-project/db/create_databases.sql ================================================ drop database if exists db_charmer_sandbox_test; create database db_charmer_sandbox_test; drop database if exists db_charmer_logs_test; create database db_charmer_logs_test; drop database if exists db_charmer_events_test_shard01; create database db_charmer_events_test_shard01; drop database if exists db_charmer_events_test_shard02; create database db_charmer_events_test_shard02; grant all privileges on db_charmer_sandbox_test.* to 'db_charmer_ro'@'localhost'; ================================================ FILE: test-project/db/migrate/20090810013829_create_log_records.rb ================================================ class CreateLogRecords < ActiveRecord::Migration db_magic :connection => :logs def self.up create_table :log_records do |t| t.integer :user_id t.string :level t.string :message t.timestamps end end def self.down drop_table :log_records end end ================================================ FILE: test-project/db/migrate/20090810013922_create_posts.rb ================================================ class CreatePosts < ActiveRecord::Migration def self.up create_table :posts do |t| t.string :title t.text :body t.integer :user_id t.timestamps end end def self.down drop_table :posts end end ================================================ FILE: test-project/db/migrate/20090810221944_create_users.rb ================================================ class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :login t.string :password t.timestamps end end def self.down drop_table :users end end ================================================ FILE: test-project/db/migrate/20100305234245_create_categories.rb ================================================ class CreateCategories < ActiveRecord::Migration def self.up create_table :categories do |t| t.string :name t.timestamps end end def self.down drop_table :categories end end ================================================ FILE: test-project/db/migrate/20100305234340_create_categories_posts.rb ================================================ class CreateCategoriesPosts < ActiveRecord::Migration def self.up pk_in_join_table = !DbCharmer.rails3? create_table :categories_posts, :id => pk_in_join_table do |t| t.integer :post_id t.integer :category_id end end def self.down drop_table :categories_posts end end ================================================ FILE: test-project/db/migrate/20100305235831_create_avatars.rb ================================================ class CreateAvatars < ActiveRecord::Migration def self.up create_table :avatars do |t| t.integer :user_id t.string :name t.timestamps end end def self.down drop_table :avatars end end ================================================ FILE: test-project/db/migrate/20100328201317_create_sharding_map_tables.rb ================================================ class CreateShardingMapTables < ActiveRecord::Migration db_magic :connection => :social_shard_info def self.up create_table :event_shards_info, :force => true do |t| t.timestamps t.string :db_host, :null => false t.integer :db_port, :null => false, :default => 3306 t.string :db_user, :null => false, :default => 'root' t.string :db_pass, :null => false, :default => '' t.string :db_name, :null => false t.boolean :open, :null => false, :default => false t.boolean :enabled, :null => false, :default => false t.integer :blocks_count, :null => false, :default => 0 end add_index :event_shards_info, [:enabled, :open, :blocks_count], :name => "alloc" create_table :event_shards_map, :id => false, :force => true do |t| t.integer :start_id, :null => false t.integer :end_id, :null => false t.integer :shard_id, :null => false t.integer :block_size, :null => false, :default => 0 t.timestamps end add_index :event_shards_map, [:start_id, :end_id], :unique => true add_index :event_shards_map, :shard_id end def self.down drop_table :event_shards_map drop_table :event_shards_info end end ================================================ FILE: test-project/db/migrate/20100330180517_create_event_tables.rb ================================================ class CreateEventTables < ActiveRecord::Migration # In test environment just use database.yml-defined connections if Rails.env.test? db_magic :connections => [ :social_shard01, :social_shard02 ] else db_magic :sharded_connection => :social end def self.up sql = <<-SQL CREATE TABLE `timeline_events` ( `event_id` int(11) NOT NULL AUTO_INCREMENT, `from_uid` int(11) NOT NULL, `to_uid` int(11) NOT NULL, `original_created_at` datetime NOT NULL, `event_type` int(11) NOT NULL, `event_data` text, `replies_count` int(11) NOT NULL DEFAULT '0', `parent_id` int(11) NOT NULL DEFAULT '0', `touched_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `on_profile` int(1) NOT NULL DEFAULT '0', PRIMARY KEY (`to_uid`,`parent_id`,`touched_at`,`event_id`), UNIQUE KEY `event_id_and_to_uid_key` (`event_id`,`to_uid`), KEY `on_profile_index` (`to_uid`,`on_profile`,`touched_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 SQL execute(sql) end def self.down drop_table :timeline_events end end ================================================ FILE: test-project/db/migrate/20100817191548_create_cars.rb ================================================ class CreateCars < ActiveRecord::Migration def self.up create_table :cars do |t| t.string :type t.string :license t.timestamps end end def self.down drop_table :cars end end ================================================ FILE: test-project/db/migrate/20111005193941_create_comments.rb ================================================ class CreateComments < ActiveRecord::Migration def self.up create_table :comments do |t| t.string :commentable_type, :null => false t.integer :commentable_id, :null => false t.text :body, :null => false t.timestamps end end def self.down drop_table :comments end end ================================================ FILE: test-project/db/seeds.rb ================================================ # This file should contain all the record creation needed to seed the database with its default values. # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). # # Examples: # # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) # Mayor.create(:name => 'Daley', :city => cities.first) ================================================ FILE: test-project/db/sharding.sql ================================================ -- MySQL dump 10.13 Distrib 5.1.44, for apple-darwin10.2.0 (i386) -- -- Host: localhost Database: db_charmer_sandbox_test -- ------------------------------------------------------ -- Server version 5.1.44 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `events_shard_info` -- DROP TABLE IF EXISTS `events_shard_info`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `events_shard_info` ( `id` int(10) unsigned NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `db_host` varchar(255) NOT NULL, `db_port` int(10) unsigned NOT NULL DEFAULT '3306', `db_user` varchar(255) NOT NULL DEFAULT 'root', `db_pass` varchar(255) NOT NULL DEFAULT '', `open` tinyint(1) unsigned NOT NULL DEFAULT '0', `enabled` tinyint(1) unsigned NOT NULL DEFAULT '0', `blocks_count` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `alloc` (`enabled`,`open`,`blocks_count`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `events_shard_info` -- LOCK TABLES `events_shard_info` WRITE; /*!40000 ALTER TABLE `events_shard_info` DISABLE KEYS */; /*!40000 ALTER TABLE `events_shard_info` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `events_shard_dict` -- DROP TABLE IF EXISTS `events_shard_dict`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `events_shard_dict` ( `start_id` int(10) unsigned NOT NULL, `end_id` int(10) unsigned NOT NULL, `shard_id` int(10) unsigned NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `block_size` int(10) unsigned NOT NULL, PRIMARY KEY (`start_id`,`end_id`), KEY `shard_id` (`shard_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `events_shard_dict` -- LOCK TABLES `events_shard_dict` WRITE; /*!40000 ALTER TABLE `events_shard_dict` DISABLE KEYS */; /*!40000 ALTER TABLE `events_shard_dict` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2010-03-22 1:37:30 ================================================ FILE: test-project/spec/controllers/posts_controller_spec.rb ================================================ require 'spec_helper' describe PostsController do fixtures :posts # Delete these examples and add some real ones it "should support db_charmer readonly actions method" do PostsController.respond_to?(:force_slave_reads).should be_true end it "index action should force slave reads" do PostsController.force_slave_reads_action?(:index).should be_true end it "create action should not force slave reads" do PostsController.force_slave_reads_action?(:create).should be_false end describe "GET 'index'" do context "slave reads enforcing (action is listed in :only)" do it "should enable enforcing" do get 'index' controller.force_slave_reads?.should be_true end it "should actually force slave reads" do Post.connection.should_not_receive(:select_value) # no counts Post.connection.should_not_receive(:select_all) # no finds Post.on_slave.connection.should_receive(:select_value).and_return(1) get 'index' end end end describe "GET 'show'" do context "slave reads enforcing (action is listed in :only)" do it "should enable enforcing" do get 'show', :id => Post.first.id controller.force_slave_reads?.should be_true end it "should actually force slave reads" do post = Post.first Post.connection.should_not_receive(:select_value) # no counts Post.connection.should_not_receive(:select_all) # no finds Post.on_slave.connection.should_receive(:select_value).and_return(1) Post.on_slave.connection.should_receive(:select_all).and_return([post.attributes]) get 'show', :id => post.id end end end describe "GET 'new'" do context "slave reads enforcing (action is listed in :except)" do it "should not enable enforcing" do get 'new' controller.force_slave_reads?.should be_false end it "should not do any actual enforcing" do Post.connection.should_receive(:select_value).and_return(0) # count Post.on_slave.connection.should_not_receive(:select_value) # no counts Post.on_slave.connection.should_not_receive(:select_all) # no selects get 'new' end end end describe "GET 'create'" do it "should redirect to post url upon successful completion" do get 'create', :post => { :title => 'xxx', :user_id => 1 } response.should redirect_to(post_url(Post.last)) end it "should create a Post record" do lambda { get 'create', :post => { :title => 'xxx', :user_id => 1 } }.should change { Post.count }.by(+1) end context "slave reads enforcing (action is not listed in force_slave_reads params)" do it "should not enable enforcing" do get 'create' controller.force_slave_reads?.should_not be_true end it "should not do any actual enforcing" do Post.on_slave.connection.should_not_receive(:select_value) Post.connection.should_receive(:select_value).once.and_return(1) get 'create' end end end describe "GET 'destroy'" do it "should redurect to index upon completion" do get 'destroy', :id => Post.first.id response.should redirect_to(:action => :index) end it "should delete a record" do lambda { get 'destroy', :id => Post.first.id }.should change { Post.count }.by(-1) end end end ================================================ FILE: test-project/spec/fixtures/avatars.yml ================================================ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: user_id: 1 name: avatar1 two: user_id: 2 name: avatar2 ================================================ FILE: test-project/spec/fixtures/categories.yml ================================================ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: id: 1 name: one two: id: 2 name: two ================================================ FILE: test-project/spec/fixtures/categories_posts.yml ================================================ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one_one: post_id: 1 category_id: 1 one_two: post_id: 1 category_id: 2 two_one: post_id: 2 category_id: 1 windoze_two: post_id: 4 category_id: 2 ================================================ FILE: test-project/spec/fixtures/comments.yml ================================================ avatar: commentable: one (Avatar) body: "This is an avatar" post: commentable: one (Post) body: "This is a post" user: commentable: one (User) body: "This is a user" ================================================ FILE: test-project/spec/fixtures/event_shards_info.yml ================================================ shard1: id: 1 db_host: localhost db_name: db_charmer_events_test_shard01 open: 1 enabled: 1 blocks_count: 2 created_at: <%= Time.now.to_s(:db) %> updated_at: <%= Time.now.to_s(:db) %> shard2: id: 2 db_host: localhost db_name: db_charmer_events_test_shard02 open: 1 enabled: 1 blocks_count: 1 created_at: <%= Time.now.to_s(:db) %> updated_at: <%= Time.now.to_s(:db) %> empty: id: 3 db_host: localhost db_name: db_charmer_events_test_shard01 open: 1 enabled: 1 blocks_count: 0 created_at: <%= Time.now.to_s(:db) %> updated_at: <%= Time.now.to_s(:db) %> ================================================ FILE: test-project/spec/fixtures/event_shards_map.yml ================================================ block1: start_id: 0 end_id: 10 shard_id: 1 block_size: 10 created_at: <%= Time.now.to_s(:db) %> updated_at: <%= Time.now.to_s(:db) %> block2: start_id: 10 end_id: 20 shard_id: 2 block_size: 10 created_at: <%= Time.now.to_s(:db) %> updated_at: <%= Time.now.to_s(:db) %> block3: start_id: 20 end_id: 30 shard_id: 1 block_size: 10 created_at: <%= Time.now.to_s(:db) %> updated_at: <%= Time.now.to_s(:db) %> ================================================ FILE: test-project/spec/fixtures/log_records.yml ================================================ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: level: MyString message: MyString two: level: MyString message: MyString ================================================ FILE: test-project/spec/fixtures/posts.yml ================================================ one: id: 1 title: MyString body: MyText user_id: 1 two: id: 2 title: MyString body: MyText user_id: 2 windoze: id: 3 title: Windows Sucks body: Yeah, it does! user_id: 3 foo: id: 4 title: Foo body: Foo body user_id: 3 ================================================ FILE: test-project/spec/fixtures/users.yml ================================================ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: id: 1 login: MyString password: MyString two: id: 2 login: MyString password: MyString bill: id: 3 login: bill password: windoze ================================================ FILE: test-project/spec/integration/multi_threading_spec.rb ================================================ require 'spec_helper' describe "DbCharmer integration tests" do def do_test(test_seconds, thread_count) start_time = Time.now.to_f threads = Array.new while threads.size < thread_count threads << Thread.new do while Time.now.to_f - start_time < test_seconds do User.create!(:login => "user#{rand}", :password => rand) User.uncached { User.on_db(:slave01).first } end end end # Wait for threads to finish threads.each(&:join) end it "should work in single-threaded mode" do do_test(10, 1) end it "should work with 5 threads" do do_test(10, 5) end it "should use default connection passed in db_magic call in all threads" do # Define a class with db magic in it class TestLogRecordWithThreads < ActiveRecord::Base self.table_name = :log_records db_magic :connection => :logs end # Check conection in the same thread TestLogRecordWithThreads.connection.db_charmer_connection_name.should == "logs" # Check connection in a different thread Thread.new { TestLogRecordWithThreads.connection.db_charmer_connection_name.should == "logs" }.join end it "should use default connection passed in db_magic call when master connection is being remapped" do class TestLogRecordWithThreadsAndRemapping < ActiveRecord::Base self.table_name = :log_records db_magic :connection => :logs end # Test in main thread expect { DbCharmer.with_remapped_databases(:master => :slave01) do TestLogRecordWithThreadsAndRemapping.first end }.to_not raise_error # Test in another thread Thread.new { expect { DbCharmer.with_remapped_databases(:master => :slave01) do TestLogRecordWithThreadsAndRemapping.first end }.to_not raise_error }.join end end unless ENV['SKIP_MT_TESTS'] ================================================ FILE: test-project/spec/models/avatar_spec.rb ================================================ require 'spec_helper' describe Avatar do before(:each) do @valid_attributes = { :user_id => 1, :name => "value for name" } end it "should create a new instance given valid attributes" do Avatar.create!(@valid_attributes) end end ================================================ FILE: test-project/spec/models/cars_spec.rb ================================================ require 'spec_helper' describe Ford, "STI model" do before(:each) do @valid_attributes = { :license => "FFGH-9134" } end it "should create a new instance given valid attributes" do Ford.create!(@valid_attributes) end it "should properly handle slave find calls" do Ford.first.should be_valid end end describe Toyota, "STI model" do before(:each) do @valid_attributes = { :license => "TFGH-9134" } end it "should create a new instance given valid attributes" do Toyota.create!(@valid_attributes) end it "should properly handle slave find calls" do Toyota.first.should be_valid end end ================================================ FILE: test-project/spec/models/categories_posts_spec.rb ================================================ require 'spec_helper' describe CategoriesPosts do before(:each) do @valid_attributes = { :post_id => 1, :category_id => 1 } end it "should create a new instance given valid attributes" do CategoriesPosts.create!(@valid_attributes) end end ================================================ FILE: test-project/spec/models/category_spec.rb ================================================ require 'spec_helper' describe Category do before(:each) do @valid_attributes = { :name => "value for name" } end it "should create a new instance given valid attributes" do Category.create!(@valid_attributes) end end ================================================ FILE: test-project/spec/models/comment_spec.rb ================================================ require 'spec_helper' describe Comment do fixtures :comments, :avatars, :posts, :users describe "preload polymorphic association" do subject do lambda { Comment.find(:all, :include => :commentable) } end it { should_not raise_error } end end ================================================ FILE: test-project/spec/models/event_spec.rb ================================================ require 'spec_helper' describe Event, "sharded model" do fixtures :event_shards_info, :event_shards_map it "should respond to shard_for method" do Event.should respond_to(:shard_for) end it "should correctly switch shards" do # Cleanup sharded tables Event.on_each_shard { |event| event.delete_all } # Check that they are empty Event.shard_for(2).all.should be_empty Event.shard_for(12).all.should be_empty # Create some data (one record in each shard) Event.shard_for(2).create!( :from_uid => 1, :to_uid => 2, :original_created_at => Time.now, :event_type => 1, :event_data => 'foo' ) Event.shard_for(12).create!( :from_uid => 1, :to_uid => 12, :original_created_at => Time.now, :event_type => 1, :event_data => 'bar' ) # Check sharded tables to make sure they have the data Event.shard_for(2).find_all_by_from_uid(1).map(&:event_data).should == [ 'foo' ] Event.shard_for(12).find_all_by_from_uid(1).map(&:event_data).should == [ 'bar' ] end it "should allocate new blocks when needed" do # Cleanup sharded tables Event.on_each_shard { |event| event.delete_all } # Check new block, it should be empty Event.shard_for(100).count.should be_zero # Create an object Event.shard_for(100).create!( :from_uid => 1, :to_uid => 100, :original_created_at => Time.now, :event_type => 1, :event_data => 'blah' ) # Check the new block Event.shard_for(100).count.should == 1 end it "should fail to perform any database operations w/o a shard specification" do Event.stub(:column_defaults).and_return({}) Event.stub(:columns_hash).and_return({}) lambda { Event.first }.should raise_error(ActiveRecord::ConnectionNotEstablished) lambda { Event.create }.should raise_error(ActiveRecord::ConnectionNotEstablished) lambda { Event.delete_all }.should raise_error(ActiveRecord::ConnectionNotEstablished) end it "should not fail when AR does some internal calls to the database" do # Cleanup sharded tables Event.on_each_shard { |event| event.delete_all } # Create an object x = Event.shard_for(100).create!( :from_uid => 1, :to_uid => 100, :original_created_at => Time.now, :event_type => 1, :event_data => 'blah' ) Event.reset_column_information lambda { x.inspect }.should_not raise_error end end ================================================ FILE: test-project/spec/models/log_record_spec.rb ================================================ require 'spec_helper' describe LogRecord do before(:each) do @valid_attributes = { :level => "value for level", :message => "value for message" } end it "should create a new instance given valid attributes" do LogRecord.create!(@valid_attributes) end end ================================================ FILE: test-project/spec/models/post_spec.rb ================================================ require 'spec_helper' describe Post do before(:each) do @valid_attributes = { :title => "value for title", :body => "value for body" } end it "should create a new instance given valid attributes" do Post.create!(@valid_attributes) end end ================================================ FILE: test-project/spec/models/range_sharded_model_spec.rb ================================================ require 'spec_helper' describe RangeShardedModel do describe "class method shard_for" do describe "should correctly set shards in range-defined shards" do [ 0, 1, 50, 99].each do |id| it "for #{id}" do RangeShardedModel.shard_for(id) do |m| m.connection.object_id.should == RangeShardedModel.on_db(:shard1).connection.object_id end end end [ 100, 101, 150, 199, 200].each do |id| it "for #{id}" do RangeShardedModel.shard_for(id) do |m| m.connection.object_id.should == RangeShardedModel.on_db(:shard2).connection.object_id end end end end describe "should correctly set shards in default shard" do [ 201, 500].each do |id| it "for #{id}" do RangeShardedModel.shard_for(id) do |m| m.connection.object_id.should == RangeShardedModel.on_db(:shard3).connection.object_id end end end end it "should raise an exception when there is no default shard and no ranged shards matched" do begin default_shard = RangeShardedModel.sharded_connection.sharder.ranges.delete(:default) lambda { RangeShardedModel.shard_for(500) }.should raise_error(ArgumentError) ensure RangeShardedModel.sharded_connection.sharder.ranges[:default] = default_shard end end end end ================================================ FILE: test-project/spec/models/user_spec.rb ================================================ require 'spec_helper' describe User do before(:each) do @valid_attributes = { :login => "value for login", :password => "value for password" } User.switch_connection_to(nil) User.db_charmer_default_connection = nil end it "should create a new instance given valid attributes" do User.create!(@valid_attributes) end it "should create a new instance in a specified db" do # Just to make sure User.on_db(:user_master).connection.object_id.should_not == User.connection.object_id # Default connection should not be touched User.connection.should_not_receive(:insert) # Only specified connection receives an insert User.on_db(:user_master).connection.should_receive(:insert) # Test! User.on_db(:user_master).create!(@valid_attributes) end end ================================================ FILE: test-project/spec/sharding/connection_spec.rb ================================================ require 'spec_helper' describe DbCharmer::Sharding::Connection do describe "in constructor" do it "should not fail if method name is correct" do lambda { DbCharmer::Sharding::Connection.new(:name => :foo, :method => :range, :ranges => {}) }.should_not raise_error end it "should fail if method name is missing" do lambda { DbCharmer::Sharding::Connection.new(:name => :foo) }.should raise_error(ArgumentError) end it "should fail if method name is invalid" do lambda { DbCharmer::Sharding::Connection.new(:name => :foo, :method => :foo) }.should raise_error(NameError) end it "should instantiate a sharder class according to the :method value" do DbCharmer::Sharding::Method::Range.should_receive(:new) DbCharmer::Sharding::Connection.new(:name => :foo, :method => :range, :ranges => {}) end end end ================================================ FILE: test-project/spec/sharding/method/db_block_map_spec.rb ================================================ require 'spec_helper' describe DbCharmer::Sharding::Method::DbBlockMap do fixtures :event_shards_info, :event_shards_map before(:each) do @sharder = DbCharmer::Sharding::Method::DbBlockMap.new( :name => :social, :block_size => 10, :map_table => :event_shards_map, :shards_table => :event_shards_info, :connection => :social_shard_info ) @conn = DbCharmer::ConnectionFactory.connect(:social_shard_info) end describe "standard interface" do it "should respond to shard_for_id" do @sharder.should respond_to(:shard_for_key) end it "should return a shard config to be used for a key" do @sharder.shard_for_key(1).should be_kind_of(Hash) end it "should have shard_connections method and return a list of db connections" do @sharder.shard_connections.should_not be_empty end end it "should correctly return shards for all blocks defined in the mapping table" do blocks = @conn.select_all("SELECT * FROM event_shards_map") blocks.each do |blk| shard = @sharder.shard_for_key(blk['start_id']) shard[:connection_name].should match(/social.*#{blk['shard_id']}$/) shard = @sharder.shard_for_key(blk['start_id'].to_i + 1) shard[:connection_name].should match(/social.*#{blk['shard_id']}$/) shard = @sharder.shard_for_key(blk['end_id'].to_i - 1) shard[:connection_name].should match(/social.*#{blk['shard_id']}$/) end end describe "for non-existing blocks" do before do @max_id = @conn.select_value("SELECT max(end_id) FROM event_shards_map").to_i Rails.cache.clear end it "should not fail" do lambda { @sharder.shard_for_key(@max_id + 1) }.should_not raise_error end it "should create a new one" do @sharder.shard_for_key(@max_id + 1).should_not be_nil end it "should assign it to the least loaded shard" do @sharder.shard_for_key(@max_id + 1)[:connection_name].should match(/shard.*03$/) end it "should not consider non-open shards" do @conn.execute("UPDATE event_shards_info SET open = 0 WHERE id = 3") @sharder.shard_for_key(@max_id + 1)[:connection_name].should_not match(/shard.*03$/) end it "should not consider disabled shards" do @conn.execute("UPDATE event_shards_info SET enabled = 0 WHERE id = 3") @sharder.shard_for_key(@max_id + 1)[:connection_name].should_not match(/shard.*03$/) end it "should increment the blocks counter on the shard" do lambda { @sharder.shard_for_key(@max_id + 1) }.should change { @conn.select_value("SELECT blocks_count FROM event_shards_info WHERE id = 3").to_i }.by(+1) end it "should raise duplicate key error when allocating same block twice" do @sharder.allocate_new_block_for_key(@max_id + 1) lambda { @sharder.allocate_new_block_for_key(@max_id + 1) }.should raise_error(ActiveRecord::StatementInvalid) end it "should handle duplicate key errors" do @sharder.shard_for_key(@max_id + 1) actual_block = @sharder.block_for_key(@max_id + 1) @sharder.should_receive(:block_for_key).twice.and_return(nil, actual_block) @sharder.shard_for_key(@max_id + 1) end end it "should fail on invalid shard references" do @conn.execute("DELETE FROM event_shards_info") lambda { @sharder.shard_for_key(1) }.should raise_error(ArgumentError) end it "should cache shards info" do shard = DbCharmer::Sharding::Method::DbBlockMap::ShardInfo.first DbCharmer::Sharding::Method::DbBlockMap::ShardInfo.should_receive(:find_by_id).once.and_return(shard) @sharder.shard_info_by_id(1) @sharder.shard_info_by_id(1) end it "should not cache shards info when explicitly asked not to" do shard = DbCharmer::Sharding::Method::DbBlockMap::ShardInfo.first DbCharmer::Sharding::Method::DbBlockMap::ShardInfo.should_receive(:find_by_id).twice.and_return(shard) @sharder.shard_info_by_id(1, false) @sharder.shard_info_by_id(1, false) end it "should cache blocks" do @sharder.block_for_key(1) @sharder.connection.should_not_receive(:select_one) @sharder.block_for_key(1) @sharder.block_for_key(2) end it "should not cache blocks if asked not to" do block = @sharder.block_for_key(1) @sharder.connection.should_receive(:select_one).twice.and_return(block) @sharder.block_for_key(1, false) @sharder.block_for_key(2, false) end end ================================================ FILE: test-project/spec/sharding/method/hash_map_spec.rb ================================================ require 'spec_helper' describe DbCharmer::Sharding::Method::HashMap do SHARDING_MAP = { 'US' => :us_users, 'CA' => :ca_users, :default => :other_users } before do @sharder = DbCharmer::Sharding::Method::HashMap.new(:map => SHARDING_MAP) end describe "standard interface" do it "should respond to shard_for_id" do @sharder.should respond_to(:shard_for_key) end it "should return a shard name to be used for an key" do @sharder.shard_for_key('US').should be_kind_of(Symbol) end it "should support default shard" do @sharder.support_default_shard?.should be_true end end describe "should correctly return shards for all keys defined in the map" do SHARDING_MAP.except(:default).each do |key, val| it "for #{key}" do @sharder.shard_for_key(key).should == val end end end it "should correctly return default shard" do @sharder.shard_for_key('UA').should == :other_users end it "should raise an exception when there is no default shard and nothing matched" do @sharder.map.delete(:default) lambda { @sharder.shard_for_key('UA') }.should raise_error(ArgumentError) end end ================================================ FILE: test-project/spec/sharding/method/range_spec.rb ================================================ require 'spec_helper' describe DbCharmer::Sharding::Method::Range do SHARDING_RANGES = { 0...100 => :shard1, 100..200 => :shard2, :default => :shard3 } before do @sharder = DbCharmer::Sharding::Method::Range.new(:ranges => SHARDING_RANGES) end describe "standard interface" do it "should respond to shard_for_id" do @sharder.should respond_to(:shard_for_key) end it "should return a shard name to be used for an key" do @sharder.shard_for_key(1).should be_kind_of(Symbol) end it "should support default shard" do @sharder.support_default_shard?.should be_true end end describe "should correctly return shards for all ids in defined ranges" do [ 0, 1, 50, 99].each do |id| it "for #{id}" do @sharder.shard_for_key(id).should == :shard1 end end [ 100, 101, 150, 199, 200].each do |id| it "for #{id}" do @sharder.shard_for_key(id).should == :shard2 end end end describe "should correctly return shard for all ids outside the ranges if has a default" do [ 201, 500].each do |id| it "for #{id}" do @sharder.shard_for_key(id).should == :shard3 end end end it "should raise an exception when there is no default shard and no ranges matched" do @sharder.ranges.delete(:default) lambda { @sharder.shard_for_key(500) }.should raise_error(ArgumentError) end end ================================================ FILE: test-project/spec/sharding/sharding_spec.rb ================================================ require 'spec_helper' describe "DbCharmer::Sharding" do describe "in register_connection method" do it "should raise an exception if passed config has no :name parameter" do lambda { DbCharmer::Sharding.register_connection(:method => :range, :ranges => { :default => :foo }) }.should raise_error(ArgumentError) end it "should not raise an exception if passed config has all required params" do lambda { DbCharmer::Sharding.register_connection(:method => :range, :ranges => { :default => :foo }, :name => :foo) }.should_not raise_error end end describe "in sharded_connection method" do it "should raise an error for invalid connection names" do lambda { DbCharmer::Sharding.sharded_connection(:blah) }.should raise_error(ArgumentError) end end end ================================================ FILE: test-project/spec/spec_helper.rb ================================================ # This file is copied to spec/ when you run 'rails generate rspec:install' ENV["RAILS_ENV"] = 'test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} RSpec.configure do |config| # == Mock Framework # # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: # # config.mock_with :mocha # config.mock_with :flexmock # config.mock_with :rr config.mock_with :rspec # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = "#{::Rails.root}/spec/fixtures" # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. config.use_transactional_fixtures = false config.use_instantiated_fixtures = false end ================================================ FILE: test-project/spec/support/rails31_stub_connection.rb ================================================ def stub_columns_for_rails31(connection) return unless DbCharmer.rails31? connection.abstract_connection_class.retrieve_connection.stub(:columns).and_return([]) end ================================================ FILE: test-project/spec/unit/abstract_adapter/log_formatting_spec.rb ================================================ require 'spec_helper' if DbCharmer.rails2? describe 'AbstractAdapter' do it "should respond to connection_name accessor" do ActiveRecord::Base.connection.respond_to?(:connection_name).should be_true end it "should have connection_name read accessor working" do DbCharmer::ConnectionFactory.generate_abstract_class('logs').connection.connection_name.should == 'logs' DbCharmer::ConnectionFactory.generate_abstract_class('slave01').connection.connection_name.should == 'slave01' ActiveRecord::Base.connection.connection_name.should be_nil end it "should append connection name to log records on non-default connections" do User.switch_connection_to nil default_message = User.connection.send(:format_log_entry, 'hello world') switched_message = User.on_db(:slave01).connection.send(:format_log_entry, 'hello world') switched_message.should_not == default_message switched_message.should match(/slave01/) end end end ================================================ FILE: test-project/spec/unit/action_controller/force_slave_reads_spec.rb ================================================ require 'spec_helper' class BlahController < ActionController::Base; end describe ActionController, "with force_slave_reads extension" do before do BlahController.force_slave_reads({}) # cleanup status end it "should not force slave reads when there are no actions defined as forced" do BlahController.force_slave_reads_action?(:index).should be_false end it "should force slave reads for :only actions" do BlahController.force_slave_reads :only => :index BlahController.force_slave_reads_action?(:index).should be_true end it "should not force slave reads for non-listed actions when there is :only parameter" do BlahController.force_slave_reads :only => :index BlahController.force_slave_reads_action?(:show).should be_false end it "should not force slave reads for :except actions" do BlahController.force_slave_reads :except => :delete BlahController.force_slave_reads_action?(:delete).should be_false end it "should force slave reads for non-listed actions when there is :except parameter" do BlahController.force_slave_reads :except => :delete BlahController.force_slave_reads_action?(:index).should be_true end it "should not force slave reads for actions listed in both :except and :only lists" do BlahController.force_slave_reads :only => :delete, :except => :delete BlahController.force_slave_reads_action?(:delete).should be_false end it "should not force slave reads for non-listed actions when there are :except and :only lists present" do BlahController.force_slave_reads :only => :index, :except => :delete BlahController.force_slave_reads_action?(:show).should be_false end end ================================================ FILE: test-project/spec/unit/active_record/association_preload_spec.rb ================================================ require 'spec_helper' if DbCharmer.rails2? describe "ActiveRecord preload_associations method" do it "should be public" do ActiveRecord::Base.public_methods.collect(&:to_s).member?('preload_associations').should be_true end end end describe "ActiveRecord in finder methods" do fixtures :categories, :users, :posts, :categories_posts, :avatars before do Post.db_magic :connection => nil User.db_magic :connection => nil end after do Post.db_magic(Post::DB_MAGIC_DEFAULT_PARAMS) end it "should switch all belongs_to association connections when :include is used" do User.connection.should_not_receive(:select_all) Post.on_db(:slave01).all(:include => :user) end it "should switch all has_many association connections when :include is used" do Post.connection.should_not_receive(:select_all) User.on_db(:slave01).all(:include => :posts) end it "should switch all has_one association connections when :include is used" do Avatar.connection.should_not_receive(:select_all) User.on_db(:slave01).all(:include => :avatar) end it "should switch all has_and_belongs_to_many association connections when :include is used" do Post.connection.should_not_receive(:select_all) Category.on_db(:slave01).all(:include => :posts) end #------------------------------------------------------------------------------------------- it "should not switch assocations when called on a top-level connection" do User.connection.should_receive(:select_all).and_return([]) Post.all(:include => :user) end it "should not switch connection when association model and main model are on different servers" do LogRecord.connection.should_receive(:select_all).and_return([]) User.on_db(:slave01).all(:include => :log_records) end end ================================================ FILE: test-project/spec/unit/active_record/association_proxy_spec.rb ================================================ require 'spec_helper' describe "DbCharmer::AssociationProxy extending AR::Associations" do fixtures :users, :posts it "should add proxy? => true method" do users(:bill).posts.proxy?.should be_true end describe "in has_many associations" do before do @user = users(:bill) @posts = @user.posts.all Post.switch_connection_to(:logs) User.switch_connection_to(:logs) end after do Post.switch_connection_to(nil) User.switch_connection_to(nil) end it "should implement on_db proxy" do Post.connection.should_not_receive(:select_all) User.connection.should_not_receive(:select_all) stub_columns_for_rails31 Post.on_db(:logs).connection Post.on_db(:slave01).connection.should_receive(:select_all).and_return(@posts.map { |p| p.attributes }) assert_equal @posts, @user.posts.on_db(:slave01) end it "on_db should work in prefix mode" do Post.connection.should_not_receive(:select_all) User.connection.should_not_receive(:select_all) stub_columns_for_rails31 Post.on_db(:logs).connection Post.on_db(:slave01).connection.should_receive(:select_all).and_return(@posts.map { |p| p.attributes }) @user.on_db(:slave01).posts.should == @posts end it "should actually proxy calls to the rails association proxy" do Post.switch_connection_to(nil) @user.posts.on_db(:slave01).count.should == @user.posts.count end it "should work with named scopes" do Post.switch_connection_to(nil) @user.posts.windows_posts.on_db(:slave01).count.should == @user.posts.windows_posts.count end it "should work with chained named scopes" do Post.switch_connection_to(nil) @user.posts.windows_posts.dummy_scope.on_db(:slave01).count.should == @user.posts.windows_posts.dummy_scope.count end end describe "in belongs_to associations" do before do @post = posts(:windoze) @user = users(:bill) User.switch_connection_to(:logs) User.connection.object_id.should_not == Post.connection.object_id end after do User.switch_connection_to(nil) end it "should implement on_db proxy" do pending Post.connection.should_not_receive(:select_all) User.connection.should_not_receive(:select_all) User.on_db(:slave01).connection.should_receive(:select_all).once.and_return([ @user ]) @post.user.on_db(:slave01).should == @post.user end it "on_db should work in prefix mode" do pending Post.connection.should_not_receive(:select_all) User.connection.should_not_receive(:select_all) User.on_db(:slave01).connection.should_receive(:select_all).once.and_return([ @user ]) @post.on_db(:slave01).user.should == @post.user end it "should actually proxy calls to the rails association proxy" do User.switch_connection_to(nil) @post.user.on_db(:slave01).should == @post.user end end end ================================================ FILE: test-project/spec/unit/active_record/class_attributes_spec.rb ================================================ require 'spec_helper' class FooModel < ActiveRecord::Base; end describe DbCharmer, "for ActiveRecord models" do context "in db_charmer_connection_proxy methods" do before do FooModel.db_charmer_connection_proxy = nil FooModel.db_charmer_default_connection = nil end it "should implement both accessor methods" do proxy = double('connection proxy') FooModel.db_charmer_connection_proxy = proxy FooModel.db_charmer_connection_proxy.should be(proxy) end end context "in db_charmer_default_connection methods" do before do FooModel.db_charmer_default_connection = nil FooModel.db_charmer_default_connection = nil end it "should implement both accessor methods" do conn = double('connection') FooModel.db_charmer_default_connection = conn FooModel.db_charmer_default_connection.should be(conn) end end context "in db_charmer_opts methods" do before do FooModel.db_charmer_opts = nil end it "should implement both accessor methods" do opts = { :foo => :bar} FooModel.db_charmer_opts = opts FooModel.db_charmer_opts.should be(opts) end end context "in db_charmer_slaves methods" do it "should return [] if no slaves set for a model" do FooModel.db_charmer_slaves = nil FooModel.db_charmer_slaves.should == [] end it "should implement both accessor methods" do proxy = double('connection proxy') FooModel.db_charmer_slaves = [ proxy ] FooModel.db_charmer_slaves.should == [ proxy ] end it "should implement random slave selection" do FooModel.db_charmer_slaves = [ :proxy1, :proxy2, :proxy3 ] srand(0) FooModel.db_charmer_random_slave.should == :proxy1 FooModel.db_charmer_random_slave.should == :proxy2 FooModel.db_charmer_random_slave.should == :proxy1 FooModel.db_charmer_random_slave.should == :proxy2 FooModel.db_charmer_random_slave.should == :proxy2 FooModel.db_charmer_random_slave.should == :proxy3 end end context "in db_charmer_connection_levels methods" do it "should return 0 by default" do FooModel.db_charmer_connection_level = nil FooModel.db_charmer_connection_level.should == 0 end it "should implement both accessor methods and support inc/dec operations" do FooModel.db_charmer_connection_level = 1 FooModel.db_charmer_connection_level.should == 1 FooModel.db_charmer_connection_level += 1 FooModel.db_charmer_connection_level.should == 2 FooModel.db_charmer_connection_level -= 1 FooModel.db_charmer_connection_level.should == 1 end it "should implement db_charmer_top_level_connection? method" do FooModel.db_charmer_connection_level = 1 FooModel.should_not be_db_charmer_top_level_connection FooModel.db_charmer_connection_level = 0 FooModel.should be_db_charmer_top_level_connection end end context "in connection method" do it "should return AR's original connection if no connection proxy is set" do FooModel.db_charmer_connection_proxy = nil FooModel.db_charmer_default_connection = nil FooModel.connection.should be_kind_of(ActiveRecord::ConnectionAdapters::AbstractAdapter) end end context "in db_charmer_force_slave_reads? method" do it "should use per-model settings when possible" do FooModel.db_charmer_force_slave_reads = true DbCharmer.should_not_receive(:force_slave_reads?) FooModel.db_charmer_force_slave_reads?.should be_true end it "should use global settings when local setting is false" do FooModel.db_charmer_force_slave_reads = false DbCharmer.should_receive(:force_slave_reads?).and_return(true) FooModel.db_charmer_force_slave_reads?.should be_true DbCharmer.should_receive(:force_slave_reads?).and_return(false) FooModel.db_charmer_force_slave_reads?.should be_false end end end ================================================ FILE: test-project/spec/unit/active_record/connection_switching_spec.rb ================================================ require 'spec_helper' class FooModelForConnSwitching < ActiveRecord::Base; end class BarModelForConnSwitching < ActiveRecord::Base; end describe DbCharmer, "AR connection switching" do describe "in switch_connection_to method" do before(:all) do BarModelForConnSwitching.hijack_connection! end before :each do @proxy = double('proxy') @proxy.stub(:db_charmer_connection_name).and_return(:myproxy) end before do BarModelForConnSwitching.db_charmer_connection_proxy = @proxy BarModelForConnSwitching.connection.should be(@proxy) end it "should accept nil and reset connection to default" do BarModelForConnSwitching.switch_connection_to(nil) BarModelForConnSwitching.connection.should be(ActiveRecord::Base.connection) end it "should accept a string and generate an abstract class with connection factory" do BarModelForConnSwitching.switch_connection_to('logs') BarModelForConnSwitching.connection.object_id == DbCharmer::ConnectionFactory.connect('logs').object_id end it "should accept a symbol and generate an abstract class with connection factory" do BarModelForConnSwitching.switch_connection_to(:logs) BarModelForConnSwitching.connection.object_id.should == DbCharmer::ConnectionFactory.connect('logs').object_id end it "should accept a model and use its connection proxy value" do FooModelForConnSwitching.switch_connection_to(:logs) BarModelForConnSwitching.switch_connection_to(FooModelForConnSwitching) BarModelForConnSwitching.connection.object_id.should == DbCharmer::ConnectionFactory.connect('logs').object_id end context "with a hash parameter" do before do @conf = { :adapter => 'mysql', :username => "db_charmer_ro", :database => "db_charmer_sandbox_test", :connection_name => 'sanbox_ro' } end it "should fail if there is no :connection_name parameter" do @conf.delete(:connection_name) lambda { BarModelForConnSwitching.switch_connection_to(@conf) }.should raise_error(ArgumentError) end it "generate an abstract class with connection factory" do BarModelForConnSwitching.switch_connection_to(@conf) BarModelForConnSwitching.connection.object_id.should == DbCharmer::ConnectionFactory.connect_to_db(@conf[:connection_name], @conf).object_id end end it "should support connection switching for AR::Base" do ActiveRecord::Base.switch_connection_to(:logs) ActiveRecord::Base.connection.object_id == DbCharmer::ConnectionFactory.connect('logs').object_id ActiveRecord::Base.switch_connection_to(nil) end end end describe DbCharmer, "for ActiveRecord models" do describe "in establish_real_connection_if_exists method" do it "should check connection name if requested" do lambda { FooModelForConnSwitching.establish_real_connection_if_exists(:foo, true) }.should raise_error(ArgumentError) end it "should not check connection name if not reqested" do lambda { FooModelForConnSwitching.establish_real_connection_if_exists(:foo) }.should_not raise_error end it "should not check connection name if reqested not to" do lambda { FooModelForConnSwitching.establish_real_connection_if_exists(:foo, false) }.should_not raise_error end it "should establish connection when connection configuration exists" do FooModelForConnSwitching.should_receive(:establish_connection) FooModelForConnSwitching.establish_real_connection_if_exists(:logs) end it "should not establish connection even when connection configuration does not exist" do FooModelForConnSwitching.should_not_receive(:establish_connection) FooModelForConnSwitching.establish_real_connection_if_exists(:blah) end end end ================================================ FILE: test-project/spec/unit/active_record/db_magic_spec.rb ================================================ require 'spec_helper' class Blah < ActiveRecord::Base; end describe "In ActiveRecord models" do describe "db_magic method" do context "with :connection parameter" do after do DbCharmer.connections_should_exist = false end it "should change model's connection to specified one" do Blah.db_magic :connection => :logs Blah.connection.object_id.should == DbCharmer::ConnectionFactory.connect(:logs).object_id end it "should pass :should_exist paramater value to the underlying connection logic" do DbCharmer::ConnectionFactory.should_receive(:connect).with(:logs, 'blah') Blah.db_magic :connection => :logs, :should_exist => 'blah' DbCharmer.connections_should_exist = true DbCharmer::ConnectionFactory.should_receive(:connect).with(:logs, false) Blah.db_magic :connection => :logs, :should_exist => false end it "should use global DbCharmer's connections_should_exist attribute if no :should_exist passed" do DbCharmer.connections_should_exist = true DbCharmer::ConnectionFactory.should_receive(:connect).with(:logs, true) Blah.db_magic :connection => :logs end end context "with :slave or :slaves parameter" do it "should merge :slave and :slaves values" do Blah.db_charmer_slaves = [] Blah.db_charmer_slaves.should be_empty Blah.db_magic :slave => :slave01 Blah.db_charmer_slaves.size.should == 1 Blah.db_magic :slaves => [ :slave01 ] Blah.db_charmer_slaves.size.should == 1 Blah.db_magic :slaves => [ :slave01 ], :slave => :logs Blah.db_charmer_slaves.size.should == 2 end it "should make db_charmer_force_slave_reads = true by default" do Blah.db_magic :slave => :slave01 Blah.db_charmer_force_slave_reads.should be_true end it "should pass force_slave_reads value to db_charmer_force_slave_reads" do Blah.db_magic :slave => :slave01, :force_slave_reads => false Blah.db_charmer_force_slave_reads.should be_false Blah.db_magic :slave => :slave01, :force_slave_reads => true Blah.db_charmer_force_slave_reads.should be_true end end it "should set up a hook to propagate db_magic params to all the children models" do class ParentFoo < ActiveRecord::Base db_magic :foo => :bar end class ChildFoo < ParentFoo; end ChildFoo.db_charmer_opts.should == ParentFoo.db_charmer_opts end context "with :sharded parameter" do class ShardTestingFoo < ActiveRecord::Base db_magic :sharded => { :key => :id, :sharded_connection => :texts } end it "should add shard_for method to the model" do ShardTestingFoo.should respond_to(:shard_for) end end end end ================================================ FILE: test-project/spec/unit/active_record/master_slave_routing_spec.rb ================================================ require 'spec_helper' describe "ActiveRecord slave-enabled models" do before do class User < ActiveRecord::Base db_magic :connection => :user_master, :slave => :slave01 end end describe "in finder method" do [ :last, :first, :all ].each do |meth| describe meth do it "should go to the slave if called on the first level connection" do User.on_slave.connection.should_receive(:select_all).and_return([]) User.send(meth) end it "should not change connection if called in an on_db block" do stub_columns_for_rails31 User.on_db(:logs).connection User.on_db(:logs).connection.should_receive(:select_all).and_return([]) User.on_slave.connection.should_not_receive(:select_all) User.on_db(:logs).send(meth) end it "should not change connection when it's already been changed by on_slave call" do pending "rails3: not sure if we need this spec" if DbCharmer.rails3? User.on_slave do User.on_slave.connection.should_receive(:select_all).and_return([]) User.should_not_receive(:on_db) User.send(meth) end end it "should not change connection if called in a transaction" do User.on_db(:user_master).connection.should_receive(:select_all).and_return([]) User.on_slave.connection.should_not_receive(:select_all) User.transaction { User.send(meth) } end end end it "should go to the master if called find with :lock => true option" do User.on_db(:user_master).connection.should_receive(:select_all).and_return([]) User.on_slave.connection.should_not_receive(:select_all) User.find(:first, :lock => true) end it "should not go to the master if no :lock => true option passed" do User.on_db(:user_master).connection.should_not_receive(:select_all) User.on_slave.connection.should_receive(:select_all).and_return([]) User.find(:first) end it "should correctly pass all find params to the underlying code" do User.delete_all u1 = User.create(:login => 'foo') u2 = User.create(:login => 'bar') User.find(:all, :conditions => { :login => 'foo' }).should == [ u1 ] User.find(:all, :limit => 1).size.should == 1 User.find(:first, :conditions => { :login => 'bar' }).should == u2 end end describe "in calculation method" do [ :count, :minimum, :maximum, :average ].each do |meth| describe meth do it "should go to the slave if called on the first level connection" do User.on_slave.connection.should_receive(:select_value).and_return(1) User.send(meth, :id).should == 1 end it "should not change connection if called in an on_db block" do User.on_db(:logs).connection.should_receive(:select_value).and_return(1) User.on_slave.connection.should_not_receive(:select_value) User.on_db(:logs).send(meth, :id).should == 1 end it "should not change connection when it's already been changed by an on_slave call" do pending "rails3: not sure if we need this spec" if DbCharmer.rails3? User.on_slave do User.on_slave.connection.should_receive(:select_value).and_return(1) User.should_not_receive(:on_db) User.send(meth, :id).should == 1 end end it "should not change connection if called in a transaction" do User.on_db(:user_master).connection.should_receive(:select_value).and_return(1) User.on_slave.connection.should_not_receive(:select_value) User.transaction { User.send(meth, :id).should == 1 } end end end end describe "in data manipulation methods" do it "should go to the master by default" do User.on_db(:user_master).connection.should_receive(:delete) User.delete_all end it "should go to the master even in slave-enabling chain calls" do User.on_db(:user_master).connection.should_receive(:delete) User.on_slave.delete_all end it "should go to the master even in slave-enabling block calls" do User.on_db(:user_master).connection.should_receive(:delete) User.on_slave { |u| u.delete_all } end end describe "in instance method" do describe "reload" do it "should always be done on the master" do User.delete_all u = User.create User.on_db(:user_master).connection.should_receive(:select_all).and_return([{}]) User.on_slave.connection.should_not_receive(:select_all) User.on_slave { u.reload } end end end end ================================================ FILE: test-project/spec/unit/active_record/migration/multi_db_migrations_spec.rb ================================================ require 'spec_helper' class SpecMigration < ActiveRecord::Migration def self.up execute "UPDATE log_records SET level = 'debug'" end def self.down execute "UPDATE log_records SET level = 'blah'" end end class SpecMultiDbMigration < ActiveRecord::Migration db_magic :connection => :logs def self.up execute "UPDATE log_records SET level = 'debug'" end def self.down execute "UPDATE log_records SET level = 'blah'" end end class SpecMultiDbMigration2 < ActiveRecord::Migration def self.up execute "UPDATE log_records SET level = 'yo'" on_db(:logs) { execute "UPDATE log_records SET level = 'debug'" } end def self.down execute "UPDATE log_records SET level = 'bar'" on_db(:logs) { execute "UPDATE log_records SET level = 'blah'" } end end class SpecMultiDbMigration3 < ActiveRecord::Migration db_magic :connection => [:logs, :default] def self.up execute "UPDATE log_records SET level = 'hoho'" end def self.down execute "UPDATE log_records SET level = 'blah'" end end class SpecMultiDbMigration4 < ActiveRecord::Migration db_magic :connections => [:logs, :default] def self.up execute "UPDATE log_records SET level = 'hoho'" end def self.down execute "UPDATE log_records SET level = 'blah'" end end class SpecMultiDbMigration5 < ActiveRecord::Migration db_magic :connections => [:logs, :default] def up execute "UPDATE log_records SET level = 'hoho'" end def down execute "UPDATE log_records SET level = 'blah'" end end class SpecMultiDbMigration6 < ActiveRecord::Migration def change on_db(:logs) do create_table :logs_rails32_test do |t| t.text :t end end end end describe "Multi-db migractions" do before(:all) do DbCharmer.connections_should_exist = true end after(:all) do DbCharmer.connections_should_exist = false end def connection_with_name(name) DbCharmer::ConnectionFactory.connect(name).abstract_connection_class.retrieve_connection end describe "w/o any magic calls" do it "should send all up requests to the default connection" do ActiveRecord::Base.connection.should_receive(:execute).with("UPDATE log_records SET level = 'debug'") SpecMigration.migrate(:up) end it "should send all down requests to the default connection" do ActiveRecord::Base.connection.should_receive(:execute).with("UPDATE log_records SET level = 'blah'") SpecMigration.migrate(:down) end describe "after AR::Migration db_magic call" do it "should use default migration config" do ActiveRecord::Migration.db_magic :connection => :logs ActiveRecord::Base.connection.should_not_receive(:execute) connection_with_name(:logs).should_receive(:execute).with("UPDATE log_records SET level = 'debug'") SpecMigration.migrate(:up) ActiveRecord::Migration.db_magic :connection => :default end end end describe "with db_magic calls" do it "should send all up requests to specified connection" do ActiveRecord::Base.connection.should_not_receive(:execute) connection_with_name(:logs).should_receive(:execute).with("UPDATE log_records SET level = 'debug'") SpecMultiDbMigration.migrate(:up) end it "should send all down requests to specified connection" do ActiveRecord::Base.connection.should_not_receive(:execute) connection_with_name(:logs).should_receive(:execute).with("UPDATE log_records SET level = 'blah'") SpecMultiDbMigration.migrate(:down) end describe "after AR::Migration db_magic call" do it "should use specified connection and ignore global migration config" do ActiveRecord::Migration.db_magic :connection => :slave01 ActiveRecord::Base.connection.should_not_receive(:execute) connection_with_name(:slave01).should_not_receive(:execute) connection_with_name(:logs).should_receive(:execute).with("UPDATE log_records SET level = 'debug'") SpecMultiDbMigration.migrate(:up) ActiveRecord::Migration.db_magic :connection => :default end end end describe "with on_db blocks" do it "should send specified up requests to specified connection" do ActiveRecord::Base.connection.should_receive(:execute).with("UPDATE log_records SET level = 'yo'") connection_with_name(:logs).should_receive(:execute).with("UPDATE log_records SET level = 'debug'") SpecMultiDbMigration2.migrate(:up) end it "should send secified down requests to specified connection" do ActiveRecord::Base.connection.should_receive(:execute).with("UPDATE log_records SET level = 'bar'") connection_with_name(:logs).should_receive(:execute).with("UPDATE log_records SET level = 'blah'") SpecMultiDbMigration2.migrate(:down) end end describe "with db_magic calls" do it "should send all up requests to specified connection" do ActiveRecord::Base.connection.should_receive(:execute).with("UPDATE log_records SET level = 'hoho'") connection_with_name(:logs).should_receive(:execute).with("UPDATE log_records SET level = 'hoho'") SpecMultiDbMigration3.migrate(:up) end it "should send all down requests to specified connection" do ActiveRecord::Base.connection.should_receive(:execute).with("UPDATE log_records SET level = 'blah'") connection_with_name(:logs).should_receive(:execute).with("UPDATE log_records SET level = 'blah'") SpecMultiDbMigration3.migrate(:down) end end describe "with db_magic calls" do it "should send all up requests to specified connection" do ActiveRecord::Base.connection.should_receive(:execute).with("UPDATE log_records SET level = 'hoho'") connection_with_name(:logs).should_receive(:execute).with("UPDATE log_records SET level = 'hoho'") SpecMultiDbMigration4.migrate(:up) end it "should send all down requests to specified connection" do ActiveRecord::Base.connection.should_receive(:execute).with("UPDATE log_records SET level = 'blah'") connection_with_name(:logs).should_receive(:execute).with("UPDATE log_records SET level = 'blah'") SpecMultiDbMigration4.migrate(:down) end end if DbCharmer.rails31? describe 'with db_magic calls in instance methods' do it "should send all up requests to specified connection" do ActiveRecord::Base.connection.should_receive(:execute).with("UPDATE log_records SET level = 'hoho'") connection_with_name(:logs).should_receive(:execute).with("UPDATE log_records SET level = 'hoho'") SpecMultiDbMigration5.migrate(:up) end it "should send all down requests to specified connection" do ActiveRecord::Base.connection.should_receive(:execute).with("UPDATE log_records SET level = 'blah'") connection_with_name(:logs).should_receive(:execute).with("UPDATE log_records SET level = 'blah'") SpecMultiDbMigration5.migrate(:down) end end describe 'with db_magic calls in recorder' do it "should send all up requests to specified connection" do ActiveRecord::Base.connection.should_not_receive(:execute) connection_with_name(:logs).should_receive(:execute).with(/CREATE TABLE/) SpecMultiDbMigration6.migrate(:up) end it "should send all down requests to specified connection" do ActiveRecord::Base.connection.should_not_receive(:execute) connection_with_name(:logs).should_receive(:execute).with(/DROP TABLE/) SpecMultiDbMigration6.migrate(:down) end end end end ================================================ FILE: test-project/spec/unit/active_record/named_scope/named_scope_spec.rb ================================================ require 'spec_helper' describe "Named scopes" do fixtures :users, :posts before(:all) do Post.switch_connection_to(nil) User.switch_connection_to(nil) end describe "prefixed by on_db" do it "should work on the proxy" do Post.on_db(:slave01).windows_posts.should == Post.windows_posts end it "should actually run queries on the specified db" do Post.on_db(:slave01).connection.should_receive(:select_all).once.and_return([]) Post.on_db(:slave01).windows_posts.all # Post.windows_posts.all end it "should work with long scope chains" do Post.on_db(:slave01).connection.should_not_receive(:select_all) Post.on_db(:slave01).connection.should_receive(:select_value).and_return(5) Post.on_db(:slave01).windows_posts.count.should == 5 end it "should work with associations" do users(:bill).posts.on_db(:slave01).windows_posts.all.should == users(:bill).posts.windows_posts end end describe "postfixed by on_db" do it "should work on the proxy" do Post.windows_posts.on_db(:slave01).should == Post.windows_posts end it "should actually run queries on the specified db" do Post.on_db(:slave01).connection.object_id.should_not == Post.connection.object_id Post.on_db(:slave01).connection.should_receive(:select_all).and_return([]) Post.windows_posts.on_db(:slave01).all Post.windows_posts.all end it "should work with long scope chains" do Post.on_db(:slave01).connection.should_not_receive(:select_all) Post.on_db(:slave01).connection.should_receive(:select_value).and_return(5) Post.windows_posts.on_db(:slave01).count.should == 5 end it "should work with associations" do users(:bill).posts.windows_posts.on_db(:slave01).all.should == users(:bill).posts.windows_posts end end end ================================================ FILE: test-project/spec/unit/active_record/relation_spec.rb ================================================ require 'spec_helper' if DbCharmer.rails3? describe "ActiveRecord::Relation for a model with db_magic" do before do class RelTestModel < ActiveRecord::Base db_magic :connection => nil self.table_name = :users end end it "should be created with correct default connection" do rel = RelTestModel.on_db(:user_master).where("1=1") rel.db_charmer_connection.object_id.should == RelTestModel.on_db(:user_master).connection.object_id end it "should switch the default connection when on_db called" do rel = RelTestModel.where("1=1") rel_master = rel.on_db(:user_master) rel_master.db_charmer_connection.object_id.should_not == rel.db_charmer_connection.object_id end it "should keep default connection value when relation is cloned in chained calls" do rel = RelTestModel.on_db(:user_master).where("1=1") rel.where("2=2").db_charmer_connection.object_id.should == rel.db_charmer_connection.object_id end it "should execute select queries on the default connection" do rel = RelTestModel.on_db(:user_master).where("1=1") RelTestModel.on_db(:user_master).connection.should_receive(:select_all).and_return([]) RelTestModel.connection.should_not_receive(:select_all) rel.first end it "should execute delete queries on the default connection" do rel = RelTestModel.on_db(:user_master).where("1=1") RelTestModel.on_db(:user_master).connection.should_receive(:delete) RelTestModel.connection.should_not_receive(:delete) rel.delete_all end it "should execute update_all queries on the default connection" do rel = RelTestModel.on_db(:user_master).where("1=1") RelTestModel.on_db(:user_master).connection.should_receive(:update) RelTestModel.connection.should_not_receive(:update) rel.update_all("login = login + 'new'") end it "should execute update queries on the default connection" do rel = RelTestModel.on_db(:user_master).where("1=1") user = RelTestModel.create!(:login => 'login') RelTestModel.on_db(:user_master).connection.should_receive(:update) RelTestModel.connection.should_not_receive(:update) rel.update(user.id, :login => "foobar") end it "should return correct connection" do rel = RelTestModel.on_db(:user_master).where("1=1") rel.connection.object_id.should == rel.db_charmer_connection.object_id end end end ================================================ FILE: test-project/spec/unit/connection_factory_spec.rb ================================================ require 'spec_helper' describe DbCharmer::ConnectionFactory do context "in generate_abstract_class method" do it "should fail if requested connection config does not exists" do lambda { DbCharmer::ConnectionFactory.generate_abstract_class('foo') }.should raise_error(ArgumentError) end it "should not fail if requested connection config does not exists and should_exist = false" do lambda { DbCharmer::ConnectionFactory.generate_abstract_class('foo', false) }.should_not raise_error end it "should fail if requested connection config does not exists and should_exist = true" do lambda { DbCharmer::ConnectionFactory.generate_abstract_class('foo', true) }.should raise_error(ArgumentError) end it "should generate abstract connection classes" do klass = DbCharmer::ConnectionFactory.generate_abstract_class('foo', false) klass.superclass.should be(ActiveRecord::Base) end it "should work with weird connection names" do klass = DbCharmer::ConnectionFactory.generate_abstract_class('foo.bar@baz#blah', false) klass.superclass.should be(ActiveRecord::Base) end end context "in generate_empty_abstract_ar_class method" do it "should generate an abstract connection class" do klass = DbCharmer::ConnectionFactory.generate_empty_abstract_ar_class('::MyFooAbstractClass') klass.superclass.should be(ActiveRecord::Base) end end context "in establish_connection method" do it "should generate an abstract class" do klass = mock('AbstractClass') conn = mock('connection1') klass.stub!(:retrieve_connection).and_return(conn) DbCharmer::ConnectionFactory.should_receive(:generate_abstract_class).and_return(klass) DbCharmer::ConnectionFactory.establish_connection(:foo).should be(conn) end it "should create and return a connection proxy for the abstract class" do klass = mock('AbstractClass') DbCharmer::ConnectionFactory.should_receive(:generate_abstract_class).and_return(klass) DbCharmer::ConnectionProxy.should_receive(:new).with(klass, :foo) DbCharmer::ConnectionFactory.establish_connection(:foo) end end context "in establish_connection_to_db method" do it "should generate an abstract class" do klass = mock('AbstractClass') conn = mock('connection2') klass.stub!(:establish_connection) klass.stub!(:retrieve_connection).and_return(conn) DbCharmer::ConnectionFactory.should_receive(:generate_empty_abstract_ar_class).and_return(klass) DbCharmer::ConnectionFactory.establish_connection_to_db(:foo, :username => :foo).should be(conn) end it "should create and return a connection proxy for the abstract class" do klass = mock('AbstractClass') klass.stub!(:establish_connection) DbCharmer::ConnectionFactory.should_receive(:generate_empty_abstract_ar_class).and_return(klass) DbCharmer::ConnectionProxy.should_receive(:new).with(klass, :foo) DbCharmer::ConnectionFactory.establish_connection_to_db(:foo, :username => :foo) end end context "in connect method" do before do DbCharmer::ConnectionFactory.reset! end it "should return a connection proxy" do DbCharmer::ConnectionFactory.connect(:logs).should be_kind_of(ActiveRecord::ConnectionAdapters::AbstractAdapter) end # should_receive is evil on a singletone classes # it "should memoize proxies" do # conn = mock('connection3') # DbCharmer::ConnectionFactory.should_receive(:establish_connection).with('foo', false).once.and_return(conn) # DbCharmer::ConnectionFactory.connect(:foo) # DbCharmer::ConnectionFactory.connect(:foo) # end end context "in connect_to_db method" do before do DbCharmer::ConnectionFactory.reset! @conf = { :adapter => 'mysql', :username => "db_charmer_ro", :database => "db_charmer_sandbox_test", :connection_name => 'sanbox_ro' } end it "should return a connection proxy" do DbCharmer::ConnectionFactory.connect_to_db(@conf[:connection_name], @conf).should be_kind_of(ActiveRecord::ConnectionAdapters::AbstractAdapter) end # should_receive is evil on a singletone classes # it "should memoize proxies" do # conn = mock('connection4') # DbCharmer::ConnectionFactory.should_receive(:establish_connection_to_db).with(@conf[:connection_name], @conf).once.and_return(conn) # DbCharmer::ConnectionFactory.connect_to_db(@conf[:connection_name], @conf) # DbCharmer::ConnectionFactory.connect_to_db(@conf[:connection_name], @conf) # end end end ================================================ FILE: test-project/spec/unit/connection_proxy_spec.rb ================================================ require 'spec_helper' describe DbCharmer::ConnectionProxy do before(:each) do class ProxyTest; end @conn = mock('connection') @proxy = DbCharmer::ConnectionProxy.new(ProxyTest, :foo) end it "should retrieve connection from an underlying class" do ProxyTest.should_receive(:retrieve_connection).and_return(@conn) @proxy.inspect end it "should be a blankslate for the connection" do ProxyTest.stub!(:retrieve_connection).and_return(@conn) @proxy.should be(@conn) end it "should proxy methods with a block parameter" do module MockConnection def self.foo raise "No block given!" unless block_given? yield end end ProxyTest.stub!(:retrieve_connection).and_return(MockConnection) res = @proxy.foo { :foo } res.should == :foo end it "should proxy all calls to the underlying class connections" do ProxyTest.stub!(:retrieve_connection).and_return(@conn) @conn.should_receive(:foo) @proxy.foo end end ================================================ FILE: test-project/spec/unit/db_charmer_spec.rb ================================================ require 'spec_helper' describe DbCharmer do after do DbCharmer.current_controller = nil DbCharmer.connections_should_exist = false end it "should define version constants" do DbCharmer::Version::STRING.should match(/^\d+\.\d+\.\d+/) end it "should have connections_should_exist accessors" do DbCharmer.connections_should_exist.should_not be_nil DbCharmer.connections_should_exist = :foo DbCharmer.connections_should_exist.should == :foo end it "should have connections_should_exist? method" do DbCharmer.connections_should_exist = true DbCharmer.connections_should_exist?.should be_true DbCharmer.connections_should_exist = false DbCharmer.connections_should_exist?.should be_false DbCharmer.connections_should_exist = "shit" DbCharmer.connections_should_exist?.should be_true DbCharmer.connections_should_exist = nil DbCharmer.connections_should_exist?.should be_false end it "should have current_controller accessors" do DbCharmer.respond_to?(:current_controller).should be_true DbCharmer.current_controller = :foo DbCharmer.current_controller.should == :foo DbCharmer.current_controller = nil end context "in force_slave_reads? method" do it "should return true if force_slave_reads=true" do DbCharmer.force_slave_reads?.should be_false DbCharmer.force_slave_reads do DbCharmer.force_slave_reads?.should be_true end DbCharmer.force_slave_reads?.should be_false end it "should return false if no controller defined and global force_slave_reads=false" do DbCharmer.current_controller = nil DbCharmer.force_slave_reads?.should be_false end it "should consult with the controller about forcing slave reads if possible" do DbCharmer.current_controller = mock("controller") DbCharmer.current_controller.should_receive(:force_slave_reads?).and_return(true) DbCharmer.force_slave_reads?.should be_true DbCharmer.current_controller.should_receive(:force_slave_reads?).and_return(false) DbCharmer.force_slave_reads?.should be_false end end context "in with_controller method" do it "should fail if no block given" do lambda { DbCharmer.with_controller(:foo) }.should raise_error(ArgumentError) end it "should switch controller while running the block" do DbCharmer.current_controller = nil DbCharmer.current_controller.should be_nil DbCharmer.with_controller(:foo) do DbCharmer.current_controller.should == :foo end DbCharmer.current_controller.should be_nil end it "should ensure current controller is reverted to nil in case of errors" do lambda { DbCharmer.with_controller(:foo) { raise "fuck" } }.should raise_error DbCharmer.current_controller.should be_nil end end end ================================================ FILE: test-project/spec/unit/multi_db_proxy_spec.rb ================================================ require 'spec_helper' describe "ActiveRecord model with db_magic" do before do class Blah < ActiveRecord::Base self.table_name = :posts db_magic :connection => nil end end describe "(instance)" do before do @blah = Blah.new end describe "in on_db method" do describe "with a block" do it "should switch connection to specified one and yield the block" do Blah.db_charmer_connection_proxy.should be_nil @blah.on_db(:logs) do Blah.db_charmer_connection_proxy.should_not be_nil end end it "should switch connection back after the block finished its work" do Blah.db_charmer_connection_proxy.should be_nil @blah.on_db(:logs) {} Blah.db_charmer_connection_proxy.should be_nil end it "should manage connection level values" do Blah.db_charmer_connection_level.should == 0 @blah.on_db(:logs) do |m| m.class.db_charmer_connection_level.should == 1 end Blah.db_charmer_connection_level.should == 0 end end describe "as a chain call" do it "should switch connection for all chained calls" do Blah.db_charmer_connection_proxy.should be_nil @blah.on_db(:logs).should_not be_nil end it "should switch connection for non-chained calls" do Blah.db_charmer_connection_proxy.should be_nil @blah.on_db(:logs).to_s Blah.db_charmer_connection_proxy.should be_nil end it "should restore connection" do User.first User.connection.object_id.should == User.on_master.connection.object_id User.on_db(:slave01).first User.connection.object_id.should == User.on_master.connection.object_id end it "should restore connection after error" do pending "Disabled in RSpec prior to version 2 because of lack of .any_instance support" unless Object.respond_to?(:any_instance) User.on_db(:slave01).first User.first ActiveRecord::Base.connection_handler.clear_all_connections! ActiveRecord::ConnectionAdapters::MysqlAdapter.any_instance.stub(:connect) { raise Mysql::Error, 'Connection error' } expect { User.on_db(:slave01).first }.to raise_error(Mysql::Error) ActiveRecord::ConnectionAdapters::MysqlAdapter.any_instance.unstub(:connect) User.connection.connection_name.should == User.on_master.connection.connection_name end end end end describe "(class)" do describe "in on_db method" do describe "with a block" do it "should switch connection to specified one and yield the block" do Blah.db_charmer_connection_proxy.should be_nil Blah.on_db(:logs) do Blah.db_charmer_connection_proxy.should_not be_nil end end it "should switch connection back after the block finished its work" do Blah.db_charmer_connection_proxy.should be_nil Blah.on_db(:logs) {} Blah.db_charmer_connection_proxy.should be_nil end it "should manage connection level values" do Blah.db_charmer_connection_level.should == 0 Blah.on_db(:logs) do |m| m.db_charmer_connection_level.should == 1 end Blah.db_charmer_connection_level.should == 0 end end describe "as a chain call" do it "should switch connection for all chained calls" do Blah.db_charmer_connection_proxy.should be_nil Blah.on_db(:logs).should_not be_nil end it "should switch connection for non-chained calls" do Blah.db_charmer_connection_proxy.should be_nil Blah.on_db(:logs).to_s Blah.db_charmer_connection_proxy.should be_nil end end end describe "in on_slave method" do before do Blah.db_magic :slaves => [ :slave01 ] end it "should use one tof the model's slaves if no slave given" do Blah.on_slave.db_charmer_connection_proxy.object_id.should == Blah.coerce_to_connection_proxy(:slave01).object_id end it "should use given slave" do Blah.on_slave(:logs).db_charmer_connection_proxy.object_id.should == Blah.coerce_to_connection_proxy(:logs).object_id end it 'should support block calls' do Blah.on_slave do |m| m.db_charmer_connection_proxy.object_id.should == Blah.coerce_to_connection_proxy(:slave01).object_id end end end describe "in on_master method" do before do Blah.db_magic :slaves => [ :slave01 ] end it "should run queries on the master" do Blah.on_master.db_charmer_connection_proxy.should be_nil end end end end ================================================ FILE: test-project/spec/unit/with_remapped_databases_spec.rb ================================================ require 'spec_helper' describe "DbCharmer#with_remapped_databases" do before(:all) do DbCharmer.connections_should_exist = false end let(:logs_connection) { DbCharmer::ConnectionFactory.connect(:logs) } let(:slave_connection) { DbCharmer::ConnectionFactory.connect(:slave01) } let(:master_connection) { Avatar.connection } before :each do class User < ActiveRecord::Base db_magic :connection => :slave01 end end def should_have_connection(model_class, connection) model_class.connection.object_id.should == connection.object_id end it "should remap the right connection" do should_have_connection(LogRecord, logs_connection) DbCharmer.with_remapped_databases(:logs => :slave01) do should_have_connection(LogRecord, slave_connection) end should_have_connection(LogRecord, logs_connection) end it "should not remap other connections" do should_have_connection(Avatar, master_connection) should_have_connection(User, slave_connection) DbCharmer.with_remapped_databases(:logs => :slave01) do should_have_connection(Avatar, master_connection) should_have_connection(User, slave_connection) end should_have_connection(Avatar, master_connection) should_have_connection(User, slave_connection) end it "should allow remapping multiple databases" do should_have_connection(Avatar, master_connection) should_have_connection(LogRecord, logs_connection) DbCharmer.with_remapped_databases(:master => :logs, :logs => :slave01) do should_have_connection(Avatar, logs_connection) should_have_connection(LogRecord, slave_connection) end should_have_connection(Avatar, master_connection) should_have_connection(LogRecord, logs_connection) end it "should remap the master connection when asked to, but not other connections" do should_have_connection(Avatar, master_connection) should_have_connection(User, slave_connection) should_have_connection(LogRecord, logs_connection) DbCharmer.with_remapped_databases(:master => :slave01) do should_have_connection(Avatar, slave_connection) should_have_connection(User, slave_connection) should_have_connection(LogRecord, logs_connection) end should_have_connection(Avatar, master_connection) should_have_connection(User, slave_connection) should_have_connection(LogRecord, logs_connection) end it "should not override connections that are explicitly specified" do DbCharmer.with_remapped_databases(:logs => :slave01) do should_have_connection(LogRecord, slave_connection) should_have_connection(LogRecord.on_db(:master), master_connection) LogRecord.on_db(:master) do should_have_connection(LogRecord, master_connection) end should_have_connection(LogRecord.on_db(:logs), logs_connection) LogRecord.on_db(:logs) do should_have_connection(LogRecord, logs_connection) end should_have_connection(LogRecord, slave_connection) end end it "should successfully run selects on the right database" do # We need this call to make sure rails would fetch columns info from the logs server before we mess its connection up LogRecord.all # Remap LogRecord connection to slave01 and make sure selects would go there (even though we do not have the table there) DbCharmer.with_remapped_databases(:logs => :slave01) do logs_connection.should_not_receive(:select_all) slave_connection.should_receive(:select_all).and_return([]) stub_columns_for_rails31 slave_connection LogRecord.all.should be_empty end end def unhijack!(klass) if klass.respond_to?(:connection_with_magic) klass.class_eval <<-END class << self undef_method(:connection_with_magic) alias_method(:connection, :connection_without_magic) undef_method(:connection_without_magic) undef_method(:connection_pool_with_magic) alias_method(:connection_pool, :connection_pool_without_magic) undef_method(:connection_pool_without_magic) end END end raise "Unable to unhijack #{klass.name}" if klass.respond_to?(:connection_with_magic) end it "should hijack connections only when necessary" do unhijack!(Category) Category.respond_to?(:connection_with_magic).should be_false DbCharmer.with_remapped_databases(:logs => :slave01) do Category.respond_to?(:connection_with_magic).should be_false end Category.respond_to?(:connection_with_magic).should be_false DbCharmer.with_remapped_databases(:master => :slave01) do Category.respond_to?(:connection_with_magic).should be_true should_have_connection(Category, slave_connection) end end end ================================================ FILE: test-project-2.x/Gemfile ================================================ source 'http://rubygems.org' gem 'rails', '2.3.18' gem 'rake', '0.9.2.2' gem 'mysql' gem 'rspec', '1.3.2' gem 'rspec-rails', '1.3.4' # Load DbCharmer as a gem gem 'db-charmer', :path => '..', :require => 'db_charmer' ================================================ FILE: test-project-2.x/Rakefile ================================================ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require(File.join(File.dirname(__FILE__), 'config', 'boot')) require 'rake' require 'rake/testtask' require 'rake/rdoctask' require 'tasks/rails' ================================================ FILE: test-project-2.x/config/boot.rb ================================================ # We only have test environment here ENV['RAILS_ENV'] = 'test' # Don't change this file! # Configure your app in config/environment.rb and config/environments/*.rb RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) module Rails class << self def boot! unless booted? preinitialize pick_boot.run end end def booted? defined? Rails::Initializer end def pick_boot (vendor_rails? ? VendorBoot : GemBoot).new end def vendor_rails? File.exist?("#{RAILS_ROOT}/vendor/rails") end def preinitialize load(preinitializer_path) if File.exist?(preinitializer_path) end def preinitializer_path "#{RAILS_ROOT}/config/preinitializer.rb" end end class Boot def run load_initializer Rails::Initializer.class_eval do def load_gems @bundler_loaded ||= Bundler.require :default, Rails.env end end Rails::Initializer.run(:set_load_path) end end class VendorBoot < Boot def load_initializer require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" Rails::Initializer.run(:install_gem_spec_stubs) Rails::GemDependency.add_frozen_gem_path end end class GemBoot < Boot def load_initializer self.class.load_rubygems load_rails_gem require 'initializer' end def load_rails_gem if version = self.class.gem_version gem 'rails', version else gem 'rails' end rescue Gem::LoadError => load_error $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) exit 1 end class << self def rubygems_version Gem::RubyGemsVersion rescue nil end def gem_version if defined? RAILS_GEM_VERSION RAILS_GEM_VERSION elsif ENV.include?('RAILS_GEM_VERSION') ENV['RAILS_GEM_VERSION'] else parse_gem_version(read_environment_rb) end end def load_rubygems require 'rubygems' min_version = '1.3.1' unless rubygems_version >= min_version $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) exit 1 end rescue LoadError $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) exit 1 end def parse_gem_version(text) $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ end private def read_environment_rb File.read("#{RAILS_ROOT}/config/environment.rb") end end end end # All that for this: Rails.boot! ================================================ FILE: test-project-2.x/config/database.yml.example ================================================ common: &common adapter: mysql encoding: utf8 reconnect: false pool: 1 username: root password: #---------------------------------------------------------------- test: <<: *common database: db_charmer_sandbox_test # logs database logs: <<: *common database: db_charmer_logs_test # slave database slave01: <<: *common username: db_charmer_ro database: db_charmer_sandbox_test user_master: <<: *common database: db_charmer_sandbox_test # shard mapping db social_shard_info: <<: *common database: db_charmer_sandbox_test # for migrations only social_shard01: <<: *common database: db_charmer_events_test_shard01 # for migrations only social_shard02: <<: *common database: db_charmer_events_test_shard02 #---------------------------------------------------------------- test22: <<: *common database: db_charmer_sandbox22_test # logs database logs: <<: *common database: db_charmer_logs22_test # slave database slave01: <<: *common username: db_charmer_ro database: db_charmer_sandbox22_test user_master: <<: *common database: db_charmer_sandbox22_test # shard mapping db social_shard_info: <<: *common database: db_charmer_sandbox22_test # for migrations only social_shard01: <<: *common database: db_charmer_events22_test_shard01 # for migrations only social_shard02: <<: *common database: db_charmer_events22_test_shard02 ================================================ FILE: test-project-2.x/config/environment.rb ================================================ # Specifies gem version of Rails to use when vendor/rails is not present RAILS_GEM_VERSION = '2.3.18' unless defined? RAILS_GEM_VERSION # Bootstrap the Rails environment, frameworks, and default configuration require File.join(File.dirname(__FILE__), 'boot') Rails::Initializer.run do |config| config.time_zone = 'UTC' end ================================================ FILE: test-project-2.x/config/environments/test.rb ================================================ # Settings specified here will take precedence over those in config/environment.rb # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.action_controller.consider_all_requests_local = true config.action_controller.perform_caching = false config.action_view.cache_template_loading = true # Disable request forgery protection in test environment config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test # Use SQL instead of Active Record's schema dumper when creating the test database. # This is necessary if your schema can't be completely dumped by the schema dumper, # like if you have constraints or database-specific column types # config.active_record.schema_format = :sql ================================================ FILE: test-project-2.x/config/initializers/backtrace_silencers.rb ================================================ # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying do debug a problem that might steem from framework code. # Rails.backtrace_cleaner.remove_silencers! ================================================ FILE: test-project-2.x/config/initializers/db_charmer.rb ================================================ DbCharmer.connections_should_exist = false # Since we are not in production DbCharmer.enable_controller_magic! ================================================ FILE: test-project-2.x/config/initializers/inflections.rb ================================================ # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format # (all these examples are active by default): # ActiveSupport::Inflector.inflections do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end ================================================ FILE: test-project-2.x/config/initializers/mime_types.rb ================================================ # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf # Mime::Type.register_alias "text/html", :iphone ================================================ FILE: test-project-2.x/config/initializers/new_rails_defaults.rb ================================================ # Be sure to restart your server when you modify this file. # These settings change the behavior of Rails 2 apps and will be defaults # for Rails 3. You can remove this initializer when Rails 3 is released. if defined?(ActiveRecord) # Include Active Record class name as root for JSON serialized output. ActiveRecord::Base.include_root_in_json = true # Store the full class name (including module namespace) in STI type column. ActiveRecord::Base.store_full_sti_class = true end # Use ISO 8601 format for JSON serialized times and dates. ActiveSupport.use_standard_json_time_format = true # Don't escape HTML entities in JSON, leave that for the #json_escape helper. # if you're including raw json in an HTML page. ActiveSupport.escape_html_entities_in_json = false ================================================ FILE: test-project-2.x/config/initializers/session_store.rb ================================================ # Be sure to restart your server when you modify this file. # Your secret key for verifying cookie session data integrity. # If you change this key, all old sessions will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. ActionController::Base.session = { :key => '_db_charmer_sandbox_session', :secret => '9b67feed7aa8a2741d9f0ac6efde543d726f7a017c8a635346be733f287fd479fbd8521c1e8a06e91af7920de1fb50b942bdf24b6ecee1569ed947c13f6697af' } # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information # (create the session table with "rake db:sessions:create") # ActionController::Base.session_store = :active_record_store ================================================ FILE: test-project-2.x/config/initializers/sharding.rb ================================================ # Range-based shards for testing TEXTS_SHARDING_RANGES = { 0...100 => :shard1, 100..200 => :shard2, :default => :shard3 } DbCharmer::Sharding.register_connection( :name => :texts, :method => :range, :ranges => TEXTS_SHARDING_RANGES ) #------------------------------------------------ # Db blocks map sharding for testing SOCIAL_SHARDING = DbCharmer::Sharding.register_connection( :name => :social, :method => :db_block_map, :block_size => 10, :map_table => :event_shards_map, :shards_table => :event_shards_info, :connection => :social_shard_info ) ================================================ FILE: test-project-2.x/config/locales/en.yml ================================================ # Sample localization file for English. Add more files in this directory for other locales. # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. en: hello: "Hello world" ================================================ FILE: test-project-2.x/config/preinitializer.rb ================================================ begin require "rubygems" require "bundler" rescue LoadError raise "Could not load the bundler gem. Install it with `gem install bundler`." end if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24") raise RuntimeError, "Your bundler version is too old for Rails 2.3." + "Run `gem install bundler` to upgrade." end begin # Set up load paths for all bundled gems ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__) Bundler.setup rescue Bundler::GemNotFound raise RuntimeError, "Bundler couldn't find some gems." + "Did you run `bundle install`?" end ================================================ FILE: test-project-2.x/config/routes.rb ================================================ ActionController::Routing::Routes.draw do |map| # Resource routes map.resources :posts map.resources :cars # Install the default routes as the lowest priority. # Note: These default routes make all actions in every controller accessible via GET requests. You should # consider removing or commenting them out if you're using named routes and resources. map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end ================================================ FILE: test-project-2.x/script/console ================================================ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../config/boot' require 'commands/console' ================================================ FILE: test-project-2.x/spec/spec.opts ================================================ --colour --format specdoc ================================================ FILE: test-project-2.x/spec/spec_helper.rb ================================================ # This file is copied to ~/spec when you run 'ruby script/generate rspec' # from the project root directory. ENV["RAILS_ENV"] = 'test' require File.expand_path(File.join(File.dirname(__FILE__),'..','config','environment')) require 'spec/autorun' require 'spec/rails' # Requires supporting files with custom matchers and macros, etc, # in ./support/ and its subdirectories. Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f} Spec::Runner.configure do |config| # If you're not using ActiveRecord you should remove these # lines, delete config/database.yml and disable :active_record # in your config/boot.rb config.use_transactional_fixtures = false config.use_instantiated_fixtures = false config.fixture_path = RAILS_ROOT + '/spec/fixtures/' # == Fixtures # # You can declare fixtures for each example_group like this: # describe "...." do # fixtures :table_a, :table_b # # Alternatively, if you prefer to declare them only once, you can # do so right here. Just uncomment the next line and replace the fixture # names with your fixtures. # # config.global_fixtures = :table_a, :table_b # # If you declare global fixtures, be aware that they will be declared # for all of your examples, even those that don't use them. # # You can also declare which fixtures to use (for example fixtures for test/fixtures): # # config.fixture_path = RAILS_ROOT + '/spec/fixtures/' # # == Mock Framework # # RSpec uses its own mocking framework by default. If you prefer to # use mocha, flexmock or RR, uncomment the appropriate line: # # config.mock_with :mocha # config.mock_with :flexmock # config.mock_with :rr # # == Notes # # For more information take a look at Spec::Runner::Configuration and Spec::Runner end