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
それ、どうやったら出来るの?