【问题标题】:finder_sql does not parse string with Railsfinder_sql 不使用 Rails 解析字符串
【发布时间】:2011-12-17 14:23:16
【问题描述】:

我的问题是我用于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 错误。

环境

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

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 postgresql activerecord finder-sql


    【解决方案1】:

    我认为您真正要寻找的是:

    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

    【讨论】:

    • @stefan 请标记正确的解决方案,这个对我有用:)
    【解决方案2】:

    :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 中发生了变化,但文档没有更新。

    【讨论】:

      【解决方案3】:

      我知道这不是您希望听到的,但问题是您应该让 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 即可获取订阅用户的完整列表。

      【讨论】:

      • 有点。真正的问题是 AR 的文档不完整,文档中的示例被破坏(或者可能只是过时且未维护)。使用标准 Rails 是首选方式,但并非总是可行,如果您需要使用 :finder_sql,那么几乎可以肯定它必须是 lambda 而不是简单的字符串。
      猜你喜欢
      • 1970-01-01
      • 2013-08-02
      • 2016-10-12
      • 1970-01-01
      • 1970-01-01
      • 2012-08-06
      • 2011-10-23
      • 2011-10-14
      • 2011-07-14
      相关资源
      最近更新 更多