32
create_table :categories_posts, :id => false do |t|
  t.column :category_id, :integer, :null => false
  t.column :post_id, :integer, :null => false
end

対応するカテゴリテーブルと投稿テーブルを参照する列を持つ結合テーブル(上記のとおり)があります。カテゴリ_posts結合テーブルの複合キーcategory_id、post_idに一意性制約を適用したかったのです。しかし、Railsはこれをサポートしていません(私は信じています)。

category_idとpost_idの同じ組み合わせを持つデータ内の重複行の可能性を回避するために、Railsに複合キーがない場合の最善の回避策は何ですか?

ここでの私の仮定は次のとおりです。

  1. デフォルトの自動番号列(id:integer)は、この状況でデータを保護するために何もしません。
  2. ActiveScaffoldは解決策を提供するかもしれませんが、特によりエレガントな答えがある場合は、この単一の機能のためだけにそれをプロジェクトに含めるのがやり過ぎかどうかはわかりません。
4

5 に答える 5

41

両方の列を含む一意のインデックスを追加します。これにより、重複する category_id/post_id ペアを含むレコードを挿入できなくなります。

add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
于 2009-05-19T04:59:23.800 に答える
18

「正しい」アプローチを推奨することは非常に困難です。

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

結合テーブルに 2 つの個別のインデックスを追加して、検索を高速化することもできます。

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

(書籍Rails 3 Wayによると) SELECT クエリと INSERT/UPDATE クエリの間で競合状態が発生する可能性があるため、検証は完全ではないことに注意してください。重複レコードがないことを絶対に確認する必要がある場合は、一意の制約を使用することをお勧めします。

2)防弾アプローチ

このアプローチでは、データベース レベルに制約を設定します。したがって、複合インデックスを作成することを意味します。

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

大きな利点は、データベースの整合性が優れていることです。欠点は、ユーザーへのエラー報告があまり役に立たないことです。複合インデックスの作成では、列の順序が重要であることに注意してください。

選択性の低い列をインデックスの先頭の列として配置し、最も選択的な列を最後に配置すると、先頭以外のインデックス列を条件とする他のクエリでも INDEX SKIP SCAN を利用できます。それらを利用するには、もう 1 つのインデックスを追加する必要がある場合がありますが、これはデータベースに大きく依存します。

3)両方の組み合わせ

両方の組み合わせについて読むことができますが、私はナンバーワンだけが好きです.

于 2011-08-16T08:41:54.500 に答える
10

一方のフィールドの一意性を、もう一方のフィールドをスコープとして検証する方が簡単だと思います。

API から:

validates_uniqueness_of(*attr_names)

指定された属性の値がシステム全体で一意かどうかを検証します。"davidhh" という名前のユーザーを 1 人だけにする場合に便利です。

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

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

  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.
于 2009-05-19T06:55:21.450 に答える
6

Railsでこの問題が発生した場合、次の両方を実装します。

1) データベース レベルで一意の複合インデックスを宣言して、dbms で重複レコードが作成されないようにする必要があります。

2) 上記よりもスムーズなエラー メッセージを提供するには、Rails モデルに検証を追加します。

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
于 2009-05-19T05:00:39.180 に答える
1

解決策は、モデルにインデックスと検証の両方を追加することです。

したがって、移行では次のようになります: add_index :categories_posts, [:category_id, :post_id], :unique => true

モデルでは: validates_uniqueness_of :category_id, :scope => [:category_id, :post_id] validates_uniqueness_of :post_id, :scope => [:category_id, :post_id]

于 2010-08-27T09:23:44.863 に答える