Изменение запроса ActiveRecord, когда точка / период находятся в значении условия - PullRequest
7 голосов
/ 05 марта 2011

Смотрите обновления внизу.Я значительно сузил это.

Я также создал приложение barebones, демонстрирующее эту ошибку: https://github.com/coreyward/bug-demo

И я также создал заявку на официальную ошибкуtracker: https://rails.lighthouseapp.com/projects/8994/tickets/6611-activerecord-query-changing-when-a-dotperiod-is-in-condition-value

Если кто-то может сказать мне, как это сделать, или объяснить, где это происходит в Rails, я был бы очень благодарен.


У меня странное / неожиданное поведение.Это заставило бы меня поверить, что либо есть ошибка (подтверждение того, что эта ошибка была бы идеальным ответом), либо я упускаю то, что находится прямо у меня под носом (или что я не понимаю).

Код

class Gallery < ActiveRecord::Base
  belongs_to :portfolio
  default_scope order(:ordinal)
end

class Portfolio < ActiveRecord::Base
  has_many :galleries
end

# later, in a controller action
scope = Portfolio.includes(:galleries) # eager load galleries
if some_condition
  @portfolio = scope.find_by_domain('domain.com')
else
  @portfolio = scope.find_by_vanity_url('vanity_url')
end
  • У меня есть Portfolios, который может иметь несколько Galleries каждый.
  • galleries имеют ordinal, vanity_urldomain атрибуты.
  • gallery ordinals задаются как целые числа от нуля и выше.Я подтвердил, что это работает, как и ожидалось, проверив Gallery.where(:portfolio_id => 1).map &:ordinal, который возвращает [0,1,2,3,4,5,6], как и ожидалось.
  • Оба vanity_url и domain являются t.string, :null => false столбцами с уникальными индексами.

Проблема

Если some_condition имеет значение true и find_by_domain запущено, возвращенные галереи не соответствуют области по умолчанию.Если выполняется find_by_vanity_url, галереи упорядочиваются в соответствии с областью по умолчанию.Я посмотрел на генерируемые запросы, и они очень отличаются.

Запросы

# find_by_domain SQL: (edited out additional selected columns for brevity)

Portfolio Load (2.5ms)  SELECT DISTINCT `portfolios`.id FROM `portfolios` LEFT OUTER JOIN `galleries` ON `galleries`.`portfolio_id` = `portfolios`.`id` WHERE `portfolios`.`domain` = 'lvh.me' LIMIT 1
Portfolio Load (0.4ms)  SELECT `portfolios`.`id` AS t0_r0, `portfolios`.`vanity_url` AS t0_r2, `portfolios`.`domain` AS t0_r11, `galleries`.`id` AS t1_r0, `galleries`.`portfolio_id` AS t1_r1, `galleries`.`ordinal` AS t1_r6 FROM `portfolios` LEFT OUTER JOIN `galleries` ON `galleries`.`portfolio_id` = `portfolios`.`id` WHERE `portfolios`.`domain` = 'lvh.me' AND `portfolios`.`id` IN (1)

# find_by_vanity_url SQL:

Portfolio Load (0.4ms)  SELECT `portfolios`.* FROM `portfolios` WHERE `portfolios`.`vanity_url` = 'cw' LIMIT 1
Gallery Load (0.3ms)  SELECT `galleries`.* FROM `galleries` WHERE (`galleries`.portfolio_id = 1) ORDER BY ordinal

Таким образом, запрос, сгенерированный find_by_domain, не имеетORDER заявление, следовательно, вещи не упорядочены по желанию.Мой вопрос ...

Почему это происходит?Что побуждает Rails 3 генерировать разные запросы к этим двум столбцам?


Обновление

Это действительно странно.Я рассмотрел и исключил все следующее:

  • Индексы по столбцам
  • Зарезервированные / специальные слова в Rails
  • Столкновение имени столбца между таблицами(т. е. домен находится в обеих таблицах)
  • Тип поля, как в БД, так и в Схеме
  • Параметр «allow null»
  • Отдельная область действия

Я получаю то же поведение, что и find_by_vanity_url с указанием местоположения, телефона и названия;Я получаю то же поведение, что и find_by_domain с электронной почтой.


Другое обновление

Я сузил его до , когда параметр имеет точку (.) Вname :

find_by_something('localhost') # works fine
find_by_something('name_routed_to_127_0_0_1') # works fine
find_by_something('my_computer.local') # fails
find_by_something('lvh.me') #fails

Я недостаточно знаком с внутренностями, чтобы сказать, где сформированный запрос может измениться в зависимости от значения условия WHERE.

