finder_sql不使用Rails解析字符串
我有一个问题,我用于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]
我认为你真正想要的是:
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
以下文档: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源,无法工作。 如果你查看源代码,你会看到如下内容 :
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中的双引号字符串,以便#{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中已更改,但文档未更新。
我知道这不是你希望听到的,但问题是你应该让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
即可获得订阅用户的完整列表。