2009-05-19 11 views
28
create_table :categories_posts, :id => false do |t| 
    t.column :category_id, :integer, :null => false 
    t.column :post_id, :integer, :null => false 
end 

Iは、対応カテゴリテーブルを参照し、ポストテーブル列を含む結合テーブルを(上記のように)を有します。 複合キーcategory_id、post_idcategories_posts join tableに一意の制約を適用したいと考えました。しかし、Railsはこれをサポートしていません(私は信じています)。結合テーブルで、Railsが複合キーを持たない場合の最善の回避策は何ですか?

category_idとpost_idの同じ組み合わせを持つデータの重複行を避けるには、Railsで複合キーが存在しない場合の最善の回避策?ここ

私の仮定は以下のとおりです。

  1. デフォルトの自動番号欄 (ID:整数) このような状況で私のデータを保護するために何もしないでしょう。
  2. のActiveScaffoldは ソリューションを提供することができるが、私はそれが よりエレガントな答えがある場合は特に、単純にこの単一 機能のための私の プロジェクトに含めるやり過ぎだ かはわかりません。

答えて

33

両方の列を含む一意のインデックスを追加します。これにより、重複したcategory_id/post_idのペアを含むレコードを挿入できなくなります。あなたは、DBMSが重複をさせないことを確実にするために、データベース・レベルで宣言したユニークな複合インデックスを持つ必要があります

1):私はレールで、この問題を持っているとき、私は次の両方を実装し

add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post' 
+0

ありがとうございました。さまざまなブログの記事を読むことから、私は複合インデックスが不可能であり、この構文は存在しないと考えました。 –

+0

これは、ユーザーが重複するrecを入力しようとすると、UIの操作性が低下します。 –

+1

@Larry - 答えの検証ロジックを使用して、コンポジットインデックスのRails構文と組み合わせることはできませんでしたか? –

5

レコードが作成されます。私はあなたに他のとフィールドのいずれかの一意性を検証するために簡単に見つけることができると思い

validates_each :category_id, :on => :create do |record, attr, value| 
    c = value; p = record.post_id 
    if c && p && # If no values, then that problem 
       # will be caught by another validator 
    CategoryPost.find_by_category_id_and_post_id(c, p) 
    record.errors.add :base, 'This post already has this category' 
    end 
end 
+0

tvanoffsonの答えによれば、複合索引は確かに可能であり、その構文を提供しています。その場合、データベースレベルで複合インデックスを宣言することをお勧めします(良い提案は必要ありません)。私の前提は、複合インデックスはRailsでは不可能でしたが、おそらく私は間違っていました。 –

+2

Tvanfossonのソリューションは、私の#1と同じです。インデックスは、RailsやRubyレベルではなく、dbレベルで宣言されています。 dupがサブミットされると、Railsは「SQLエラー」を報告することしかできません。そのため、モデルでも(Railsレベルで)検証する必要があります。 –

+0

「データベースレベルで」と言ったとき、Railsでadd_indexメソッドを使用せずに、データベースで作成するつもりだと思っていました。私の質問の理由は複合索引が可能であるかどうかわからなかったからです。しかし、なぜtvanoffsonの答えから構文に検証ロジックを追加できないのかわかりません。申し訳ありませんが、あなたが意図したものです。しかし、私はあなたの答えからそれを理解していませんでした。 –

9

2)は、Railsのモデルに検証を追加、ちょうど上記よりも滑らかなエラーMSGのを提供するために、スコープ:

API FROM:

validates_uniqueness_of(*attr_names) 

は、指定された属性の値は、システム全体で一意であるかどうかを検証します。 1人のユーザーしか「davidhh」という名前にすることができないようにするのに便利です。

class Person < ActiveRecord::Base 
    validates_uniqueness_of :user_name, :scope => :account_id 
    end 

また、指定された属性の値が複数のスコープパラメータに基づいて一意であるかどうかを検証することもできます。たとえば、教師が特定のクラスについて学期ごとにスケジュールに従うことができるようにすることができます。

class TeacherSchedule < ActiveRecord::Base 
    validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id] 
    end 

レコードが作成されると、チェックがレコードが指定された属性(列へのマッピング)のために指定された値で、データベースに存在しないことを確認するために行われます。レコードが更新されると、同じチェックが行われますが、レコード自体は無視されます。

設定オプション:

* message - Specifies a custom error message (default is: "has already been taken") 
* scope - One or more columns by which to limit the scope of the uniquness constraint. 
* case_sensitive - Looks for an exact match. Ignored by non-text columns (true by default). 
* allow_nil - If set to true, skips this validation if the attribute is null (default is: false) 
* if - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value. 
+0

これは素晴らしいことです。ありがとう。 –

+0

として@izap答えた:検証は、SELECTとINSERT/UPDATEクエリ間の潜在的な競合状態のため、絶対確実ではありません。 –

1

ソリューションは、モデル内のインデックスと検証の両方を追加することができます。 ADD_INDEX:categories_posts、[:CATEGORY_ID、:post_idの]:ユニーク=>真

とモデルで: validates_uniqueness_of:CATEGORY_ID、:範囲=> [:CATEGORY_IDあなたが持っている、移行中のSO

、:post_idの] validates_uniqueness_of:post_idの、:範囲=> [:CATEGORY_ID、:post_idの]

8

それは非常に難しいは "右" のアプローチをお勧めします。

1)実用的なアプローチ

使用のバリデータと独自の複合インデックスを追加しないでください。これにより、UIで素晴らしいメッセージが表示され、うまくいきます。

あなたはまた、検索をスピードアップするためにテーブルを結合して二つの別々のインデックスを追加することができます
class CategoryPost < ActiveRecord::Base 
    belongs_to :category 
    belongs_to :post 

    validates_uniqueness_of :category_id, :scope => :post_id, :message => "can only have one post assigned" 
end 

add_index :categories_posts, :category_id 
add_index :categories_posts, :post_id 

(ブックによるRailsの3ウェイ)注意検証はので誰にでもできるではありませんしてくださいSELECTクエリとINSERT/UPDATEクエリの間に潜在的な競合状態がある可能性があります。重複するレコードがないことを絶対に確認する必要がある場合は、一意の制約を使用することをお勧めします。

2)我々はデータベースレベルで制約を載せていきたいと思います。このアプローチでは、防弾アプローチ

。したがって、複合インデックスを作成することを意味します。

add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post' 

大きな利点は、データベースの整合性が非常に優れていることです。複合インデックスの作成には、列の順序が重要であることに注意してください。

インデックスに先行する列としてあまり選択的でない列を置き、最後に最も選択的な列を配置すると、先頭でないインデックス列の条件を持つ他のクエリでもINDEX SKIP SCANを利用できます。それらを利用するためにもう1つのインデックスを追加する必要があるかもしれませんが、これはデータベースに依存しています。両方

一つの

3)組み合わせは、両方の組み合わせについて読むことができますが、私はナンバーワンだけを好きに傾向があります。

+0

モデルとデータのインテグリティを提案するので、これは最善の解決策と考えるべきです。 –

+0

私は1番だけを使用することについての推奨には同意しないが、すべての長所と短所が言及されているので、これはまだアップヴォートに値する。 – kronn

関連する問題