Ответы [ 2 ]

8 голосов
/ 25 марта 2011

Разница между двумя стратегиями активной загрузки обсуждается в комментариях здесь

https://github.com/rails/rails/blob/3-0-stable/activerecord/lib/active_record/association_preload.rb

Из документации:

# The second strategy is to use multiple database queries, one for each
# level of association. Since Rails 2.1, this is the default strategy. In
# situations where a table join is necessary (e.g. when the +:conditions+
# option references an association's column), it will fallback to the table
# join strategy.

Я считаю, чтоточка в "foo.bar" заставляет активную запись думать, что вы помещаете условие в таблицу, которая находится за пределами исходной модели, которая предлагает вторую стратегию, обсуждаемую в документации.

Два отдельных запроса выполняются один с моделью Person, а второй - с моделью Item.

 Person.includes(:items).where(:name => 'fubar')

Person Load (0.2ms)  SELECT "people".* FROM "people" WHERE "people"."name" = 'fubar'
Item Load (0.4ms)  SELECT "items".* FROM "items" WHERE ("items".person_id = 1) ORDER BY items.ordinal

Поскольку второй запрос выполняется для модели Item, он наследует область по умолчанию, гдеВы указали order(:ordinal).

Второй запрос, который он пытается загружать с полным набором, запускает модель person и не будет использовать область ассоциации по умолчанию.

 Person.includes(:items).where(:name => 'foo.bar')

Person Load (0.4ms)  SELECT "people"."id" AS t0_r0, "people"."name" AS t0_r1, 
"people"."created_at" AS t0_r2, "people"."updated_at" AS t0_r3, "items"."id" AS t1_r0, 
"items"."person_id" AS t1_r1, "items"."name" AS t1_r2, "items"."ordinal" AS t1_r3, 
"items"."created_at" AS t1_r4, "items"."updated_at" AS t1_r5 FROM "people" LEFT OUTER JOIN 
"items" ON "items"."person_id" = "people"."id" WHERE "people"."name" = 'foo.bar'

Itэто немного глючит, но я вижу, как это будет с несколькими различными способами представления списка опций, чтобы быть уверенным, что вы поймаете все из них, будет сканировать выполненные условия "ГДЕ"для точки и использовать вторую стратегию, и они оставляют это таким образом, потому что обе стратегии являются функциональными.Я бы на самом деле зашёл так далеко, что сказал, что аномальное поведение в первом запросе, а не во втором.Если вы хотите, чтобы порядок для этого запроса сохранялся, я рекомендую одно из следующего:

1) Если вы хотите, чтобы у ассоциации был порядок, когда он вызывается, то вы можете указать это с помощью ассоциации,Как ни странно, это есть в документации, но я не смог заставить его работать.

Источник: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many

class Person < ActiveRecord::Base
  has_many :items, :order => 'items.ordinal'
end

2) Другим способом было бы просто добавить оператор заказак рассматриваемому запросу.

Person.includes(:items).where(:name => 'foo.bar').order('items.ordinal')

3) В тех же строках будет задана именованная область действия

class Person < ActiveRecord::Base
  has_many :items
  named_scope :with_items, includes(:items).order('items.ordinal')
end

И для вызова этого:

Person.with_items.where(:name => 'foo.bar')
5 голосов
/ 27 января 2012

Это выпуск № 950 в проекте Rails GitHub .Похоже, что неявная активная загрузка (которая и является причиной этой ошибки) устарела в Rails 3.2 и удалена в Rails 4.0.Вместо этого вы будете явно указывать Rails, что вам нужно JOIN для предложения WHERE - например:

Post.includes(:comments).where("comments.title = 'lol'").references(:comments)

Если вам крайне необходимо исправить эту ошибку в Rails 3.1. *, Вы можете взломать ActiveRecord::Relation#tables_in_string, чтобыменее агрессивен в сопоставлении имен таблиц.Я создал Суть моего (не элегантного и медленного) решения .Это разница:

diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 30f1824..d7335f3 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -528,7 +528,13 @@ module ActiveRecord
       return [] if string.blank?
       # always convert table names to downcase as in Oracle quoted table names are in uppercase
       # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
-      string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
+      candidates = string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
+      candidates.reject do |t|
+        s = string.partition(t).first
+        s.chop! if s.last =~ /['"]/
+        s.reverse!
+        s =~ /^\s*=/
+      end
     end
   end
 end

Это работает только для моего очень конкретного случая (Постгрес и условие равенства), но, возможно, вы можете изменить его, чтобы он работал на вас.

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