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