Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ gemfiles/*.lock
log/*
.*.sw[a-z]
/bin/test_local
/test/config/database.local.yml
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ rvm:
- 2.6.6
- 2.7.1
gemfile:
- gemfiles/rails52.gemfile
- gemfiles/rails60.gemfile
script:
- "bin/test"
Expand Down
5 changes: 0 additions & 5 deletions Appraisals
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
appraise "rails52" do
gem 'activerecord', '~> 5.2.0'
gem 'mysql2', '>= 0.4.4', "< 0.6.0"
end

appraise "rails60" do
gem 'activerecord', '~> 6.0.0'
end
85 changes: 55 additions & 30 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,74 @@
# Contributor Code of Conduct
# Contributor Covenant Code of Conduct

As contributors and maintainers of this project, and in the interest of
fostering an open and welcoming community, we pledge to respect all people who
contribute through reporting issues, posting feature requests, updating
documentation, submitting pull requests or patches, and other activities.
## Our Pledge

We are committed to making participation in this project a harassment-free
experience for everyone, regardless of level of experience, gender, gender
identity and expression, sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing other's private information, such as physical or electronic
addresses, without explicit permission
* Other unethical or unprofessional conduct
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

By adopting this Code of Conduct, project maintainers commit themselves to
fairly and consistently applying these principles to every aspect of managing
this project. Project maintainers who do not follow or enforce the Code of
Conduct may be permanently removed from the project team.
## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting a project maintainer at tsukasa.oishi@gmail.com. All
reported by contacting the project team at tsukasa.oishi@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. Maintainers are
obligated to maintain confidentiality with regard to the reporter of an
incident.
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 1.3.0, available at
[http://contributor-covenant.org/version/1/3/0/][version]
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [https://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/3/0/
[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/4/
105 changes: 5 additions & 100 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ When what happens one of the replicas is unreachable for any reason, FreshConnec
Removing a trouble replica from a cluster is a work of the load balancer.
FreshConnection expects the load balancer to work during three retries.

If you would like access to multi replica servers without a load balancer, you should use [EbisuConnection](https://github.com/tsukasaoishi/ebisu_connection).
EbisuConnection has functions of load balancer.

## Usage
### Access to the DB Replica
Read queries are automatically connected to the DB replica.
Expand Down Expand Up @@ -78,12 +75,8 @@ old_article.destroy

## ActiveRecord Versions Supported

- FreshConnection supports ActiveRecord version 5.2 or later.
- If you are using Rails 5.1, you can use FreshConnection version 3.0.3 or before.

### Not Support Multiple Database
I haven't tested it in an environment using MultipleDB in Rails 6.
I plan to enable use with MultipleDB in FreshConnection version 4.0 or later.
- FreshConnection supports ActiveRecord version 6.0 or later.
- If you are using Rails 5.2, you can use FreshConnection version 3.1.0 or before.

## Databases Supported
FreshConnection currently supports MySQL and PostgreSQL.
Expand All @@ -108,98 +101,10 @@ $ gem install fresh_connection
```

## Configuration
FreshConnection uses [ActiveRecord Multiple Databases](https://guides.rubyonrails.org/active_record_multiple_databases.html) for connection management.
See the Rails documentation for how to set up a read replica.

The FreshConnection database replica is configured within the standard Rails
database configuration file, `config/database.yml`, using a `replica:` stanza.

Below is a sample such configuration file.

### `config/database.yml`

```yaml
default: &default
adapter: mysql2
encoding: utf8
pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password:

production:
<<: *default
database: blog_production
username: master_db_user
password: <%= ENV['MASTER_DATABASE_PASSWORD'] %>
host: master_db

replica:
username: replica_db_user
password: <%= ENV['REPLICA_DATABASE_PASSWORD'] %>
host: replica_db
```

`replica` is the configuration used for connecting read-only queries to the database replica. All other connections will use the database master settings.


### Multiple DB Replicas
If you want to use multiple configured DB replicas, the configuration can contain multiple `replica` stanzas in the configuration file `config/database.yml`.

For example:

```yaml
default: &default
adapter: mysql2
encoding: utf8
pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password:

production:
<<: *default
database: blog_production
username: master_db_user
password: <%= ENV['MASTER_DATABASE_PASSWORD'] %>
host: master_db

replica:
username: replica_db_user
password: <%= ENV['REPLICA_DATABASE_PASSWORD'] %>
host: replica_db

admin_replica:
username: admin_replica_db_user
password: <%= ENV['ADMIN_REPLICA_DATABASE_PASSWORD'] %>
host: admin_replica_db
```

The custom replica stanza can then be applied as an argument to the `establish_fresh_connection` method in the models that should use it. For example:

```ruby
class AdminUser < ActiveRecord::Base
establish_fresh_connection :admin_replica
end
```

The child (sub) classes of the configured model will inherit the same access as the parent class. Example:

```ruby
class AdminBase < ActiveRecord::Base
establish_fresh_connection :admin_replica
end

class AdminUser < AdminBase
end

class Benefit < AdminBase
end

class Customer < ActiveRecord::Base
end
```

The `AdminUser` and `Benefit` models will access the database configured for the `admin_replica` group.

The `Customer` model will use the default connections: read-only queries will connect to the standard DB replica, and state-changing queries will connect to the DB master.

`establish_fresh_connection` used by FreshConnection has been discontinued.

### Replica Configuration With Environment Variables

Expand Down
2 changes: 1 addition & 1 deletion bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ require "fresh_connection"
# Pry.start

require "irb"
IRB.start
IRB.start(__FILE__)
2 changes: 0 additions & 2 deletions bin/setup
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ set -euo pipefail
IFS=$'\n\t'
set -vx

bundle config set path '.bundle'
bundle install
bundle exec appraisal install

# Do any other automated setup that you need to do here
21 changes: 5 additions & 16 deletions bin/test
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,10 @@ IFS=$'\n\t'

set -vx

if [ -v DATABASE_URL ]; then
echo "[specified config]"
echo "[mysql]"
FC_TEST_ADAPTER=mysql \
bundle exec rake test
else
echo "[mysql]"
DATABASE_URL="mysql2://root@localhost/fresh_connection_test_master" \
DATABASE_REPLICA1_URL="mysql2://root@localhost/fresh_connection_test_replica1" \
DATABASE_REPLICA2_URL="mysql2://root@localhost/fresh_connection_test_replica2" \
DATABASE_FAKE_REPLICA_URL="mysql2://root@localhost/fresh_connection_test_master" \
bundle exec rake test

echo "[postgresql]"
DATABASE_URL="postgresql://localhost/fresh_connection_test_master" \
DATABASE_REPLICA1_URL="postgresql://localhost/fresh_connection_test_replica1" \
DATABASE_REPLICA2_URL="postgresql://localhost/fresh_connection_test_replica2" \
DATABASE_FAKE_REPLICA_URL="postgresql://localhost/fresh_connection_test_master" \
bundle exec rake test
fi
echo "[postgresql]"
FC_TEST_ADAPTER=psgr \
bundle exec rake test
17 changes: 10 additions & 7 deletions fresh_connection.gemspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'fresh_connection/version'
require_relative 'lib/fresh_connection/version'

Gem::Specification.new do |spec|
spec.name = "fresh_connection"
Expand All @@ -12,14 +9,20 @@ Gem::Specification.new do |spec|
spec.summary = %q{FreshConnection supports connections with configured replica servers.}
spec.description = %q{https://github.com/tsukasaoishi/fresh_connection}
spec.homepage = "https://github.com/tsukasaoishi/fresh_connection"
spec.license = "MIT"
spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")

spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.metadata["homepage_uri"] = spec.homepage

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_dependency 'activerecord', '>= 5.2.0', '< 6.1'
spec.add_dependency 'activerecord', '>= 6.0.0', '< 6.1'

spec.add_development_dependency 'mysql2', '>= 0.4.4'
spec.add_development_dependency 'pg', '>= 0.18', '< 2.0'
Expand Down
8 changes: 0 additions & 8 deletions gemfiles/rails52.gemfile

This file was deleted.

21 changes: 13 additions & 8 deletions lib/fresh_connection/access_control.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ class << self
private_constant :RETRY_LIMIT

def manage_access(model:, replica_access:, &block)
return force_master_access(&block) if model.master_db_only?
return force_master_access(model: model, &block) if model.master_db_only?

retry_count = 0
begin
access(replica_access, &block)
access(replica_access, model: model, &block)
rescue *catch_exceptions
if recovery?(model.replica_spec_name)
retry_count += 1
Expand All @@ -28,21 +28,26 @@ def replica_access?

private

def force_master_access(&block)
switch_to(:master, &block)
def force_master_access(model: , &block)
switch_to(:master, model: model, &block)
end

def access(replica_access, &block)
def access(replica_access, model: , &block)
return yield if access_db

db = replica_access ? :replica : :master
switch_to(db, &block)
switch_to(db, model: model, &block)
end

def switch_to(new_db)
def switch_to(new_db, model:)
old_db = access_db
access_to(new_db)
yield

if replica_access?
model.connected_to(role: model.reading_role) { yield }
else
yield
end
ensure
access_to(old_db)
end
Expand Down
Loading