2017-11-03 4 views
0

データベースに一意性制約を追加して、重複するエントリがジョインテーブルに追加されないようにしようとしています。しかし、それは動作していないようです。私は結合テーブルのモデルを持っていないので、モデルレベルのバリデーションを追加していません。データベースの一意性制約がアソシエーションを使用して重複レコードの作成を停止しない

class CreateBreedsAndTags < ActiveRecord::Migration[5.1] 
    def change 
    create_table :breeds do |t| 
     t.string :name, unique: true, present: true 
     t.timestamps 
    end 

    create_table :tags do |t| 
     t.string :name, unique: true, present: true 
     t.timestamps 
    end 

    create_join_table :breeds, :tags do |t| 
     t.integer :breed_id 
     t.integer :tag_id 
     t.index [:breed_id, :tag_id], unique: true 
    end 
    end 
end 

品種とタグモデルは両方とも非常に単純であり、私は関連を試してみたかったので、彼らはhas_and_belongs_to_manyを使用します。ここでは

移行です。私はアソシエーションに -> { distinct }を追加することができますが、最初に重複が作成されるのを止めたいと思います。

class Breed < ApplicationRecord 
    # Some validations and stuff here 
    has_and_belongs_to_many :tags 
end 

私はレールコンソールで繁殖してタグを作成した場合。結合テーブル上のデータベースレベルのユニークな制約があるにもかかわらず、私はこのような何かを行うことができます。

b = Breed.create(name: 'b') 
t = Tag.create(name: 't') 
b << t 
b << t 
b.save! 
b.tags # outputs the same tag multiple times 

EDIT:

1)それは私が<<をoverritingをお勧めします。このstack overflowを見つけたことは注目に値します協会。しかし、これは私のユニークな制約が失敗している理由を説明していません。

2)dbレベルの制約を推奨するstack overflowが見つかりましたが、それは私には効果がありません。

EDIT2:

ここでは、データベースから、いくつかのテーブル情報である:

​​

そして、私はそれぞれの移行が最大の単一のテーブルを作成または変更する必要があり\d breeds_tags

Table "public.breeds_tags" 
    Column | Type | Modifiers 
----------+--------+----------- 
breed_id | bigint | not null 
tag_id | bigint | not null 
Indexes: 
    "index_breeds_tags_on_breed_id" btree (breed_id) 
    "index_breeds_tags_on_tag_id" btree (tag_id) 
+1

データベースの結合テーブルを 'psql'で見たことがありますか? –

+0

@muistooshort私はいくつかのデータベース情報で私の答えを更新しました。 2つのインデックスが作成されたようですが、ユニークな制約はありませんか? als oa 'breed_tags'テーブルがあります!私は基本的にこの[スタックオーバーフロー](https://stackoverflow.com/questions/2204058/list-columns-with-indexes-in-postgresql)からクエリを実行しました – Dbz

+0

@Dbz:あなたはそうです。 Railsは2つのインデックスを作成しました。ユニークな制約はありませんでした。 [Railsの一意性ヘルパー](http://guides.rubyonrails.org/active_record_validations.html#uniqueness)は、オブジェクトが保存される直前に属性の値が一意であることを検証し、データベースに一意性制約を作成しません。 。 " –

答えて

2

を走りました。各マイグレーションは、dbへのアトミックおよびリバース可能な変更でなければなりません。同じマイグレーションで同じテーブルを参照する外部キーとテーブルの両方を作成すると、逆にしようとするとどうなりますか?

# rails g model tags name:string 
class CreateTags < ActiveRecord::Migration[5.1] 
    def change 
    create_table :tags do |t| 
     t.string :name 
     t.timestamps 
    end 
    end 
end 

# rails g model breeds name:string 
class CreateBreeds < ActiveRecord::Migration[5.1] 
    def change 
    create_table :breeds do |t| 
     t.string :name 

     t.timestamps 
    end 
    end 
end 

# rails g migration create_join_table_breeds_tags breeds tags 
class CreateJoinTableBreedsTags < ActiveRecord::Migration[5.1] 
    def change 
    create_join_table :breeds, :tags do |t| 
     t.index [:breed_id, :tag_id], unique: true 
    end 
    end 
end 

また、create_join_tableマクロは外部キー列を作成します。だから、それらを手動で追加する必要はありません。

# don't do this. 
t.integer :breed_id 
t.integer :tag_id 

実際にあなたがほとんど団体のためt.integerを使用しないでください。代わりに参照マクロを使用してください。あなたは参加がhas_and_belongs_toはアプリがチェックするための方法を提供していないとして、あなたはhas_many through:を使用してモデルを作成する必要があります一意にする必要がある場合は

=> #<ActiveRecord::Associations::CollectionProxy [#<Tag id: 1, name: "bar", created_at: "2017-11-03 23:34:51", updated_at: "2017-11-03 23:34:51">]> 
irb(main):005:0> b.tags << t 
    (0.2ms) BEGIN 
    SQL (3.8ms) INSERT INTO "breeds_tags" ("breed_id", "tag_id") VALUES ($1, $2) [["breed_id", 1], ["tag_id", 1]] 
    (0.2ms) ROLLBACK 
ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_breeds_tags_on_breed_id_and_tag_id" 
DETAIL: Key (breed_id, tag_id)=(1, 1) already exists. 

これは期待どおりに動作一意性制約を作成しますデータベースドライバが爆発する前に一意です。 ActiveRecord::RecordNotUnique例外をキャッチするために、あなたのコードをいくつかの本当に汚れたレスキュー文で囲む必要があります。

exceptions should not be used for normal flow control以降はお勧めできません。

# rails g model breed_tag breed:belongs_to 

# the table naming for has_many through: is different 
class CreateBreedTags < ActiveRecord::Migration[5.1] 
    def change 
    create_table :breed_tags do |t| 
     t.belongs_to :breed, foreign_key: true 
     t.belongs_to :tag, foreign_key: true 
     t.index [:breed_id, :tag_id], unique: true 
     t.timestamps 
    end 
    end 
end 

class BreedTag < ApplicationRecord 
    belongs_to :breed 
    belongs_to :tag 
    validates_uniqueness_of :breed_id, scope: :tag_id 
end 

class Breed < ApplicationRecord 
    has_many :breed_tags 
    has_many :tags, through: :breed_tags 
end 

class Tag < ApplicationRecord 
    has_many :breed_tags 
    has_many :breeds, through: :breed_tags 
end 
+0

これは、コードが2つのインデックスを作成する理由を正確には示しません。しかし、私は問題を再現することができませんでした。これは実際にRails 5アプリで動作するようにテストされています。 – max

+0

ちょっとマックス、答えてくれてありがとう。私は 'references'を使うことに同意します(そして、joinテーブルは必要ありません)。しかし、何かを夢中でテストしていました!私はあなたの提案にいくつかの考えとテストを与えます。ありがとうございました – Dbz

関連する問題