finder_sql не разбирает строку с Rails - PullRequest
3 голосов
/ 17 декабря 2011

У меня проблема в том, что запрос, который я использую для 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} не заменяется идентификатором объекта, который затем вызывает PostgreSQLошибка.

Среда

  • Rails 3.1
  • rvm
  • PostgreSQL 9.1
  • Ubuntu 11.10
  • Ruby1.9.2p290 (редакция 2011-07-09, 32553) [x86_64-linux]

Ответы [ 3 ]

10 голосов
/ 16 мая 2012

Я думаю, что вы на самом деле ищете это:

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

7 голосов
/ 18 декабря 2011

Документация для :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 для ввода первой ветви, так что вы захотите лямбду для :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, но документация не была обновлена.

0 голосов
/ 18 декабря 2011

Я знаю, что это не то, что вы надеетесь услышать, но проблема в том, что вы должны позволить 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, чтобы получить полный список подписавшихся пользователей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...