我还创建了一个准备这个bug的准系统应用程序: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
>我有投资组合,每个可以有多个画廊.
>图库具有序数,vanity_url和域属性.
>画廊序数从零开始设置为整数.我已经确认这可以通过检查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中的保留/特殊单词
>表之间的列名冲突(即两个表上的域)
>字段类型,包括DB和Schema
>“允许空”设置
>单独的范围
我得到与find_by_vanity_url相同的行为,包括位置,电话和标题;我通过电子邮件获得与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条件的值来确定查询形成的位置.
解决方法
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模型运行第二个查询,所以它会继承您指定顺序(: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
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')