4

使用するクエリfinder_sqlがPostgreSQLに渡される前に正しく解析されず、データベース構文エラーが発生するという問題があります。

問題を説明するために、ここからサンプルコードを使用しました。

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

人物モデルがないので変更class_nameしただけですが、ここでは関係ありません。"User"

has_many :subscribers, :class_name => "User", :finder_sql =>
'SELECT DISTINCT people.* ' +
'FROM people p, post_subscriptions ps ' +
'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
'ORDER BY p.first_name'

これを使用すると、次のエラーが発生します。

User Load (0.3ms)  SELECT DISTINCT people.* FROM people p, post_subscriptions ps WHERE
ps.post_id = #{id} AND ps.person_id = p.id ORDER BY p.first_name
PGError: ERROR:  Syntaxerror near »{« 
LINE 1: ...ople p, post_subscriptions ps WHERE ps.post_id = #{id} AND p...
                                                         ^

ご覧のとおり#{id}、オブジェクトのIDに置き換えられていないため、PostgreSQLエラーが発生します。

環境

  • レール3.1
  • rvm
  • PostgreSQL 9.1
  • Ubuntu 11.10
  • Ruby 1.9.2p290(2011-07-09リビジョン32553)[x86_64-linux]
4

3 に答える 3

10

私はあなたが実際に探しているのはこれだと思います:

has_many :posts, :finder_sql =>
    proc {"SELECT p.* from posts p join topics t on p.topic_id = t.id where t.id=#{id}"}

Rails 3.1以降、。のようなフィールドを使用するには、文字列の代わりにprocを使用する必要があります#{id}

ここで問題を参照してください:https ://github.com/rails/rails/issues/3920

于 2012-05-16T14:15:24.007 に答える
8

のドキュメント:finder_sqlはひどく不完全であり、サンプルコードは壊れています。あなたが発見したように、これは:

has_many :subscribers, :class_name => "User", :finder_sql =>
  'SELECT DISTINCT people.* ' +
  'FROM people p, post_subscriptions ps ' +
  'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
  'ORDER BY p.first_name'

は機能せず、ActiveRecordソースに基づいて機能しません。ソースを確認すると、次のようなものが表示されます。

def custom_finder_sql
  interpolate(options[:finder_sql])
end

そしてinterpolateこれを行います:

def interpolate(sql, record = nil)
  if sql.respond_to?(:to_proc)
    owner.send(:instance_exec, record, &sql)
  else
    sql
  end
end

したがって、あなた:finder_sqlが単なる文字列(例のように)である場合、それは補間なしでそのまま使用され、SQLが壊れてしまいます。補間が必要な場合はinterpolate、最初のブランチを入力する必要があります。これにより、ラムバのランバ:finder_sqlとラムダ内の二重引用符で囲まれた文字列が機能するように#{id}なります。

has_many :subscribers, :class_name => "User", :finder_sql => ->(record) do
      "SELECT DISTINCT people.* " +
      "FROM people p, post_subscriptions ps " +
      "WHERE ps.post_id = #{id} AND ps.person_id = p.id " +
      "ORDER BY p.first_name"
end

interpolateこれは、instance_exec呼び出しが評価され、問題のオブジェクトのコンテキスト内で文字列を補間するように、内部の最初のブランチに入る必要があります。recordいつになるかわからないnilので、代わりにこれが必要になる場合があります。

has_many :subscribers, :class_name => "User", :finder_sql => ->(record) do
      record = self if(record.nil?)
      "SELECT DISTINCT people.* " +
      "FROM people p, post_subscriptions ps " +
      "WHERE ps.post_id = #{record.id} AND ps.person_id = p.id " +
      "ORDER BY p.first_name"
end

また、ここにいる間は、暗黙的な結合条件ではなく、明示的な結合条件を使用してください。

has_many :subscribers, :class_name => "User", :finder_sql => ->(record) do
      record = self if(record.nil?)
      "SELECT DISTINCT people.* " +
      "FROM people p " +
      "JOIN post_subscriptions ps on p.id = ps.person_id " +
      "WHERE ps.post_id = #{record.id} " +
      "ORDER BY p.first_name"
end

一重引用符/二重引用符について見つけたブログと:finder_sql

http://tamersalama.com/2007/05/17/finder_sql-single-vs-double-quotes/

は古く、Rails3+には適用されないようです。上記の抜粋は3.1からのものですが、表示されている動作は、コードと動作が3.0で変更された可能性があることを示していますが、ドキュメントは更新されていません。

于 2011-12-18T01:37:12.887 に答える
0

私はこれがあなたが聞きたいと思っていることではないことを知っていますが、問題はあなたがActiveRecordにあなたのためにこの仕事をさせなければならないということです。

この問題を本当に解決したいのは、次の3つのファイルを用意することです。

# user.rb
class User < ActiveRecord::Base
  self.table_name = 'people'
  has_many :post_subscriptions
end
# post_subscription.rb
class PostSubscription < ActiveRecord::Base
  belongs_to :user
  belongs_to :post
end
# post.rb
class Post < ActiveRecord::Base
  has_many :post_subscriptions
  has_many :subscribers, :through => :post_subscriptions, :source => :user
end

そうすれば、SQLをまったく書く必要がなくなります。@post.subscribers購読しているユーザーの完全なリストを取得するには、電話するだけです。

于 2011-12-18T00:42:08.013 に答える