当点/周期处于条件值时,ActiveRecord查询会更改

时间:2011-03-04 21:04:21

标签: ruby-on-rails-3 activerecord

查看底部的更新。我把它缩小了很多。

我还创建了一个准确显示此错误的准系统应用程序:https://github.com/coreyward/bug-demo

我还在官方跟踪器中创建了一张错误票: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
  • galleriesordinalvanity_urldomain属性。
  • gallery ordinals设置为从零开始的整数。我已通过检查Gallery.where(:portfolio_id => 1).map &:ordinal按预期确认这是有效的,[0,1,2,3,4,5,6]按预期返回vanity_url
  • domaint.string, :null => false都是some_condition列,具有唯一索引。

问题

如果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生成的查询没有find_by_vanity_url语句,因此没有按需要排序。我的问题是......

为什么会这样?是什么促使Rails 3为这两列生成不同的查询?


更新

这真的很奇怪。我已经考虑并排除了以下所有内容:

  • 列上的索引
  • Rails中的保留/特殊字词
  • 表之间的列名冲突(即两个表上的域)
  • 字段类型,包括DB和Schema
  • “允许空值”设置
  • 单独的范围

我得到与find_by_domain相同的行为,包括位置,电话和标题;我通过电子邮件获得与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

我对内部人员不熟悉,无法根据{{1}}条件的值来说明所形成的查询的位置。

2 个答案:

答案 0 :(得分:8)

在这里的评论中讨论了两种急切加载策略之间的区别

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.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'

这是一个小小的错误,但我可以看到它可以通过几种不同的方式呈现一个选项列表,确保你抓住所有这些方法的方法是扫描已完成的点的“WHERE”条件并使用第二种策略,并且它们保持这种方式,因为这两种策略都是有效的。我实际上会说,异常行为是在第一个查询中,而不是第二个查询中。如果您希望此查询的排序仍然存在,我建议您执行以下操作之一:

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)另一种方法是将order语句添加到相关查询中。

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')

答案 1 :(得分:5)

这是issue #950 on the Rails GitHub project。看起来隐含的急切加载(导致此错误的原因)已在Rails 3.2中弃用并在Rails 4.0中删除。相反,您将明确告诉Rails您需要WHERE子句的JOIN - 例如:

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

如果您迫切需要在Rails 3.1。*中修复此错误,您可以破解ActiveRecord::Relation#tables_in_string在匹配表名时不那么激进。我创建了a Gist of my (inelegant and slow) solution。这是差异:

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

它只适用于我的特定情况(Postgres和一个平等条件),但也许你可以改变它以适合你。