finder_sql不使用Rails解析字符串

Ste*_*fan 3 postgresql activerecord ruby-on-rails finder-sql ruby-on-rails-3

我有一个问题,我使用的查询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'
Run Code Online (Sandbox Code Playgroud)

当我使用它时,我收到以下错误:

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...
                                                         ^
Run Code Online (Sandbox Code Playgroud)

正如您所看到的那样#{id},不会替换对象的id,然后会引发PostgreSQL错误.

环境

  • Rails 3.1
  • RVM
  • PostgreSQL 9.1
  • Ubuntu 11.10
  • Ruby 1.9.2p290(2011-07-09修订版32553)[x86_64-linux]

ide*_*der 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}"}
Run Code Online (Sandbox Code Playgroud)

从Rails 3.1开始,你必须使用proc而不是字符串来使用像#{id}.

请在此处查看问题:https://github.com/rails/rails/issues/3920


mu *_*ort 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'
Run Code Online (Sandbox Code Playgroud)

将无法工作,并且,基于ActiveRecord源,无法工作.如果你查看源代码,你会看到如下内容:

def custom_finder_sql
  interpolate(options[:finder_sql])
end
Run Code Online (Sandbox Code Playgroud)

然后interpolate这样做:

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

因此,如果您:finder_sql只是一个字符串(例如在示例中),那么它将按原样使用,根本不进行插值,最终会出现损坏的SQL.如果你想要插值,那么你必须interpolate进入第一个分支,所以你需要lamba for :finder_sql和lambda中的双引号字符串,这样#{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
Run Code Online (Sandbox Code Playgroud)

这应该进入内部的第一个分支,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
Run Code Online (Sandbox Code Playgroud)

虽然我们在这里,但请使用显式连接条件而不是隐式连接条件:

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
Run Code Online (Sandbox Code Playgroud)

您发现有关单/双引号的博客:finder_sql:

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

已过时,似乎不适用于Rails 3+.上面的摘录来自3.1,但您看到的行为表明代码和行为可能在3.0中已更改,但文档未更新.