Skip to content
This repository has been archived by the owner on Jun 5, 2023. It is now read-only.

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ankane committed Nov 2, 2017
0 parents commit 03a36fb
Show file tree
Hide file tree
Showing 16 changed files with 361 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
@@ -0,0 +1,9 @@
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
*.lock
3 changes: 3 additions & 0 deletions CHANGELOG.md
@@ -0,0 +1,3 @@
## 0.0.1

- First release
4 changes: 4 additions & 0 deletions Gemfile
@@ -0,0 +1,4 @@
source "https://rubygems.org"

# Specify your gem's dependencies in multiverse.gemspec
gemspec
22 changes: 22 additions & 0 deletions LICENSE.txt
@@ -0,0 +1,22 @@
Copyright (c) 2017 Andrew Kane

MIT License

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.
109 changes: 109 additions & 0 deletions README.md
@@ -0,0 +1,109 @@
# Multiverse

:fire: Multiple databases for Rails

One of the easiest ways to scale your database is to move large, infrequently-joined tables to a separate database. **ActiveRecord supports multiple databases, but Rails doesn’t provide a way to manage them.** Multiverse changes this.

Works with Rails 5+

## Installation

Add this line to your application’s Gemfile:

```ruby
gem 'multiverse'
```

## Getting Started

Generate a new database

```sh
rails generate multiverse:db catalog
```

This generates `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.

`rails` and `rake` commands will run for the original database by default. To run commands for the new database, use the `DB` environment variable. For instance:

Create the database

```sh
DB=catalog rake db:create
```

Create a migration

```sh
DB=catalog rails generate migration add_name_to_products
```

Run migrations

```sh
DB=catalog rake db:migrate
```

Rollback

```sh
DB=catalog rake db:rollback
```

## Models

Also works for models

```sh
DB=catalog rails generate model Product
```

This generates

```rb
class Product < CatalogRecord
end
```

## Seeds

Use `db/seeds.rb` for all databases

## Web Servers

### Puma

Add to `config/puma.rb`

```ruby
on_worker_boot do
CatalogRecord.establish_connection :"catalog_#{Rails.env}"
end
```

### Unicorn

Add to `config/unicorn.rb`

```ruby
before_fork do |server, worker|
CatalogRecord.connection.disconnect!
end

