ActiveRecord6（複数DB接続）をRailsじゃない環境で使う

ActiveRecordをRailsじゃない環境で使う（2019年版）
https://qiita.com/YusukeIwaki/items/b009ea3465fd8a1868d6

の複数DB版。**「日次で集計した結果は、別のDBに保持したいの♪」**って言われても困らないように準備だ。

※これは、完全にお試しで、自分用めもです。

---

## docker-composeにもう1台MySQLコンテナを追加する

```diff:docker-compose.ymlの変更
diff --git a/docker-compose.yml b/docker-compose.yml
index 026ffba..6c93bb6 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,6 +7,13 @@ services:
     volumes:
       - mysql-data:/var/lib/mysql
 
+  datacenter:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: fugafuga
+    volumes:
+      - datacenter-data:/var/lib/mysql
+
   workspace:
     image: ruby:2.6
     stdin_open: true
@@ -14,10 +21,14 @@ services:
     environment:
       MYSQL_HOST: mysql
       MYSQL_PASSWORD: hogehoge
+      DATACENTER_MYSQL_HOST: datacenter
+      DATACENTER_MYSQL_PASSWORD: fugafuga
     depends_on:
       - mysql
+      - datacenter
     links:
       - mysql
+      - datacenter
     volumes:
       - .:/usr/src/app
     working_dir: /usr/src/app
@@ -25,3 +36,5 @@ services:
 volumes:
   mysql-data:
     driver: local
+  datacenter-data:
+    driver: local
```

## Gemfileの更新

2019/07/12現在だと、バージョン無指定では5.2.3が降ってくるので、バージョンを指定する。

```diff:Gemfileの変更
diff --git a/Gemfile b/Gemfile
index 9e56d8b..21c64ad 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,6 +4,6 @@ source "https://rubygems.org"
 
 git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
 
-gem 'activerecord'
+gem 'activerecord', '~> 6.0.0.rc1'
 gem 'mysql2'
 gem 'rake'
```

## db:create

データ集計用のDBはconfig/database.ymlにどう書けばええのん？とおもって
https://github.com/rails/rails/blob/v6.0.0.rc1/activerecord/lib/active_record/tasks/database_tasks.rb
あたりのソースを見てみたら、ふつうにconfig/database.ymlのハッシュを1階層深くするだけで動きそうに見えた。

```config/database.yml
development:
  data:
    adapter: mysql2
    encoding: utf8mb4
    database: ar_without_rails_playground
    pool: 5
    username: root
    password: <%= ENV['MYSQL_PASSWORD'] %>
    host: <%= ENV['MYSQL_HOST'] %>

  datacenter:
    adapter: mysql2
    encoding: utf8mb4
    database: ar_without_rails_aggregation
    pool: 5
    username: root
    password: <%= ENV['DATACENTER_MYSQL_PASSWORD'] %>
    host: <%= ENV['DATACENTER_MYSQL_HOST'] %>
```

```
root@2bf5bdca9402:/usr/src/app# bundle exec rake db:create    
Database 'ar_without_rails_playground' already exists
Created database 'ar_without_rails_aggregation'
```

お！

念の為、mysqlコマンドで正しく作られているかも見てみたが

```
root@2bf5bdca9402:/usr/src/app# echo 'SHOW DATABASES;' | mysql -h datacenter -u root -pfugafuga
Database
information_schema
ar_without_rails_aggregation
mysql
performance_schema
sys
root@2bf5bdca9402:/usr/src/app# echo 'SHOW DATABASES;' | mysql -h mysql -u root -phogehoge
Database
information_schema
ar_without_rails_playground
mysql
performance_schema
sys
```

期待通りにmysqlコンテナの方は変わらず、datacenterのほうにar_without_rails_aggregationデータベースが作られている。

## db:migrate

マイグレーションファイルはどう書けばええのん？ってなった。

