我遇到的问题是我用于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错误。
环境
答案 0 :(得分: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}
这样的字段。
答案 1 :(得分: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
进入第一个分支,这样你就需要一个lamba用于:finder_sql
和一个双引号的字符串在lambda中,以便{{1}将工作:
#{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
内的第一个分支,以便评估instance_exec
调用,并在相关对象的上下文中插入字符串。我不确定interpolate
何时不会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
的博客:
http://tamersalama.com/2007/05/17/finder_sql-single-vs-double-quotes/
已过期,似乎不适用于Rails 3+。上面的摘录来自3.1,但您看到的行为表明代码和行为可能在3.0中发生了变化,但文档未更新。
答案 2 :(得分: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
即可获得订阅用户的完整列表。