连接两次到同一个表时,Rails 3.1范围被忽略

jer*_*ere 5 sql activerecord scope ruby-on-rails ruby-on-rails-3.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
  )}
  ...
Run Code Online (Sandbox Code Playgroud)

使用此代码,我可以调用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
  )}
Run Code Online (Sandbox Code Playgroud)

此解决方案的问题在于别名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
Run Code Online (Sandbox Code Playgroud)

运行范围和链接范围的结果:

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"
Run Code Online (Sandbox Code Playgroud)

当我链接查询时,我想得到两个范围的结果的交集.我希望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
Run Code Online (Sandbox Code Playgroud)

我怎么做?

Phi*_*rom 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
Run Code Online (Sandbox Code Playgroud)

并在控制台中运行它......并且查询看起来很好......当然,它不会返回任何结果,因为条件不可能满足,但所有内容都按照我的预期合并/链接。

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

这是 Rails 3.2.8 的情况...