4

日付をアクティブなレコードクエリと比較するデータベースにとらわれない方法を見つけようとしています。私は次のクエリを持っています:

UserRole.where("(effective_end_date - effective_start_date) > ?", 900.seconds)

これはMySQLでは正常に機能しますが、生成されるSQLに「interval」構文が含まれていないため、PGでエラーが発生します。コンソールから:

  ←[1m←[36mUserRole Load (2.0ms)←[0m  ←[1mSELECT "user_roles".* FROM "user_roles" WHERE "user_roles"."effective_end_date" IS NULL AND ((effective_end_d
ate - effective_start_date) > '--- 900
...
')←[0m
ActiveRecord::StatementInvalid: PG::Error: ERROR:  invalid input syntax for type interval: "--- 900

to_sql Iオプションを使用してこれを実行すると、次のようになります。

irb(main):001:0> UserRole.where("effective_end_date - effective_start_date) > ?", 900.seconds).to_sql
=> "SELECT \"user_roles\".* FROM \"user_roles\"  WHERE \"user_roles\".\"effective_end_date\" IS NULL AND (effective_end_date - effective_start_date) >
'--- 900\n...\n')"

すべての助けに感謝します。

4

1 に答える 1

3

effective_end_dateと列が実際に日付である場合、日付の最小解像度は1日であり、900秒は86400秒(別名または1日)effective_start_dateよりもかなり小さいため、クエリは無意味です。25*60*60したがって、「日付」列は実際には日時(別名タイムスタンプ)列であると想定します。これが当てはまる場合は、メンテナンス中の混乱を避けるために列の名前を変更することをお勧めします。これはeffectively_starts_ateffectively_ends_at通常のRailsの規則によく一致します。この仮定が無効な場合は、列タイプを変更するか、900の使用を停止する必要があります。

本当の問題に戻りましょう。ActiveRecord::ConnectionAdapters::Quoting#quoteActiveRecordは、次の方法を使用してRuby値をSQL値に変換します。

def quote(value, column = nil)
  # records are quoted as their primary key
  return value.quoted_id if value.respond_to?(:quoted_id)

  case value
  #...
  else
    "'#{quote_string(YAML.dump(value))}'"
  end
end

したがって、プレースホルダーの値として何かを使用しようとし、そのタイプに特定の処理が組み込まれていない場合は、YAML(デフォルトのIMOの奇妙な選択)が得られます。また、900.secondsActiveSupport::Durationオブジェクトであり(何を900.seconds.class言っているかにかかわらず)、そのためcase valueのブランチがないためActiveSupport::Duration900.secondsYAML化されます。

PostgreSQLアダプタは独自のquoteを提供しますActiveRecord::ConnectionAdapters::PostgreSQLAdapter#quoteが、それはどちらも知りませんActiveSupport::Duration。MySQLアダプタquoteも。を認識していませんActiveSupport::Durationquoteこれらのメソッドにモンキーパッチを適用することができます。イニシャライザでこのようなもの:

class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
    # Grab an alias for the standard quote method
    alias :std_quote :quote
    # Bludgeon some sense into things
    def quote(value, column = nil)
        return "interval '#{value.to_i} seconds'" if(value.is_a?(ActiveSupport::Duration))
        std_quote(value, column)
    end
end

ActiveSupport::Durationそのパッチを適用すると、 :を使用したときにPostgreSQLが理解できる間隔が得られます。

> Model.where('a - b > ?', 900.seconds).to_sql
 => "SELECT \"models\".* FROM \"models\"  WHERE (a - b > interval '900 seconds')" 
> Model.where('a - b > ?', 11.days).to_sql
 => "SELECT \"models\".* FROM \"models\"  WHERE (a - b > interval '950400 seconds')"

同様のパッチをMySQLアダプタquote(読者の演習として残されています)に追加すると、次のようになります。

UserRole.where("(effective_end_date - effective_start_date) > ?", 900.seconds)

PostgreSQLとMySQLの両方で正しいことを実行し、コードはそれについて心配する必要はありません。


とは言うものの、さまざまなデータベースで開発して展開することは、サンタクロースを泣かせて、貯蔵用の石炭(おそらくヒ素が混入している可能性があります)を探しに行くという非常に悪い考えです。だからそうしないでください。

一方、データベースにとらわれないソフトウェアを構築しようとしている場合は、楽しい時間を過ごすことができます。データベースの移植性は主に神話であり、データベースにとらわれないソフトウェアとは、プラットフォームが提供するORMおよびデータベースインターフェイスの上に独自の移植性レイヤーを作成することを常に意味します。サポートする予定の各データベースですべてを徹底的にテストする必要があります。誰もがSQL標準にリップサービスを支払いますが、誰もそれを完全にサポートしていないようで、誰もが心配する独自の拡張機能と癖を持っています。ユーティリティメソッドとモンキーパッチを組み合わせた独自のポータビリティレイヤーを作成することになります。

于 2012-12-04T01:55:36.450 に答える