銀行員からのRailsエンジニア

銀行員からのRailsエンジニア

銀行員から転身したサービス作りが大好きなRailsエンジニアのブログです。個人で開発したサービスをいくつか運営しており、今も新しいサービスを開発しています。転職して日々感じていること、個人開発サービス運営のことなどを等身大で書いていきます。

当ブログではアフィリエイト広告を利用しています

【Rubyによるデザインパターンまとめ7】アダプターパターン

コードの品質向上のため、Rubyデザインパターンを解説した名著である Rubyによるデザインパターン で紹介されているデザインパターンを1つずつまとめており、今回が第7弾です。(毎週1つが目標です!)

前回の記事(コマンドパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com

今回は アダプター(Adapter)パターン についてまとめました。

新しい試みとして、RailsActiveRecord の中でアダプターパターンが使われている箇所があったので、その箇所をコードリーディングしながら説明しています。

f:id:ysk_pro:20191222141612p:plain

アダプターパターンとは

必要なインターフェースと既存のオブジェクトのインターフェースの違いを吸収するデザインパターンです。

以下の「アダプタ」という言葉の説明そのままの役割です。

アダプタとは、異なる複数の機器に接続する際に用いられる中間装置の総称である。(IT用語辞典バイナリ より)

文章の説明よりも実際のコードを見た方が分かりやすいと思うので、以下のコードをご覧ください。

アダプターパターンが実際に使われているコード

今回は、ActiveRecord 内でアダプターパターンが実際に使われている箇所のコードリーディングを行います。(ActiveRecord は、Ruby on Rails で使われる O/R マッパーです)

その前にまず、アダプターパターンを使っている理由を説明します。

MySQL、Postgresなどのデータベースはそれぞれ Ruby API を提供していますが、それぞれの API のインターフェースはそれぞれ異なっています。

例えば、MySQLのデータベースへ接続して何らかのSQLを実行したい場合は、MySQLAPIの仕様上 query メソッドを呼ぶ必要があります。

results = mysql_connection.query(sql)

一方で、Sybase というデータベースへ接続している場合は、同じ操作をするには sql メソッドを呼ぶ必要があります。

results = sybase_connection.sql(sql)


同じ操作をするのに、データベースの種類毎に変わるメソッドを覚えて使い分けるのは大変ですよね。

そこで、ActiveRecord はデータベースの種類を意識しなくても操作が行えるように、データベース毎の違いを吸収したインターフェース(AbstractAdapterクラス)を提供しています。(まさにアダプターパターンですね!)

AbstractAdapter で定義されているメソッドの1つであり、SQLのSELECT文の結果を返す select_all メソッドを例にRailsのコードを見ていきましょう。

(以下全てのコードは、2019/12/21時点の Rails/ActiveRecord のコードの関係ある部分の抜粋です)

AbstractAdapter は DatabaseStatements を include しており、

module ActiveRecord
  module ConnectionAdapters
    class AbstractAdapter
      include DatabaseStatements
    end
  end
end

参考:GitHubへのリンク

DatabaseStatements の中で select_all メソッドを定義しています。これが統一したインターフェースです。

module ActiveRecord
  module ConnectionAdapters
    module DatabaseStatements

      def select_all(arel, name = nil, binds = [], preparable: nil)
        arel = arel_from_relation(arel)
        sql, binds = to_sql_and_binds(arel, binds)

        if preparable.nil?
          preparable = prepared_statements ? visitor.preparable : false
        end

        if prepared_statements && preparable
          select_prepared(sql, name, binds)
        else
          select(sql, name, binds)
        end
      end
    end
  end
end

参考:GitHubへのリンク

次に、AbstractAdapter を継承して、データベース毎にそのデータベースのAPIを使って select_all メソッドを実装している箇所を見ていきます。MySQLを例に該当箇所を見ていきましょう。

AbstractMysqlAdapter が AbstractAdapter を継承しており、

module ActiveRecord
  module ConnectionAdapters
    class AbstractMysqlAdapter < AbstractAdapter
    end
  end
end

参考:GitHubへのリンク

AbstractMysqlAdapter を継承した Mysql2Adapter で MySQL::DatabaseStatements を include しており、

module ActiveRecord
  module ConnectionAdapters
    class Mysql2Adapter < AbstractMysqlAdapter
      include MySQL::DatabaseStatements
    end
  end
end

参考:GitHubへのリンク

MySQL::DatabaseStatements で select_all メソッドが定義されていました。

module ActiveRecord
  module ConnectionAdapters
    module MySQL
      module DatabaseStatements
        def select_all(*, **)
          result = if ExplainRegistry.collect? && prepared_statements
                     unprepared_statement { super }
                   else
                     super
                   end
          @connection.abandon_results!
          result
        end
      end
    end
  end
end

参考:GitHubへのリンク

おわりに

ここまで読んでいただきありがとうございます。

Adapterパターンは、RailsActiveRecord というかなり身近なところで使われており面白いですね。

Rubyによるデザインパターン の中では、ActiveRecordの例以外にも様々なサンプルコードを用いて説明されており分かりやすかったので、ご興味ある方は是非合わせてご覧ください。

Rubyによるデザインパターン

Rubyによるデザインパターン

次回は、プロキシパターンをまとめます。

来週も頑張ります!

(追記)
プロキシパターンについてまとめました!
是非合わせてご覧ください。
ysk-pro.hatenablog.com