Rails6 のちょい足しな新機能を試す46（multidb connects_to 編）

# はじめに

Rails 6 に追加されそうな新機能を試す第46段。 今回は、 `multidb connects_to` 編です。
Rails 6 では、 複数DBに対応したため、DB接続先を切り変えることができるようになっています。

Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は `gem install rails --prerelease` でインストールできます。

```shell
$ rails --version
Rails 6.0.0.rc1
```

# 今回の準備

今回は、 [Rails6 のちょい足しな新機能を試す35（multidb migration --database オプション編）](https://qiita.com/suketa/items/98c0e5e7af310915205f)
をベースに作業を進めます。


# library 側にも users テーブルを追加する

backbone 側に `users` テーブルが存在しますが、 `library` 側にも `users` テーブルを追加します。

```shell
$ bin/rails g migration CreateUser name --db=library
```

# db:migrate を実行する

```shell
bin/rails g db:migrate
```

# User モデルを作る

User モデルを作ります。

```ruby:app/models/user.rb
class User < ApplicationRecord
end
```

# Book モデルを作る

Book モデルを作ります。
`books` テーブルは、 `backbone` にはなく、 `library` 側にありますので、DB接続先を `connects_to` を使って明示的に設定します。

```ruby:app/models/book.rb
class Book < ApplicationRecord
  connects_to database: { writing: :library, reading: :library }
end
```

Rails 6.0.0rc1 では、 `:writing` と `:reading` の両方を指定する必要があるようです。

```ruby
  connects_to database: :library
```

だとエラーになりました。

今回は、Book クラスに直接 DB 接続先を記載しましたが、もし、Bookモデルの他にも、 library 側に接続する接続するモデルがあれば、 `ApplicationRecord` から派生させた抽象クラスを作成して、抽象クラスの中で、 `connects_to` を使い、 Book クラスは、その抽象クラスから派生させた方が良いでしょう。(Rails Guide  ではそういう書き方になってます。）


# seed データを作る

`backbone` 側 `library` 側双方に `users` テーブルが存在するため、両方のDBに接続してデータを登録するように、 `ActiveRecord::Base.connected_to` を使います。

`books` テーブルは、 `library` 側に存在しますが、 `Book` クラスで接続先を `library` に指定しているため、 `ActiveRecord::Base.connected_to` を使う必要がありません。

```ruby:db/seeds.rb
User.create(name: 'Taro')

ActiveRecord::Base.connected_to(database: :library) do
  User.create(name: 'Hanako')
end

Book.create(title: 'Programming Ruby')
```

# seed データを登録する

```shell
$ bin/rails db:seed
```

# controller と View を作る

登録した seed データを表示するため、 controller と view を作ります。

```shell
bin/rails g controller Dashboard index
```

# `User` モデルにメソッドを追加する

library の `users` テーブルを検索できるように `User` モデルに `all_in_library` メソッドを追加します。
`ActiveRecord::Base.connected_to` を使って `library` 側に接続先を変更してから `all` メソッドを呼び出します。

```ruby:app/models/user.rb
class User < ApplicationRecord
  def self.all_in_library
    ActiveRecord::Base.connected_to(database: :library) do
      all
    end
  end
end
```

# DashboardController#index メソッドを修正する

登録したデータを取得するように `index` メソッドを修正します。

```ruby:app/controllers/dashboard_controller.rb
class DashboardController < ApplicationController
  def index
    @books = Book.all
    @users = User.all.to_a
    @users_in_library = User.all_in_library
  end
end
```

ここで、 メソッドの2行目を `@user = User.all.to_a` としている理由は後述します。

# データを表示する View を作成する

各変数の値を表示するための View を作成します。

```ruby:app/views/dashboard/index.html.erb
<h1>Dashboard#index</h1>

<table>
  <thead>
    <tr>
      <th>
        valiable
      </th>
      <th>
        value
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        @books
      </td>
      <td>
        <%= @books.map(&:title) %>
      </td>
    </tr>
    <tr>
      <td>
        @users
      </td>
      <td>
        <%= @users.map(&:name) %>
      </td>
    </tr>
    <tr>
      <td>
        @users_in_library
      </td>
      <td>
        <%= @users_in_library.map(&:name) %>
      </td>
    </tr>
  </tbody>
</table>
```

# rails server を起動してブラウザで表示する

```shell
$ bin/rails s
```

http://localhost/dashboard/index にアクセスすると値が表示されます。
![index_ok.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209941/2f70ae5c-89c8-95ea-9859-afb2c1746e67.png)


# User.all ではなく User.all.to_a な理由

User.all にすると、 Taro の値が表示されず Hanako になってしまいます。 

![index_ng.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209941/c43dd926-0e9a-2211-a529-4825da993eca.png)


これは、 `all` メソッドが `ActiveRecord::Relation` を返すだけで、 実際にSQLを発行しないためです。 このため、 `all` にすると、 View を rendering するときに、SQLが発行されてしまい、接続先が `library` に設定された状態で SQLが発行されてしまうようです。
これを防ぐために、DB の接続が library に変わる前に SQLを発行するため、意図的に `to_a` をつけました。

```ruby:app/controllers/dashboard_controller.rb
  def index
    @books = Book.all
    @users = User.all # => ActiveRecord::Relation が返され SQLは実行されない。
    @users_in_library = User.all_in_library # 接続先が library に変わる

    # このあと index View をレンダリングする際に、全てのSQLが発行される。
  end
```



`User.all` の場合、View のレンダリングの際にすべての SQL が発行されていることがログからもわかります。

```
  Rendering dashboard/index.html.erb within layouts/application
  Book Load (0.3ms)  SELECT "books".* FROM "books"
  ↳ app/views/dashboard/index.html.erb:20:in `map'
  User Load (0.3ms)  SELECT "users".* FROM "users"
  ↳ app/views/dashboard/index.html.erb:28:in `map'
  User Load (0.5ms)  SELECT "users".* FROM "users"
  ↳ app/views/dashboard/index.html.erb:36:in `map'
```

`User.all.to_a` とした場合、controller の中で SQL が発行されていることがわかります。

```
  User Load (0.4ms)  SELECT "users".* FROM "users"
  ↳ app/controllers/dashboard_controller.rb:4:in `index'
  Rendering dashboard/index.html.erb within layouts/application
  Book Load (0.3ms)  SELECT "books".* FROM "books"
  ↳ app/views/dashboard/index.html.erb:20:in `map'
  User Load (0.4ms)  SELECT "users".* FROM "users"
  ↳ app/views/dashboard/index.html.erb:36:in `map'
```

今回、試したコードが特殊な例かも知れませんが、挙動が少し紛らわしいので、今後のバージョンで修正されるかも知れませんね。

# 試したソース

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try046_multidb_connection_switching

# 参考情報

* [What is new in Rails 6.0](https://bogdanvlviv.com/posts/ruby/rails/what-is-new-in-rails-6_0.html)
* [Part 4: Multi db improvements, Basic API for connection switching](https://github.com/rails/rails/pull/34052)
* [Multiple Databases with Active Record](https://edgeguides.rubyonrails.org/active_record_multiple_databases.html)