{"id":13492157,"url":"https://github.com/eagletmt/switch_point","last_synced_at":"2025-05-15T14:08:40.474Z","repository":{"id":17442684,"uuid":"20216270","full_name":"eagletmt/switch_point","owner":"eagletmt","description":"  Switching database connection between readonly one and writable one","archived":false,"fork":false,"pushed_at":"2020-11-22T11:51:06.000Z","size":159,"stargazers_count":591,"open_issues_count":2,"forks_count":39,"subscribers_count":29,"default_branch":"master","last_synced_at":"2025-05-14T08:13:11.462Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://github.com/eagletmt/switch_point","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eagletmt.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-05-27T10:34:59.000Z","updated_at":"2025-02-14T15:51:56.000Z","dependencies_parsed_at":"2022-09-15T11:13:44.359Z","dependency_job_id":null,"html_url":"https://github.com/eagletmt/switch_point","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eagletmt%2Fswitch_point","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eagletmt%2Fswitch_point/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eagletmt%2Fswitch_point/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eagletmt%2Fswitch_point/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eagletmt","download_url":"https://codeload.github.com/eagletmt/switch_point/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254355335,"owners_count":22057354,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T19:01:03.581Z","updated_at":"2025-05-15T14:08:35.464Z","avatar_url":"https://github.com/eagletmt.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# SwitchPoint\n[![Gem Version](https://badge.fury.io/rb/switch_point.svg)](http://badge.fury.io/rb/switch_point)\n[![Build Status](https://travis-ci.org/eagletmt/switch_point.svg?branch=master)](https://travis-ci.org/eagletmt/switch_point)\n[![Coverage Status](https://img.shields.io/coveralls/eagletmt/switch_point.svg?branch=master)](https://coveralls.io/r/eagletmt/switch_point?branch=master)\n[![Code Climate](https://codeclimate.com/github/eagletmt/switch_point/badges/gpa.svg)](https://codeclimate.com/github/eagletmt/switch_point)\n\nSwitching database connection between readonly one and writable one.\n\n## Maintenance notice\nswitch_point won't support upcoming ActiveRecord v6.1 or later.\nDevelopers should use the builtin multiple database feature introduced in ActiveRecord v6.0.\nhttps://guides.rubyonrails.org/active_record_multiple_databases.html\nThus the supported ActiveRecord version is v3.2, v4.0, v4.1, v4.2, v5.0, v5.1, and v5.2.\n\nswitch_point won't accept any new features. Bug fixes might be accepted.\nIf you'd like to add a new feature (and/or support ActiveRecord \u003e= v6.1), feel free to fork switch_point gem.\n\n### Migration from switch_point to ActiveRecord multiple database feature\n1. Upgrade your activerecord gem to v6.0\n    - ActiveRecord v6.0 is the only series which supports both builtin multiple database feature and switch_point.\n2. Change your application to use ActiveRecord multiple database feature\n    - If you'd like to keep the number of connections during this step, it would require some tricks.\n3. Remove switch_point gem from your Gemfile\n4. Upgrade your activerecord gem to v6.1 or later\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'switch_point'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install switch_point\n\n## Usage\nSuppose you have 4 databases: db-blog-master, db-blog-slave, db-comment-master and db-comment-slave.\nArticle model and Category model are stored in db-blog-{master,slave} and Comment model is stored in db-comment-{master,slave}.\n\n### Configuration\nIn database.yml:\n\n```yaml\nproduction_blog_master:\n  adapter: mysql2\n  username: blog_writable\n  host: db-blog-master\nproduction_blog_slave:\n  adapter: mysql2\n  username: blog_readonly\n  host: db-blog-slave\nproduction_comment_master:\n    ...\n```\n\nIn initializer:\n\n```ruby\nSwitchPoint.configure do |config|\n  config.define_switch_point :blog,\n    readonly: :\"#{Rails.env}_blog_slave\",\n    writable: :\"#{Rails.env}_blog_master\"\n  config.define_switch_point :comment,\n    readonly: :\"#{Rails.env}_comment_slave\",\n    writable: :\"#{Rails.env}_comment_master\"\nend\n```\n\nIn models:\n\n```ruby\nclass Article \u003c ActiveRecord::Base\n  use_switch_point :blog\nend\n\nclass Category \u003c ActiveRecord::Base\n  use_switch_point :blog\nend\n\nclass Comment \u003c ActiveRecord::Base\n  use_switch_point :comment\nend\n```\n\n### Switching connections\n\n```ruby\nArticle.with_readonly { Article.first } # Read from db-blog-slave\nCategory.with_readonly { Category.first } # Also read from db-blog-slave\nComment.with_readonly { Comment.first } # Read from db-comment-slave\n\nArticle.with_readonly do\n  article = Article.first  # Read from db-blog-slave\n  article.title = 'new title'\n  Article.with_writable do\n    article.save!  # Write to db-blog-master\n    article.reload  # Read from db-blog-master\n    Category.first  # Read from db-blog-master\n  end\nend\n```\n\nNote that Article and Category shares their connections.\n\n### Query cache\n`Model.cache` and `Model.uncached` enables/disables query cache for both\nreadonly connection and writable connection.\n\nswitch_point also provide a rack middleware `SwitchPoint::QueryCache` similar\nto `ActiveRecord::QueryCache`. It enables query cache for all models using\nswitch_point.\n\n```ruby\n# Replace ActiveRecord::QueryCache with SwitchPoint::QueryCache\nconfig.middleware.swap ActiveRecord::QueryCache, SwitchPoint::QueryCache\n\n# Enable query cache for :nanika1 only.\nconfig.middleware.swap ActiveRecord::QueryCache, SwitchPoint::QueryCache, [:nanika1]\n```\n\n## Notes\n\n### auto_writable\n`auto_writable` is disabled by default.\n\nWhen `auto_writable` is enabled, destructive queries is sent to writable connection even in readonly mode.\nBut it does NOT work well on transactions.\n\nSuppose `after_save` callback is set to User model. When `User.create` is called, it proceeds as follows.\n\n1. BEGIN TRANSACTION is sent to READONLY connection.\n2. switch_point switches the connection to WRITABLE.\n3. INSERT statement is sent to WRITABLE connection.\n4. switch_point reset the connection to READONLY.\n5. after_save callback is called.\n    - At this point, the connection is READONLY and in a transaction.\n6. COMMIT TRANSACTION is sent to READONLY connection.\n\n### connection-related methods of model\nModel has several connection-related methods: `connection_handler`, `connection_pool`, `connected?` and so on.\nSince only `connection` method is monkey-patched, other connection-related methods doesn't work properly.\nIf you'd like to use those methods, send it to `Model.switch_point_proxy.model_for_connection`.\n\n## Internals\nThere's a proxy which holds two connections: readonly one and writable one.\nA proxy has a thread-local state indicating the current mode: readonly or writable.\n\nEach ActiveRecord model refers to a proxy.\n`ActiveRecord::Base.connection` is hooked and delegated to the referred proxy.\n\nWhen the writable connection is requested to execute destructive query, the readonly connection clears its query cache.\n\n![switch_point](https://gyazo.wanko.cc/switch_point.svg)\n\n### Special case: ActiveRecord::Base.connection\nBasically, each connection managed by a proxy isn't shared between proxies.\nBut there's one exception: ActiveRecord::Base.\n\nIf `:writable` key is omitted (e.g., Nanika1 model in spec/models), it uses `ActiveRecord::Base.connection` as writable one.\nWhen `ActiveRecord::Base.connection` is requested to execute destructive query, all readonly connections managed by a proxy which uses `ActiveRecord::Base.connection` as a writable connection clear query cache.\n\n## Contributing\n\n1. Fork it ( https://github.com/eagletmt/switch_point/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feagletmt%2Fswitch_point","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feagletmt%2Fswitch_point","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feagletmt%2Fswitch_point/lists"}