├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib ├── generators │ └── multiverse │ │ ├── db_generator.rb │ │ └── templates │ │ └── record.rb.tt ├── multiverse.rb └── multiverse │ ├── generators.rb │ ├── railtie.rb │ └── version.rb ├── multiverse.gemspec └── test ├── multiverse_test.rb └── test_helper.rb /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | include: 10 | - ruby: 2.6 11 | rails: 5.2.7 12 | - ruby: 2.6 13 | rails: 5.2.7 14 | flags: --api 15 | - ruby: 2.5 16 | rails: 5.1.7 17 | - ruby: 2.4 18 | rails: 5.0.7.2 19 | - ruby: 2.4 20 | rails: 4.2.11.3 21 | bundler: 1 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: ruby/setup-ruby@v1 25 | with: 26 | ruby-version: ${{ matrix.ruby }} 27 | bundler: ${{ matrix.bundler }} 28 | bundler-cache: true 29 | - run: bundle exec rake test 30 | env: 31 | RAILS_VERSION: ${{ matrix.rails }} 32 | FLAGS: ${{ matrix.flags }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | *.lock 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.2 (2019-10-27) 2 | 3 | - Raise error when DB specified not in `database.yml` 4 | 5 | ## 0.2.1 (2018-08-23) 6 | 7 | - Added support for `config.paths["db"]` 8 | - Fixed migration generation when DB specified for Rails < 5.0.2 9 | - Fixed Rails API 10 | 11 | ## 0.2.0 (2018-02-19) 12 | 13 | - Added support for Rails 4.2 14 | - Fixed migration generation when DB specified for Rails < 5.0.3 15 | - Less dependencies 16 | - Less patching 17 | 18 | ## 0.1.2 (2018-01-19) 19 | 20 | - Fixed `db:structure:dump` when DB specified 21 | - Fixed `db:schema:cache:dump` when DB specified 22 | - Fixed `db:version` when DB specified 23 | - Fixed `db:seed` when DB specified 24 | 25 | ## 0.1.1 (2018-01-12) 26 | 27 | - Fixed `db:migrate:status` when DB specified 28 | - Better consistency with default behavior when no DB specified 29 | 30 | ## 0.1.0 (2017-11-12) 31 | 32 | - Fixed test conf 33 | 34 | ## 0.0.3 (2017-11-03) 35 | 36 | - Removed debug statement 37 | - Fixed issue with `db:test:prepare` 38 | 39 | ## 0.0.2 (2017-11-02) 40 | 41 | - Better configuration for SQLite 42 | - Fixed issue with `db:schema:load` 43 | 44 | ## 0.0.1 (2017-11-01) 45 | 46 | - First release 47 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Specify your gem's dependencies in multiverse.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2020 Andrew Kane 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multiverse 2 | 3 | :fire: Multiple databases for Rails 4 | 5 | **ActiveRecord supports multiple databases, but Rails < 6 doesn’t provide a way to manage them.** Multiverse changes this. 6 | 7 | Plus, it’s easy to [upgrade to Rails 6](#upgrading-to-rails-6) when you get there. 8 | 9 | Works with Rails 4.2+ 10 | 11 | [![Build Status](https://github.com/ankane/multiverse/workflows/build/badge.svg?branch=master)](https://github.com/ankane/multiverse/actions) 12 | 13 | ## Installation 14 | 15 | Add this line to your application’s Gemfile: 16 | 17 | ```ruby 18 | gem 'multiverse' 19 | ``` 20 | 21 | ## Getting Started 22 | 23 | In this example, we’ll have a separate database for our e-commerce catalog that we’ll call `catalog`. 24 | 25 | The first step is to generate the necessary files. 26 | 27 | ```sh 28 | rails generate multiverse:db catalog 29 | ``` 30 | 31 | This creates a `CatalogRecord` class for models to inherit from and adds configuration to `config/database.yml`. It also creates a `db/catalog` directory for migrations and `schema.rb` to live. 32 | 33 | `rails` and `rake` commands run for the original database by default. To run commands for the new database, use the `DB` environment variable. For instance: 34 | 35 | Create the database 36 | 37 | ```sh 38 | DB=catalog rails db:create 39 | ``` 40 | 41 | Create a migration 42 | 43 | ```sh 44 | DB=catalog rails generate migration add_name_to_products 45 | ``` 46 | 47 | Run migrations 48 | 49 | ```sh 50 | DB=catalog rails db:migrate 51 | ``` 52 | 53 | Rollback 54 | 55 | ```sh 56 | DB=catalog rails db:rollback 57 | ``` 58 | 59 | ## Models 60 | 61 | Also works for models 62 | 63 | ```sh 64 | DB=catalog rails generate model Product 65 | ``` 66 | 67 | This generates 68 | 69 | ```rb 70 | class Product < CatalogRecord 71 | end 72 | ``` 73 | 74 | ## Web Servers 75 | 76 | *Only necessary in Rails < 5.2* 77 | 78 | For web servers that fork, be sure to reconnect after forking (just like you do with `ActiveRecord::Base`) 79 | 80 | ### Puma 81 | 82 | In `config/puma.rb`, add inside the `on_worker_boot` block 83 | 84 | ```ruby 85 | CatalogRecord.establish_connection :"catalog_#{Rails.env}" 86 | ``` 87 | 88 | ### Unicorn 89 | 90 | In `config/unicorn.rb`, add inside the `before_fork` block 91 | 92 | ```ruby 93 | CatalogRecord.connection.disconnect! 94 | ``` 95 | 96 | And inside the `after_fork` block 97 | 98 | ```ruby 99 | CatalogRecord.establish_connection :"catalog_#{Rails.env}" 100 | ``` 101 | 102 | ## Testing 103 | 104 | ### Fixtures 105 | 106 | [Rails fixtures](https://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures) work automatically. 107 | 108 | **Note:** Referential integrity is not disabled on additional databases when fixtures are loaded, so you may run into issues if you use foreign keys. Also, you may run into errors with fixtures if the additional databases aren’t the same type as the primary. 109 | 110 | ### RSpec 111 | 112 | After running migrations for additional databases, run: 113 | 114 | ```sh 115 | DB=catalog rails db:test:prepare 116 | ``` 117 | 118 | ### Database Cleaner 119 | 120 | Database Cleaner supports multiple connections out of the box. 121 | 122 | ```ruby 123 | cleaner = DatabaseCleaner[:active_record, {model: CatalogRecord}] 124 | cleaner.strategy = :transaction 125 | cleaner.cleaning do 126 | # code 127 | end 128 | ``` 129 | 130 | [Read more here](https://github.com/DatabaseCleaner/database_cleaner#how-to-use-with-multiple-orms) 131 | 132 | ## Limitations 133 | 134 | There are a few features that aren’t supported on additional databases. 135 | 136 | - Pending migration check 137 | - `schema_cache.yml` 138 | 139 | Also note that `ActiveRecord::Migration.maintain_test_schema!` doesn’t affect additional databases. 140 | 141 | ## Upgrading to Rails 6 142 | 143 | Rails 6 provides a way to manage multiple databases :tada: 144 | 145 | To upgrade from Multiverse, nest your database configuration in `config/database.yml`: 146 | 147 | ```yml 148 | # this should be similar to default, but with migrations_paths 149 | catalog_default: &catalog_default 150 | adapter: ... 151 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 152 | migrations_paths: db/catalog_migrate 153 | 154 | development: 155 | primary: 156 | <<: *default 157 | database: ... 158 | catalog: 159 | <<: *catalog_default 160 | database: ... 161 | 162 | test: 163 | primary: 164 | <<: *default 165 | database: ... 166 | catalog: 167 | <<: *catalog_default 168 | database: ... 169 | 170 | production: 171 | primary: 172 | <<: *default 173 | database: ... 174 | catalog: 175 | <<: *catalog_default 176 | database: ... 177 | ``` 178 | 179 | Then change `establish_connection` in `app/models/catalog_record.rb` to: 180 | 181 | ```rb 182 | class CatalogRecord < ActiveRecord::Base 183 | establish_connection :catalog 184 | end 185 | ``` 186 | 187 | And move: 188 | 189 | - `db/catalog/migrate` to `db/catalog_migrate` 190 | - `db/catalog/schema.rb` to `db/catalog_schema.rb` (or `db/catalog/structure.sql` to `db/catalog_structure.sql`). 191 | 192 | Then remove `multiverse` from your Gemfile. :tada: 193 | 194 | Now you can use the updated commands: 195 | 196 | ```sh 197 | rails db:migrate # run all 198 | rails db:migrate:catalog # runs catalog only 199 | ``` 200 | 201 | Generate migrations with: 202 | 203 | ```sh 204 | rails generate migration add_name_to_products --database=catalog 205 | ``` 206 | 207 | And models with: 208 | 209 | ```sh 210 | rails generate model Product --database=catalog --parent=CatalogRecord 211 | ``` 212 | 213 | Happy scaling! 214 | 215 | ## History 216 | 217 | View the [changelog](https://github.com/ankane/multiverse/blob/master/CHANGELOG.md) 218 | 219 | ## Contributing 220 | 221 | Everyone is encouraged to help improve this project. Here are a few ways you can help: 222 | 223 | - [Report bugs](https://github.com/ankane/multiverse/issues) 224 | - Fix bugs and [submit pull requests](https://github.com/ankane/multiverse/pulls) 225 | - Write, clarify, or fix documentation 226 | - Suggest or add new features 227 | 228 | To get started with development and testing: 229 | 230 | ```sh 231 | git clone https://github.com/ankane/multiverse.git 232 | cd multiverse 233 | bundle install 234 | bundle exec rake test 235 | ``` 236 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList["test/**/*_test.rb"] 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /lib/generators/multiverse/db_generator.rb: -------------------------------------------------------------------------------- 1 | require "rails/generators" 2 | 3 | module Multiverse 4 | module Generators 5 | class DbGenerator < Rails::Generators::Base 6 | source_root File.expand_path("../templates", __FILE__) 7 | 8 | argument :name 9 | 10 | def create_initializer 11 | lower_name = name.underscore 12 | 13 | template "record.rb", "app/models/#{lower_name}_record.rb" 14 | 15 | case ActiveRecord::Base.connection_config[:adapter] 16 | when "sqlite3" 17 | development_conf = "database: db/#{lower_name}_development.sqlite3" 18 | test_conf = "database: db/#{lower_name}_test.sqlite3" 19 | production_conf = "database: db/#{lower_name}_production.sqlite3" 20 | else 21 | development_conf = "database: #{lower_name}_development" 22 | test_conf = "database: #{lower_name}_test" 23 | production_conf = "url: <%= ENV['#{lower_name.upcase}_DATABASE_URL'] %>" 24 | end 25 | 26 | append_to_file "config/database.yml" do 27 | " 28 | #{name}_development: 29 | <<: *default 30 | #{development_conf} 31 | 32 | #{name}_test: 33 | <<: *default 34 | #{test_conf} 35 | 36 | #{name}_production: 37 | <<: *default 38 | #{production_conf} 39 | " 40 | end 41 | 42 | empty_directory "db/#{lower_name}/migrate" 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/generators/multiverse/templates/record.rb.tt: -------------------------------------------------------------------------------- 1 | class <%= name.camelize %>Record < ActiveRecord::Base 2 | self.abstract_class = true 3 | establish_connection :"<%= name.underscore %>_#{Rails.env}" 4 | end 5 | -------------------------------------------------------------------------------- /lib/multiverse.rb: -------------------------------------------------------------------------------- 1 | require "multiverse/generators" 2 | require "multiverse/railtie" 3 | require "multiverse/version" 4 | 5 | module Multiverse 6 | class << self 7 | attr_writer :db 8 | 9 | def db 10 | @db ||= ENV["DB"].presence 11 | end 12 | 13 | def db_dir 14 | db_dir = "#{Rails.application.config.paths["db"].first}/#{db}" 15 | abort "Unknown DB: #{db}" unless Dir.exist?(db_dir) 16 | db_dir 17 | end 18 | 19 | def parent_class_name 20 | "#{db.camelize}Record" 21 | end 22 | 23 | def migrate_path 24 | "#{db_dir}/migrate" 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/multiverse/generators.rb: -------------------------------------------------------------------------------- 1 | module Multiverse 2 | module Generators 3 | module ModelGenerator 4 | def parent_class_name 5 | Multiverse.db ? Multiverse.parent_class_name : super 6 | end 7 | end 8 | 9 | module Migration 10 | def db_migrate_path 11 | Multiverse.db ? Multiverse.migrate_path : super 12 | end 13 | end 14 | 15 | module MigrationTemplate 16 | def migration_template(source, destination, config = {}) 17 | if Multiverse.db 18 | super(source, destination.sub("db/migrate", Multiverse.migrate_path), config) 19 | else 20 | super 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/multiverse/railtie.rb: -------------------------------------------------------------------------------- 1 | require "rails/railtie" 2 | 3 | module Multiverse 4 | class Railtie < Rails::Railtie 5 | generators do 6 | ActiveSupport.on_load(:active_record) do 7 | if ActiveRecord::VERSION::MAJOR >= 5 8 | require "rails/generators/active_record/migration" 9 | ActiveRecord::Generators::Migration.prepend(Multiverse::Generators::Migration) 10 | else 11 | require "rails/generators/migration" 12 | Rails::Generators::Migration.prepend(Multiverse::Generators::MigrationTemplate) 13 | end 14 | 15 | require "rails/generators/active_record/model/model_generator" 16 | ActiveRecord::Generators::ModelGenerator.prepend(Multiverse::Generators::ModelGenerator) 17 | ActiveRecord::Generators::ModelGenerator.prepend(Multiverse::Generators::MigrationTemplate) 18 | 19 | require "rails/generators/active_record/migration/migration_generator" 20 | ActiveRecord::Generators::MigrationGenerator.prepend(Multiverse::Generators::MigrationTemplate) 21 | end 22 | end 23 | 24 | rake_tasks do 25 | namespace :multiverse do 26 | task :load_config do 27 | if Multiverse.db 28 | ActiveRecord::Tasks::DatabaseTasks.migrations_paths = [Multiverse.migrate_path] 29 | ActiveRecord::Tasks::DatabaseTasks.db_dir = [Multiverse.db_dir] 30 | Rails.application.paths["db/seeds.rb"] = ["#{Multiverse.db_dir}/seeds.rb"] 31 | 32 | if ActiveRecord::Tasks::DatabaseTasks.database_configuration 33 | new_config = {} 34 | Rails.application.config.database_configuration.each do |env, config| 35 | if env.start_with?("#{Multiverse.db}_") 36 | new_config[env.sub("#{Multiverse.db}_", "")] = config 37 | end 38 | end 39 | abort "Unknown DB: #{Multiverse.db}" if new_config.empty? 40 | ActiveRecord::Tasks::DatabaseTasks.database_configuration = new_config 41 | end 42 | 43 | # load config 44 | ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {} 45 | ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths 46 | 47 | ActiveRecord::Base.establish_connection 48 | 49 | # need this to run again if environment is loaded afterwards 50 | Rake::Task["db:load_config"].reenable 51 | end 52 | end 53 | end 54 | 55 | Rake::Task["db:load_config"].enhance do 56 | Rake::Task["multiverse:load_config"].execute 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/multiverse/version.rb: -------------------------------------------------------------------------------- 1 | module Multiverse 2 | VERSION = "0.2.2" 3 | end 4 | -------------------------------------------------------------------------------- /multiverse.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "lib/multiverse/version" 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "multiverse" 5 | spec.version = Multiverse::VERSION 6 | spec.summary = "Multiple databases for Rails" 7 | spec.homepage = "https://github.com/ankane/multiverse" 8 | spec.license = "MIT" 9 | 10 | spec.author = "Andrew Kane" 11 | spec.email = "andrew@chartkick.com" 12 | 13 | spec.files = Dir["*.{md,txt}", "{lib}/**/*"] 14 | spec.require_path = "lib" 15 | 16 | spec.required_ruby_version = ">= 2.2" 17 | 18 | spec.add_dependency "activesupport", ">= 4.2" 19 | spec.add_dependency "activerecord", ">= 4.2" 20 | spec.add_dependency "railties", ">= 4.2" 21 | 22 | spec.add_development_dependency "bundler" 23 | spec.add_development_dependency "rake" 24 | spec.add_development_dependency "minitest" 25 | spec.add_development_dependency "sqlite3" 26 | end 27 | -------------------------------------------------------------------------------- /test/multiverse_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | 3 | class MultiverseTest < Minitest::Test 4 | def test_that_it_has_a_version_number 5 | refute_nil ::Multiverse::VERSION 6 | end 7 | 8 | def test_all 9 | gem_path = File.dirname(__dir__) 10 | clean = ENV["CLEAN"] 11 | 12 | Bundler.with_clean_env do 13 | app_dir = "/tmp/multiverse_#{rails_version.gsub(".", "")}" 14 | FileUtils.rm_rf(app_dir) 15 | Dir.mkdir(app_dir) 16 | Dir.chdir(app_dir) do 17 | # create Rails app 18 | open("Gemfile", "w") do |f| 19 | f.puts "source 'https://rubygems.org'" 20 | f.puts "gem 'rails', '#{rails_version}'" 21 | end 22 | cmd "bundle" 23 | cmd "bundle exec rails new . --force --skip-bundle --skip-sprockets #{ENV["FLAGS"]}" 24 | 25 | # sqlite fix 26 | if rails_version.to_f < 5.1 27 | gemfile = File.read("Gemfile").sub("'sqlite3'", "'sqlite3', '< 1.4.0'") 28 | File.open("Gemfile", "w") {|file| file.puts(gemfile) } 29 | elsif rails_version.to_f < 6.1 30 | gemfile = File.read("Gemfile").sub("'sqlite3'", "'sqlite3', '< 1.5.0'") 31 | File.open("Gemfile", "w") {|file| file.puts(gemfile) } 32 | end 33 | 34 | unless clean 35 | # add multiverse 36 | open("Gemfile", "a") do |f| 37 | f.puts "gem 'multiverse', path: '#{gem_path}'" 38 | end 39 | end 40 | cmd "bundle" 41 | 42 | # test unknown database 43 | unless clean 44 | cmd "DB=other bin/rails db:create", succeed: false 45 | end 46 | 47 | # test not in database.yml 48 | unless clean 49 | Dir.mkdir "db/other" 50 | cmd "DB=other bin/rails db:create", succeed: false 51 | end 52 | 53 | unless clean 54 | # generate new database 55 | cmd "bin/rails generate multiverse:db catalog" 56 | end 57 | 58 | # test create 59 | cmd "bin/rails db:create" 60 | assert database_exist?("development") 61 | assert database_exist?("test") 62 | assert !database_exist?("catalog_development") 63 | assert !database_exist?("catalog_test") 64 | 65 | unless clean 66 | cmd "DB=catalog bin/rails db:create" 67 | assert database_exist?("catalog_development") 68 | assert database_exist?("catalog_test") 69 | end 70 | 71 | # test db:seed 72 | File.open("db/seeds.rb", "a"){ |f| f.write("puts __FILE__") } 73 | cmd "bin/rails db:seed" 74 | 75 | unless clean 76 | File.open("db/catalog/seeds.rb", "a"){ |f| f.write("puts __FILE__") } 77 | cmd "DB=catalog bin/rails db:seed" 78 | end 79 | 80 | # test rails generatde model 81 | cmd "bin/rails generate model User" 82 | assert_includes File.read("app/models/user.rb"), (rails5? ? "ApplicationRecord" : "ActiveRecord::Base") 83 | assert_migration "db", "create_users" 84 | 85 | unless clean 86 | cmd "DB=catalog bin/rails generate model Product" 87 | assert_includes File.read("app/models/product.rb"), "CatalogRecord" 88 | assert_migration "db/catalog", "create_products" 89 | end 90 | 91 | # test rails generate migration 92 | cmd "bin/rails generate migration create_posts" 93 | assert_migration "db", "create_posts" 94 | 95 | unless clean 96 | cmd "DB=catalog bin/rails generate migration create_items" 97 | assert_migration "db/catalog", "create_items" 98 | end 99 | 100 | # test db:migrate 101 | cmd "bin/rails db:migrate" 102 | assert_tables("development", ["users", "posts"]) 103 | 104 | unless clean 105 | cmd "DB=catalog bin/rails db:migrate" 106 | assert_tables("catalog_development", ["products", "items"]) 107 | end 108 | 109 | # test db:migrate:status 110 | cmd "bin/rails db:migrate:status" 111 | 112 | unless clean 113 | cmd "DB=catalog bin/rails db:migrate:status" 114 | end 115 | 116 | # test db:version 117 | cmd "bin/rails db:version" 118 | cmd "DB=catalog bin/rails db:version" 119 | 120 | # test:fixtures:load 121 | assert_equal 0, row_count("development", "users") 122 | unless clean 123 | assert_equal 0, row_count("catalog_development", "products") 124 | end 125 | cmd "bin/rails db:fixtures:load" 126 | assert_equal 2, row_count("development", "users") 127 | unless clean 128 | assert_equal 2, row_count("catalog_development", "products") 129 | end 130 | 131 | # test db:rollback 132 | cmd "bin/rails db:rollback" 133 | assert_tables("development", ["users"]) 134 | 135 | unless clean 136 | assert_tables("catalog_development", ["products", "items"]) 137 | cmd "DB=catalog bin/rails db:rollback" 138 | assert_tables("catalog_development", ["products"]) 139 | end 140 | 141 | # test db:drop 142 | cmd "bin/rails db:drop" 143 | assert !database_exist?("development") 144 | assert !database_exist?("test") 145 | 146 | unless clean 147 | assert database_exist?("catalog_development") 148 | assert database_exist?("catalog_test") 149 | 150 | cmd "DB=catalog bin/rails db:drop" 151 | assert !database_exist?("catalog_development") 152 | assert !database_exist?("catalog_test") 153 | end 154 | 155 | # test db:schema:load 156 | cmd "bin/rails db:create db:schema:load" 157 | assert_tables("development", ["users"]) 158 | assert_tables("test", ["users"]) 159 | 160 | unless clean 161 | cmd "DB=catalog bin/rails db:create db:schema:load" 162 | assert_tables("catalog_development", ["products"]) 163 | assert_tables("catalog_test", ["products"]) 164 | end 165 | 166 | # test db:test:prepare 167 | cmd "bin/rails db:drop db:create db:test:prepare" 168 | assert_tables("test", ["users"]) 169 | 170 | unless clean 171 | cmd "DB=catalog bin/rails db:drop db:create db:test:prepare" 172 | assert_tables("catalog_test", ["products"]) 173 | end 174 | 175 | # test db:structure:dump 176 | cmd "bin/rails db:create db:migrate" 177 | cmd "bin/rails db:structure:dump" 178 | cmd "bin/rails db:drop db:create db:structure:load" 179 | assert_tables("development", ["users", "posts"]) 180 | assert_tables("test", ["users", "posts"]) 181 | 182 | unless clean 183 | cmd "DB=catalog bin/rails db:create db:migrate" 184 | cmd "DB=catalog bin/rails db:structure:dump" 185 | cmd "DB=catalog bin/rails db:drop db:create db:structure:load" 186 | assert_tables("catalog_development", ["products", "items"]) 187 | assert_tables("catalog_test", ["products", "items"]) 188 | end 189 | 190 | # test db:schema:cache:dump 191 | cmd "bin/rails db:schema:cache:dump" 192 | cache_ext = rails_version >= "5.1" ? "yml" : "dump" 193 | filename = "db/schema_cache.#{cache_ext}" 194 | assert_match "users", read_file(filename) 195 | cmd "bin/rails db:schema:cache:clear" 196 | assert !File.exist?(filename) 197 | 198 | unless clean 199 | cmd "DB=catalog bin/rails db:schema:cache:dump" 200 | filename = "db/catalog/schema_cache.#{cache_ext}" 201 | assert_match "products", read_file(filename) 202 | cmd "DB=catalog bin/rails db:schema:cache:clear" 203 | assert !File.exist?(filename) 204 | end 205 | end 206 | end 207 | end 208 | 209 | private 210 | 211 | def cmd(command, succeed: true) 212 | command = command.sub("bin/rails db", "bin/rake db") unless rails5? 213 | puts "> #{command}" 214 | result = system(command) 215 | if succeed 216 | assert result, "Expected command to succeed" 217 | else 218 | assert !result, "Expected command to fail" 219 | end 220 | puts 221 | end 222 | 223 | def rails_version 224 | ENV["RAILS_VERSION"] || "5.2.3" 225 | end 226 | 227 | def database_exist?(dbname) 228 | File.exist?("db/#{dbname}.sqlite3") 229 | end 230 | 231 | def read_file(filename) 232 | File.read(filename).encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') 233 | end 234 | 235 | def assert_migration(dir, name) 236 | dir = "#{dir}/migrate" 237 | assert Dir.entries(dir).any? { |f| f.include?(name) }, "#{dir} does not contain #{name} migration" 238 | end 239 | 240 | def row_count(dbname, table) 241 | db = SQLite3::Database.new("db/#{dbname}.sqlite3") 242 | db.execute("SELECT COUNT(*) FROM #{table}").first.first 243 | end 244 | 245 | def assert_tables(dbname, tables) 246 | default_tables = rails5? ? ["ar_internal_metadata"] : [] 247 | expected_tables = tables + default_tables + ["schema_migrations"] 248 | assert_equal expected_tables.sort, actual_tables(dbname).sort 249 | end 250 | 251 | def actual_tables(dbname) 252 | db = SQLite3::Database.new("db/#{dbname}.sqlite3") 253 | db.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence'").map(&:first) 254 | end 255 | 256 | def rails5? 257 | rails_version.to_i >= 5 258 | end 259 | end 260 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | Bundler.require(:default) 3 | require "minitest/autorun" 4 | require "minitest/pride" 5 | require "fileutils" 6 | require "sqlite3" 7 | --------------------------------------------------------------------------------