3

我有問題,我使用的查詢finder_sql未被正確解析之前,它被交給PostgreSQL導致數據庫語法錯誤。finder_sql不解析字符串與Rails

爲了說明我剛纔使用的示例代碼問題就在這裏:

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

我只改class_name到,因爲我沒有一個人模型,但是這並不重要位置。

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}不與隨後引發的PostgreSQL錯誤的對象的ID所取代。

環境

  • 滑軌3.1
  • RVM
  • 的PostgreSQL 9.1
  • 的Ubuntu 11.10
  • 紅寶石1.9.2p290(2011-07-09的修訂32553)[x86_64的Linux的]

回答

0

我知道這不是你希望聽到的,但問題是,喲你應該讓ActiveRecord爲你做這項工作。

你真的想解決這個問題,什麼是有這三個文件:

# 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以獲取訂閱用戶的完整列表。

+0

的排序。真正的問題是,AR的文檔不完整,文檔中的示例已被破壞(或者可能過時並且無法維護)。使用標準的Rails東西是首選的方法,但並非總是可行,如果你需要使用':finder_sql',那麼它幾乎肯定必須是lambda,而不是簡單的字符串。 – 2011-12-18 01:45:31

7

: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源,不能工作。如果你檢查源,你會see things like this

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/

是過時,似乎並不適用於Rails的3+。上面的摘錄來自3.1,但您看到的行爲表明,代碼和行爲可能在3.0中更改,但文檔未更新。

9

我想你實際上在尋找的是:

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

鐵軌3.1的,你必須使用一個進程,而不是一個字符串的使用領域,如#{id}

看到問題在這裏:https://github.com/rails/rails/issues/3920

+0

@stefan plz標記正確的解決方案,這一個爲我工作:) – Ross 2012-10-14 14:27:19