Madogiwa Blog

主に技術系の学習メモに使っていきます。

ActiveRecord::QueryMethods#whereで発行されるwhereの部分だけのSQLを取得するMEMO

はじめに

下記のような形でActiveRecordが発行するSQLからWHERE句の部分のSQLだけを取得したいケースがあり、色々やり方とか調べたのでMEMO✍

やりたかったこと

今回やりたかったのは、下記のようなscopeを定義していたときにto_sqlするとSQL全体を取得することが出来ますが、

class Book < ApplicationReacord
  scope :viewable, ->(now = Time.current) { where(published_at: now..) }
end

Book.viewable.to_sql
#=> select * from books where published_at > '2020-05-23 15:24:34'

そうではなくWHERE句のpublished_at > '2020-05-23 15:24:34'部分だけ取得したいというものです。

where部分だけのSQLを取得する方法

下記のような感じで取れるみたいです👀(⚠nodocなので動作保証されてないですが・・・!)

Book.viewable.values[:where].ast.to_sql
#=> published_at > '2020-05-23 15:24:34'

values[:where]ActiveRecord::Relation::WhereClauseのオブジェクトを取得することができて、オブジェクトに対してastメソッドを呼び出すことによってArel::Nodes系のオブジェクトを取得できるようです👀

そして取得したオブジェクトにto_sqlを呼び出すことによってそのNodeの部分だけのSQLを取得できるような形で動いてそうな気がしました・・・!

WHEREも含めて取得したいときは下記のようにするととれるようです👀(⚠こちらもnodocなので動作保証されてないです。)

Book.viewable.arel.where_sql
#=> WHERE published_at > '2020-05-23 15:24:34'

ですが、Arel::Nodes::Node#to_sqlはコメントにも無くなる可能性が明記されてるので、素直にSQLをparseした方が良さそうな気もしました😓

      # FIXME: this method should go away.  I don't like people calling
      # to_sql on non-head nodes.  This forces us to walk the AST until we
      # can find a node that has a "relation" member.
      #
      # Maybe we should just use `Table.engine`?  :'(
      def to_sql(engine = Table.engine)
        collector = Arel::Collectors::SQLString.new
        collector = engine.connection.visitor.accept self, collector
        collector.value
      end

rails/node.rb at b1f6d8c8d8ad3e2e5b96e95b455c70f2c895ce14 · rails/rails · GitHub

参考

stackoverflow.com