find_by_sql_with_deleted

acts_as_paranoidはレコードを論理削除してくれるRailsプラグインで、findの条件節に自動的にdeleted_at IS NULLとか付けてくれるわけなんだけど、どうもfind_by_sqlはその範囲外らしい。


by_sqlって付けてるんだからそのSQLをそのまま実行したいという気持ちは分からんでもないけど、そこだけ自力でdeleted_at IS NULLとかつけるのも気持ち悪いので無理矢理作ってみた。


とりあえずCaboose::Acts::ParanoidのClassMethodsに追加したらそれなりに動いてる気がする。

def find_by_sql(sql)
  ar_tables = Object.subclasses_of(ActiveRecord::Base).inject({}) do |hash, ar|
    hash[ar.table_name] = ar
    hash
  end
  froms =
    if (sql.is_a?(String) ? sql : sql.first) =~ /from\s+(\w+(\s+as\s+\w+)?(\s*,\s*\w+(\s+as\s+\w+)?)*)/im
      $1.split(',').map{|s| s.split(/\s+as\s+/im).map{|t| t.strip}}
    end
  additional_condition =
    froms.map do |from|
      tname, aname = from[1] ? from : [from.first, from.first]
      if ar_tables[tname].paranoid?
        "#{aname}.deleted_at IS NULL"
      end
    end.uniq.compact.join ' AND '
  additional_condition += ' AND' unless additional_condition.empty?

  sql =
    case sql
    when String
      sql.sub /\bwhere\b/i, "\\0 #{additional_condition}"
    when Array
      [sql[0].sub(/\bwhere\b/i, "\\0 #{additional_condition}"), *sql[1..-1]]
    end
  connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
end

まともに確認してないので、もしかしたらなんか変な副作用あるかも。
あと、テーブル名からAR名引くために無理やりObject#subclasses_ofとかしてるので重そう。なんかいいやり方があったら教えてください。