after_fork do |server, worker|
CatalogRecord.establish_connection :"catalog_#{Rails.env}"
end
```

## History

View the [changelog](https://github.com/ankane/multiverse/blob/master/CHANGELOG.md)

## Contributing

Everyone is encouraged to help improve this project. Here are a few ways you can help:

- [Report bugs](https://github.com/ankane/multiverse/issues)
- Fix bugs and [submit pull requests](https://github.com/ankane/multiverse/pulls)
- Write, clarify, or fix documentation
- Suggest or add new features
10 changes: 10 additions & 0 deletions Rakefile
@@ -0,0 +1,10 @@
require "bundler/gem_tasks"
require "rake/testtask"

Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList["test/**/*_test.rb"]
end

task :default => :test
35 changes: 35 additions & 0 deletions lib/generators/multiverse/db_generator.rb
@@ -0,0 +1,35 @@
require "rails/generators"

module Multiverse
module Generators
class DbGenerator < Rails::Generators::Base
source_root File.expand_path("../templates", __FILE__)

argument :name

def create_initializer
lower_name = name.underscore

template "record.rb", "app/models/#{lower_name}_record.rb"

append_to_file "config/database.yml" do
"
#{name}_development:
<<: *default
database: #{lower_name}_development
#{name}_test:
<<: *default
database: #{lower_name}_test
#{name}_production:
<<: *default
url: <%= ENV['#{lower_name.upcase}_DATABASE_URL'] %>
"
end

empty_directory "db/#{lower_name}/migrate"
end
end
end
end
4 changes: 4 additions & 0 deletions lib/generators/multiverse/templates/record.rb
@@ -0,0 +1,4 @@
class <%= name.camelize %>Record < ActiveRecord::Base
self.abstract_class = true
establish_connection :"<%= name.underscore %>_#{Rails.env}"
end
41 changes: 41 additions & 0 deletions lib/multiverse.rb
@@ -0,0 +1,41 @@
require "multiverse/generators"
require "multiverse/patches"
require "multiverse/railtie"
require "multiverse/version"

module Multiverse
class << self
attr_writer :db

def db
@db ||= ENV["DB"]
end

def db_dir
db_dir = db ? "db/#{db}" : "db"
abort "Unknown DB: #{db}" if db && !Dir.exist?(db_dir)
db_dir
end

def parent_class_name
db ? "#{db.camelize}Record" : "ApplicationRecord"
end

def record_class
record_class = parent_class_name.safe_constantize
abort "Missing model: #{parent_class_name}" unless record_class
record_class
end

def migrate_path
"#{db_dir}/migrate"
end
end
end

ActiveSupport.on_load(:active_record) do
ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend Multiverse::DatabaseTasks
ActiveRecord::Migration.prepend Multiverse::Migration
ActiveRecord::SchemaMigration.singleton_class.prepend Multiverse::SchemaMigration
ActiveRecord::SchemaDumper.singleton_class.prepend Multiverse::SchemaDumper
end
15 changes: 15 additions & 0 deletions lib/multiverse/generators.rb
@@ -0,0 +1,15 @@
module Multiverse
module Generators
module ModelGenerator
def parent_class_name
Multiverse.parent_class_name
end
end

module Migration
def db_migrate_path
Multiverse.migrate_path
end
end
end
end
46 changes: 46 additions & 0 deletions lib/multiverse/patches.rb
@@ -0,0 +1,46 @@
module Multiverse
module DatabaseTasks
def each_current_configuration(environment)
if Multiverse.db
environments = ["#{Multiverse.db}_#{environment}"]
environments << "#{Multiverse.db}_test" if environment == "development"
self.migrations_paths = Multiverse.migrate_path
self.db_dir = Multiverse.db_dir
else
environments = [environment]
environments << "test" if environment == "development"
end

configurations = ActiveRecord::Base.configurations.values_at(*environments)
configurations.compact.each do |configuration|
yield configuration unless configuration['database'].blank?
end
end
end

module Migration
def connection
@connection || Multiverse.record_class.connection
end

# TODO don't checkout main connection at all
def exec_migration(_, direction)
Multiverse.record_class.connection_pool.with_connection do |conn|
super(conn, direction)
end
end
end

module SchemaMigration
def connection
Multiverse.record_class.connection
end
end

module SchemaDumper
def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base)
new(Multiverse.record_class.connection, generate_options(Multiverse.record_class)).dump(stream)
stream
end
end
end
22 changes: 22 additions & 0 deletions lib/multiverse/railtie.rb
@@ -0,0 +1,22 @@
require "rails/railtie"

module Multiverse
class Railtie < Rails::Railtie
generators do
require "rails/generators/active_record/migration"
ActiveRecord::Generators::Migration.prepend(Multiverse::Generators::Migration)

require "rails/generators/active_record/model/model_generator"
ActiveRecord::Generators::ModelGenerator.prepend(Multiverse::Generators::ModelGenerator)
end

rake_tasks do
namespace :db do
task :load_config do
ActiveRecord::Tasks::DatabaseTasks.migrations_paths = [Multiverse.migrate_path]
ActiveRecord::Tasks::DatabaseTasks.db_dir = [Multiverse.db_dir]
end
end
end
end
end
3 changes: 3 additions & 0 deletions lib/multiverse/version.rb
@@ -0,0 +1,3 @@
module Multiverse
VERSION = "0.0.1"
end
27 changes: 27 additions & 0 deletions multiverse.gemspec
@@ -0,0 +1,27 @@

lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "multiverse/version"

Gem::Specification.new do |spec|
spec.name = "multiverse"
spec.version = Multiverse::VERSION
spec.authors = ["Andrew Kane"]
spec.email = ["andrew@chartkick.com"]

spec.summary = "Multiple databases for Rails"
spec.homepage = "https://github.com/ankane/multiverse"

spec.files = `git ls-files -z`.split("\x0").reject do |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 "rails", ">= 5"

spec.add_development_dependency "bundler"
spec.add_development_dependency "rake"
spec.add_development_dependency "minitest"
end
7 changes: 7 additions & 0 deletions test/multiverse_test.rb
@@ -0,0 +1,7 @@
require_relative "test_helper"

class MultiverseTest < Minitest::Test
def test_that_it_has_a_version_number
refute_nil ::Multiverse::VERSION
end
end
4 changes: 4 additions & 0 deletions test/test_helper.rb
@@ -0,0 +1,4 @@
require "bundler/setup"
Bundler.require(:default)
require "minitest/autorun"
require "minitest/pride"

0 comments on commit 03a36fb

Please sign in to comment.