質問

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

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

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

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

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

解決

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

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

他のヒント

その とても厳しい 「正しい」アプローチを推奨します。

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

ご了承ください(本によると) レール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) 両方の組み合わせ

両方の組み合わせについても読むことができますが、私はどちらか一方だけを好む傾向があります。

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

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.

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

解決策としては、モデルにインデックスと検証の両方を追加することが考えられます。

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

そしてモデルでは次のようになります。validates_uniqueness_of :category_id, :scope => [:category_id, :p ost_id] validates_uniqueness_of :p ost_id, :scope => [:category_id, :p ost_id]

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top