ActiveRecord::Tasks::DatabaseTasksを再度見てみると `migration_context` っていう気になる単語を発見。
[blame](https://github.com/rails/rails/blame/v6.0.0.rc1/activerecord/lib/active_record/tasks/database_tasks.rb)すると・・・ https://github.com/rails/rails/pull/31727 こんなのを発見。

**migrations_paths** 使えって書いてた。ほう！！！

```diff:config/database.ymlに追記
diff --git a/config/database.yml b/config/database.yml
index 7833f19..0473892 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -16,3 +16,4 @@ development:
     username: root
     password: <%= ENV['DATACENTER_MYSQL_PASSWORD'] %>
     host: <%= ENV['DATACENTER_MYSQL_HOST'] %>
+    migrations_paths: db/migrate_for_datacenter
```

```db/migrate_for_datacenter/20190712230727_create_daily_aggregation_results.rb 
class CreateDailyAggregationResults < ActiveRecord::Migration[6.0]
  def up
    create_table :daily_aggregation_results do |t|
      t.date :target_date, null: false
      t.integer :count, null: false
      t.datetime :aggregated_at, null: false, index: true
    end
  end

  def down
    drop_table :daily_aggregation_results
  end
```

すると・・・

```
root@2bf5bdca9402:/usr/src/app# bundle exec rake db:migrate
== 20190712230727 CreateDailyAggregationResults: migrating ====================
-- create_table(:daily_aggregation_results)
   -> 0.0089s
== 20190712230727 CreateDailyAggregationResults: migrated (0.0090s) ===========
```

daily_aggregation_resultsテーブルが作られた。

```
root@2bf5bdca9402:/usr/src/app# mysql -h mysql -u root -phogehoge ar_without_rails_playground
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 33
Server version: 5.7.26 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [ar_without_rails_playground]> SHOW TABLES;
+---------------------------------------+
| Tables_in_ar_without_rails_playground |
+---------------------------------------+
| ar_internal_metadata                  |
| schema_migrations                     |
| todo_items                            |
+---------------------------------------+
3 rows in set (0.001 sec)

MySQL [ar_without_rails_playground]> exit
Bye
root@2bf5bdca9402:/usr/src/app# mysql -h datacenter -u root -pfugafuga ar_without_rails_aggregation
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 18
Server version: 5.7.26 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [ar_without_rails_aggregation]> SHOW TABLES;
+----------------------------------------+
| Tables_in_ar_without_rails_aggregation |
+----------------------------------------+
| ar_internal_metadata                   |
| daily_aggregation_results              |
| schema_migrations                      |
+----------------------------------------+
3 rows in set (0.001 sec)
```

期待通りにテーブルが作られている。

## モデル

```app/models/daily_aggregation_result.rb
class DailyAggregationResult < ActiveRecord::Base
end
```

を作るわけだが、これだけだと

* DailyAggregationResultは datacenter（ar_without_rails_aggregationデータベース）のテーブル
* TodoItemは data(ar_without_rails_playgroundデータベース)のテーブル

をそれぞれ見に行くべきという宣言がどこにもなく、エラーになってしまう。

### connected_toを使って明示的に「○○に接続して」と書く方法

集計スクリプトであれば、全部集計し終わったデータをDBを切り替えて書く、ということになるのでこの方法がシンプルと思われる。

```diff:code/init.rbの変更
diff --git a/config/init.rb b/config/init.rb
index 843dd41..7345dbe 100644
--- a/config/init.rb
+++ b/config/init.rb
@@ -9,7 +9,7 @@ config_database_yml = YAML::load(ERB.new(File.read(config_database_yml_file)).re
 
 # データベース接続の定義. created_at などで ActiveSupport::TimeWithZone を返してもらうための設定。
 Time.zone = 'Tokyo'
-ActiveRecord::Base.establish_connection(config_database_yml['development'])
+ActiveRecord::Base.configurations = config_database_yml['development']
 ActiveRecord::Base.time_zone_aware_attributes = true
 
 # モデルのクラスをrequireしておく
```

のように、establish_connectionはせずに `configurations=` でYAMLを渡すだけにする。
（これ単体ではDB接続しない）

そんで、集計スクリプトで

```scripts/aggregate_todo_count_by_date.rb
require './config/init.rb'

# 日ごとの件数を数えてハッシュに入れる。
results =
  ActiveRecord::Base.connected_to(database: :data) do
    TodoItem.group("DATE_FORMAT(created_at, '%Y-%m-%d')").count
  end

# 集計結果テーブルにbulk insertする
data = results.map do |created_at, count|
  { target_date: created_at, count: count, aggregated_at: Time.current }
end
ActiveRecord::Base.connected_to(database: :datacenter) do
  DailyAggregationResult.insert_all(data)
end
```

って感じに、都度どの接続先かを明記するようにすれば、

```
irb(main):005:0> ActiveRecord::Base.connected_to(database: :datacenter){ puts DailyAggregationResult.all.map(&:attributes) }
{"id"=>1, "target_date"=>Sun, 07 Jul 2019, "count"=>12, "aggregated_at"=>Sat, 13 Jul 2019 00:25:16 JST +09:00}
{"id"=>2, "target_date"=>Mon, 08 Jul 2019, "count"=>15, "aggregated_at"=>Sat, 13 Jul 2019 00:25:16 JST +09:00}
{"id"=>3, "target_date"=>Tue, 09 Jul 2019, "count"=>10, "aggregated_at"=>Sat, 13 Jul 2019 00:25:16 JST +09:00}
{"id"=>4, "target_date"=>Wed, 10 Jul 2019, "count"=>12, "aggregated_at"=>Sat, 13 Jul 2019 00:25:16 JST +09:00}
{"id"=>5, "target_date"=>Thu, 11 Jul 2019, "count"=>18, "aggregated_at"=>Sat, 13 Jul 2019 00:25:16 JST +09:00}
{"id"=>6, "target_date"=>Fri, 12 Jul 2019, "count"=>33, "aggregated_at"=>Sat, 13 Jul 2019 00:25:16 JST +09:00}
```

集計データは別のDBに無事に書かれる。

### connects_toを使って、「このモデルはこのDBを参照せよ」って指定する方法

触るモデルが多かったり、2つのDBのモデルを同時に触る必要があったりすると、この方法を使う必要がある。

が、今回はその必要性までは感じてないので、試していない :swea

たぶん https://github.com/rails/rails/pull/34052/files に書いてあるようにすれば動くんじゃない？（無責任ｗ）

## まとめ

とりあえず、6.0の正式リリースされるまで待とう。そしたら色んな人がきっと調べてくれるはずだ。（無責任ｗ）

今回おためししたソースコードは :point_down: 
https://github.com/YusukeIwaki/ar_without_rails_playground