ActiveRecord の scope で任意のテーブルのカラム対して比較を行う
背景
外部システムへの連携を行ったことを記録する ext_synchronizations テーブルが存在する
そのテーブルには、最後に同期を行った日時として last_synced_at というカラムを持つ
連携対象のテーブルにおいて、その last_synced_at 以降に発生した変更を同期対象する
たとえば、users テーブルを連携対象とする場合、updated_at が last_synced_at 以降のものがあれば、それはまだ同期されていない変更を含む状態と認識され、それを同期対象とする
検索条件は scope として利用できるようにしたい
問題
連携の対象は users テーブル以外にも今後増えていくことが予想される
通常の Query Interface で扱う where() メソッドでは、文字列としてクエリを組み立てることになる
code:example_1(ruby)
# 最後の同期後に存在するまだ同期されていない変更を抽出する scope
scope :unsynced_after_updated, -> {
where('last_synced_at < hoges.updated_at') # <- hoges テーブル以外に対応できない
}
そうすると比較対象のテーブルを任意のものに変更することができない
引数でテーブル名を受け取ってそれを文字列として組み立てる方法もあるが、脆弱性につながるためできるだけ避けたい
code: example_2(ruby)
# 最後の同期後に存在するまだ同期されていない変更を抽出する scope
scope :unsynced_after_updated, ->(table) {
}
対応
Arel を使ってクエリを組み立てる
code:example_3(ruby)
# 最後の同期後に存在するまだ同期されていない変更を抽出する scope
scope :unsynced_after_updated, ->(table_name) {
target_table = Arel::Table.new(table_name)
}
Arel であればクエリインジェクションの問題は発生せず、任意のテーブルにも柔軟に対応可能となる。
(個人的にはあまり積極的には利用するべきではないと思うが...)
参考