Rails3.1作用域在两次连接到同一个表时被忽略



在使用rails 3.1.4的不同作用域中两次连接到同一个表时,我遇到了一个问题。其中一个作用域被完全忽略,包括join和where子句。此删除操作不会出现错误或通知。

这是一个导致问题的简化示例:

Task是一个标准rails模型,与模型SavedOutput具有多态关系。SavedOuput用作缓存,用于存储复杂方法的结果。

任务模型如下所示:

class Task < ActiveRecord::Base
has_many :saved_outputs
scope :saved_lates, lambda { joins(:saved_outputs).where(
"saved_outputs.method" => "late?",
"saved_outputs.output" => true
)}
scope :saved_completes, lambda { joins(:saved_outputs).where(
"saved_outputs.method" => "complete?",
"saved_outputs.output" => true
)}
...

使用此代码,我可以调用Task.saved_lates,而不是调用类似Task.all.select(&:late?)的东西,假设缓存的数据是当前的。

问题是调用Task.saved_lates.saved_completes不起作用。我相信rails的重复查询检测会起作用,并删除第二个作用域。即使没有发生这种情况,查询仍然会失败,因为如果不在MYSQL中使用别名,就不能两次联接到同一个表。

我有一个手动编写的联接和表别名的部分解决方案。

scope :saved_lates, lambda { joins("INNER JOIN saved_outputs AS so1 ON so1.object_type='Task' AND so1.object_id=tasks.id").where(
"so1.method" => "late?",
"so1.output" => true
)}
scope :saved_completes, lambda { joins(INNER JOIN saved_outputs AS so2 ON so2.object_type='Task' AND so2.object_id=tasks.id).where(
"so2.method" => "complete?",
"so2.output" => true
)}

此解决方案的问题在于,别名so1需要在整个项目中是唯一的。考虑到SavedOutput模型保存了许多不同模型的输出,我需要使用全局唯一id或唯一哈希系统来标记别名。

有没有一种解决方案可以无声地从查询中删除作用域
有没有办法强制rails在每个标准联接上创建一个唯一的表别名
使用带有关联符号的联接作用域作为参数是不是不好的做法?


以下是我正在查看的完整示例:

class ScopeTest < ActiveRecord::Migration
def change
create_table :foos do |t| 
t.string :name
t.boolean :active, :default => true
end 
create_table :bars do |t| 
t.integer :foo_id
t.boolean :method_value
end 
end
end
class Foo < ActiveRecord::Base
has_many :bars
scope :ones, lambda { joins(:bars).where("bars.method_value" => true) }
scope :zeroes, lambda { joins(:bars).where("bars.method_value" => false) }
end

运行作用域和链接作用域的结果:

irb(main):022:0> Foo.ones.to_sql
=> "SELECT `foos`.* FROM `foos` INNER JOIN `bars` ON `bars`.`foo_id` = `foos`.`id` WHERE `bars`.`method_value` = 1"
irb(main):023:0> Foo.zeroes.to_sql
=> "SELECT `foos`.* FROM `foos` INNER JOIN `bars` ON `bars`.`foo_id` = `foos`.`id` WHERE `bars`.`method_value` = 0"
irb(main):024:0> Foo.ones.zeroes.to_sql
=> "SELECT `foos`.* FROM `foos` INNER JOIN `bars` ON `bars`.`foo_id` = `foos`.`id` WHERE `bars`.`method_value` = 0"
irb(main):025:0> Foo.zeroes.ones.to_sql
=> "SELECT `foos`.* FROM `foos` INNER JOIN `bars` ON `bars`.`foo_id` = `foos`.`id` WHERE `bars`.`method_value` = 1"

当我链接查询时,我希望获得来自两个作用域的结果的交集。我希望sql具有与以下相同的含义:

SELECT `foos`.* from `foos` 
INNER JOIN `bars` AS `bars1` ON `bars1`.`foo_id` = `foos`.`id` 
INNER JOIN `bars` AS `bars2` ON `bars2`.`foo_id` = `foos`.`id`
WHERE `bars1`.`method_value` = 1 AND `bars2`.`method_value` = 0

我该怎么做?

你怎么知道它不起作用?SQL说发生了什么?我刚刚模仿了一个模特。。。

class Foo < ActiveRecord::Base
has_many :bars
scope :one, lambda { joins(:bars).where('x = 1') }
scope :two, lambda { joins(:bars).where('x = 2') }
end

并在控制台中运行。。。查询看起来很好。。。当然,它不会返回任何结果,因为条件不可能满足,但正如我所期望的那样,所有的东西都被合并/链接了。

Foo.one.two.to_sql
=> "SELECT `foos`.* FROM `foos` 
INNER JOIN `bars` ON `bars`.`foo_id` = `foos`.`id` 
WHERE (x = 1) AND (x = 2)"

这是Rails 3.2.8…

我相信您正在尝试获得saved_lates,而在这些情况下,您希望获得saved_completes

相反(这个不起作用,我认为下面的解释很清楚):Task.saved_lates.saved_completes你基本上能做什么:

tasks_saved_lates = Task.saved_lates
tasks_saved_completes = Task.saved_completes
tasks_saved_lates.merge(tasks_saved_completes) # this basically get intersection between the results

您可以在此处查看merge