5

Rails 3.1.4 を使用して別のスコープで同じテーブルに 2 回参加すると、問題が発生します。join 句と where 句の両方を含め、スコープの 1 つが完全に無視されます。この削除は、エラーや通知なしで行われます。

これは、問題の原因の簡単な例です。

Task は、モデル SavedOutput とのポリモーフィックな関係を持つ標準的な Rails モデルです。SavedOutput は、複雑なメソッドの結果を格納するためのキャッシュとして使用されます。

タスク モデルは次のようになります。

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 の重複したクエリの検出が開始され、2 番目のスコープが削除されると考えています。それが起こらなかったとしても、MYSQL でエイリアスを使用しないと同じテーブルに 2 回参加できないため、クエリは失敗します。

手動で作成された結合とテーブル エイリアスを使用した部分的なソリューションがあります。

  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 または一意のハッシュ システムを使用する必要があります。

クエリからスコープをサイレントに削除する解決策はありますか?
すべての標準結合でレールに一意のテーブルエイリアスを作成させる方法はありますか?
関連シンボルを引数として joins スコープを使用するのは悪い習慣ですか?


ここに私が見ているものの完全な例があります:

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"

クエリをチェーンするとき、2 つのスコープからの結果の共通部分を取得したいと考えています。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

それ、どうやったら出来るの?

4

1 に答える 1

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

于 2012-11-30T22:43:49.030 に答える