diff --git a/.github/gemfiles/rails_7.1.gemfile b/.github/gemfiles/rails_7.1.gemfile new file mode 100644 index 0000000..a17a9e9 --- /dev/null +++ b/.github/gemfiles/rails_7.1.gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +gemspec :path => '../../' + +gem 'activerecord', '~> 7.1.0' +gem 'activesupport', '~> 7.1.0' diff --git a/.github/gemfiles/rails_7.2.gemfile b/.github/gemfiles/rails_7.2.gemfile new file mode 100644 index 0000000..792a46e --- /dev/null +++ b/.github/gemfiles/rails_7.2.gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +gemspec :path => '../../' + +gem 'activerecord', '~> 7.2.0' +gem 'activesupport', '~> 7.2.0' diff --git a/.github/gemfiles/rails_8.0.gemfile b/.github/gemfiles/rails_8.0.gemfile new file mode 100644 index 0000000..f57bc80 --- /dev/null +++ b/.github/gemfiles/rails_8.0.gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +gemspec :path => '../../' + +gem 'activerecord', '~> 8.0.0' +gem 'activesupport', '~> 8.0.0' diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 6cfcbc8..9a4a60e 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -17,9 +17,10 @@ jobs: strategy: fail-fast: true matrix: - ruby: ["3.1","3.2","3.3"] + ruby: ["3.2","3.3", "3.4"] + rails: ["7.1", "7.2", "8.0"] env: - BUNDLE_GEMFILE: .github/Gemfile + BUNDLE_GEMFILE: .github/gemfiles/rails_${{ matrix.rails }}.gemfile MYSQL_HOST: 127.0.0.1 RAILS_ENV: test steps: diff --git a/README.md b/README.md index db760a0..c1d8d3f 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,16 @@ Currently, its implementation is focusing on horizontal database sharding. Howev ## Scope of this gem ### What is included in Octoball + - Octopus-like shard swithcing by `using` class method, e.g.: + ```ruby Octoball.using(:shard1) { User.find_by_name("Alice") } User.using(:shard1).first ``` + - Each model instance knows which shard it came from so shard will be switched automatically: + ```ruby user1 = User.using(:shard1).find_by_name("Bob") user2 = User.using(:shard2).find_by_name("Charlie") @@ -25,15 +29,18 @@ Currently, its implementation is focusing on horizontal database sharding. Howev user1.save! # Save the user1 in the correct shard `:shard1` user2.save! # Save the user2 in the correct shard `:shard2` ``` + - Relations such as `has_many` are also resolved from the model instance's shard: + ```ruby user = User.using(:shard1).find_by_name("Alice") user.blogs.where(title: "blog") # user's blogs are fetched from `:shard1` ``` ### What is NOT included in Octoball + - Connection handling and configuration -- managed by the native `ActiveRecord::Base.connects_to` methods introduced in ActiveRecord 6.1. - - You need to migrate from Octopus' `config/shards.yml` to [Rails native multiple DB configuration using `config/database.yml`](https://edgeguides.rubyonrails.org/active_record_multiple_databases.html). Please refer the [Setup](#Setup) section for more details. + - You need to migrate from Octopus' `config/shards.yml` to [Rails native multiple DB configuration using `config/database.yml`](https://edgeguides.rubyonrails.org/active_record_multiple_databases.html). Please refer the [Setup](#setup) section for more details. - Migration -- done by ActiveRecord 6.1+ natively. - Instead of `using` method in Octopus, you can specify the `migrations_paths` parameter in the `config/database.yml` file. - Replication handling -- done by ActiveRecord's `role` @@ -46,12 +53,13 @@ gem "octoball" ``` Define the database connections in `config/database.yml`, e.g.: + ``` default: &default - adapter: mysql2 + adapter: trilogy pool: 5 username: root - host: localhost + host: 127.0.0.1 timeout: 5000 connnect_timeout: 5000 @@ -63,7 +71,9 @@ development: <<: *default database: db_shard1 ``` + And define shards and corresponding connections in abstract ActiveRecord model class, e.g.: + ```ruby class ApplicationRecord < ActiveRecord::Base self.abstract_class = true @@ -80,21 +90,24 @@ end ``` Optionally, to use the `:master` shard as a default connection like Octopus, add the following script to `config/initializers/default_shard.rb`: + ``` ActiveRecord::Base.default_shard = :master ``` - ## Development of Octoball + Octoball has rspec tests delived from subsets of Octopus' rspec. To run the rspec tests, follow these steps: + ``` RAILS_ENV=test bundle exec rake db:prepare RAILS_ENV=test bundle exec rake spec ``` ## License + Octoball is released under the MIT license. Original Octopus' copyright: Copyright (c) Thiago Pradi diff --git a/Rakefile b/Rakefile index 4778582..6bfa273 100644 --- a/Rakefile +++ b/Rakefile @@ -9,9 +9,10 @@ RuboCop::RakeTask.new namespace :db do mysql_spec = { - adapter: 'mysql2', - host: (ENV['MYSQL_HOST'] || 'localhost'), + adapter: 'trilogy', + host: (ENV['MYSQL_HOST'] || '127.0.0.1'), username: (ENV['MYSQL_USER'] || 'root'), + port: (ENV['MYSQL_PORT'] || 3306), encoding: 'utf8mb4', } @@ -31,6 +32,8 @@ namespace :db do desc 'Create tables on tests databases' task :create_tables do + require 'active_record' + ActiveRecord::Base.configurations = { "test" => { shard1: mysql_spec.merge(database: 'octoball_shard_1'), diff --git a/octoball.gemspec b/octoball.gemspec index 6f17172..422dce7 100644 --- a/octoball.gemspec +++ b/octoball.gemspec @@ -15,12 +15,12 @@ Gem::Specification.new do |s| s.homepage = 'https://github.com/aktsk/octoball' s.require_paths = ['lib'] - s.required_ruby_version = '>= 3.1.0' + s.required_ruby_version = '>= 3.2.0' s.add_dependency 'activerecord', '>= 7.0' s.add_dependency 'activesupport', '>= 7.0' - s.add_development_dependency 'mysql2' + s.add_development_dependency 'trilogy' s.add_development_dependency 'rake' s.add_development_dependency 'rspec', '>= 3' s.add_development_dependency 'rubocop' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d51f786..e1077f8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,9 +10,10 @@ RSpec.configure do |config| mysql_spec = { - adapter: 'mysql2', - host: (ENV['MYSQL_HOST'] || 'localhost'), + adapter: 'trilogy', + host: (ENV['MYSQL_HOST'] || '127.0.0.1'), username: (ENV['MYSQL_USER'] || 'root'), + port: (ENV['MYSQL_PORT'] || 3306), encoding: 'utf8mb4', } ActiveRecord::Base.configurations = { diff --git a/spec/support/database_connection.rb b/spec/support/database_connection.rb index 6f48102..a6c0292 100644 --- a/spec/support/database_connection.rb +++ b/spec/support/database_connection.rb @@ -3,9 +3,10 @@ ActiveRecord::Base.logger = Logger.new(File.open('database.log', 'a')) mysql_spec = { - adapter: 'mysql2', - host: (ENV['MYSQL_HOST'] || 'localhost'), + adapter: 'trilogy', + host: (ENV['MYSQL_HOST'] || '127.0.0.1'), username: (ENV['MYSQL_USER'] || 'root'), + port: (ENV['MYSQL_PORT'] || 3306), encoding: 'utf8mb4', } diff --git a/spec/support/database_models.rb b/spec/support/database_models.rb index 82da656..0632a63 100644 --- a/spec/support/database_models.rb +++ b/spec/support/database_models.rb @@ -20,7 +20,7 @@ class Client < ApplicationRecord # This class sets its own connection class CustomConnectionBase < ActiveRecord::Base self.abstract_class = true - establish_connection(:adapter => 'mysql2', :host => (ENV['MYSQL_HOST'] || 'localhost'), :database => 'octoball_shard_2', :username => "#{ENV['MYSQL_USER'] || 'root'}", :password => '') + establish_connection(:adapter => 'trilogy', :host => (ENV['MYSQL_HOST'] || '127.0.0.1'), :database => 'octoball_shard_2', :username => "#{ENV['MYSQL_USER'] || 'root'}", :password => '', :port => (ENV['MYSQL_PORT'] || 3306)) connects_to shards: { custom_shard: { writing: :shard3 } }