【Rails】database.ymlのadapter:を指定した内部動作を理解する

こちらの記事は[ソニックガーデン 若手プログラマ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/sonicgarden-junior)の1日目の記事です。

若手のトップバッターとして、ふわっとしか理解していなかったアダプターについて書きました。

## はじめに

Ruby on Railsで開発を行う際に、database.ymlに設定するアダプター（`adapter:`）について、どれほど理解していますか？

```yml:database.yml
default: &default
  adapter: postgresql
  ...
```

こちらのように`adapter:`を指定するだけで、Railsが自動的にSQLを発行してくれる仕組みが気になり、内部で何が起こっているのか調べてみました。

この記事では、以下の内容について解説します。

- `adapter:`を指定すると、内部で何が変わるのか
- `pg`や`mysql2`といったgemがどのタイミングで使われるのか

## そもそも Active Record とは

Active Recordは、Ruby on Railsにおけるオブジェクトリレーショナルマッピング（ORM）システムです。これにより、データベースのテーブルをRubyのオブジェクトとして操作でき、SQLを直接書かずにデータベース操作を行うことが可能になります。

Railsを使って開発している場合、以下のようなイメージでActive Recordを利用していると思います。

![スクリーンショット 2024-12-01 8.39.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494533/6c883043-917d-1559-a8eb-ae36acafef20.png)

「ActiveRecordのコードからいい感じでSQLを発行してくれてるんだな」ぐらいのイメージでいる方もいらっしゃるかと思います。

## `adapter:`を指定すると内部的に何が変わるのか

では、`database.yml`で指定する`adapter:`が何を意味するのか、詳しく見ていきましょう。

Active Recordのアダプターは、異なるデータベースエンジン（PostgreSQL、MySQLなど）間での差異を吸収し、共通のインターフェースを提供します。これにより、データベースを切り替えても、アプリケーションコードをほとんど変更せずに済みます。

例えば、`database.yml`に以下のように`adapter: postgresql`を設定すると、Railsは`ActiveRecord::ConnectionAdapters::PostgreSQLAdapter`を使用して、PostgreSQLとの接続やSQLの発行を行います。

```yml:database.yml
adapter: postgresql
```

このように、データベースエンジンに適切なアダプターを指定することで、Railsはそのデータベースに合わせた処理を自動的に行います。もしMySQLを使用する場合は、`adapter: mysql2`を指定すれば、`ActiveRecord::ConnectionAdapters::Mysql2Adapter`が使われます。

以下は、アダプターのイメージ図です。

![スクリーンショット 2024-11-30 17.48.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494533/73f625e2-3531-a263-d355-4fdb297201aa.png)

アダプターは、異なるデータベースを使用してもActive Recordが一貫した動作をするための役割を担っています。

## `pg`や`mysql2`はどこで使われるのか

次に、`pg`や`mysql2`などのgemが、実際にどこで使われるのかについて見ていきましょう。

例えば、`ActiveRecord::ConnectionAdapters::PostgreSQLAdapter`は、内部で`pg` gemを利用してPostgreSQLとの接続を行います。以下は、`postgresql_adapter.rb`の一部です。

```ruby:postgresql_adapter.rb
require "pg"
...

def connect
  @raw_connection = self.class.new_client(@connection_parameters)
  ...
end

def new_client(conn_params)
  PG.connect(**conn_params)
  ...
end
```

`require "pg"`によってpg gemが読み込まれ、`new_client`メソッド内で`PG.connect`が呼ばれることで、PostgreSQLとの接続が確立されます。`PG.connect`は`PG::Connection`というPostgresSQL接続のクラスをnewする役割を果たしています。この接続が、RailsアプリケーションからPostgreSQLに対するクエリを発行できるようにします。

以下の図で、pg gemがどのように使用されるかのイメージを示します。

![スクリーンショット 2024-11-30 18.02.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494533/999f6490-2dab-260f-8f67-6283876ebb20.png)

## 結論

- `database.yml`で指定する`adapter:`に応じて、Railsは異なるアダプタークラス(`PostgreSQLAdapter`・`Mysql2Adapter`）を使用する
- アダプタークラス内では、対応するgem（`pg`・`mysql2`）がrequireされ、実際のデータベースとの接続が確立される

アダプターによってRailsは異なるデータベースエンジンを抽象化し、アプリケーションコードを変更することなく、さまざまなデータベースに対応できるようなるということが分かりました。

この記事を読んでくれた方も、Railsがよしなにやっている部分のコード見てみても面白いかもしれません…！

## 参考文献

この記事を書く上でどのあたりのコードを読んだのか参考になれば幸いです。

- [railtie.rb initializer "active_record.initialize_database"](https://github.com/rails/rails/blob/2aa67ecba7018e985fc75c529204f3844615b7c4/activerecord/lib/active_record/railtie.rb#L256-L262)
- [connection_handling.rb establish_connection](https://github.com/rails/rails/blob/dd8f7185faeca6ee968a6e9367f6d8601a83b8db/activerecord/lib/active_record/connection_handling.rb#L50)
- [postgresql_adapter.rb new_client](https://github.com/rails/rails/blob/dd8f7185faeca6ee968a6e9367f6d8601a83b8db/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L57)
- [postgresql_adapter.rb connect](https://github.com/rails/rails/blob/dd8f7185faeca6ee968a6e9367f6d8601a83b8db/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L933)
- [ruby-pg pg.rb connect](https://github.com/ged/ruby-pg/blob/c6d1047b419e87e57aaec076d9338e80f9bd269a/lib/pg.rb#L67)