我正在尝试向数据库添加唯一性约束,以阻止将重复条目添加到连接表中。但是,它似乎没有起作用。我没有连接表的模型,所以我没有添加模型级验证。
以下是迁移:
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
Breed和Tag模型都非常简单,它们使用has_and_belongs_to_many
,因为我想测试这种关联。我可以在关联中添加-> { distinct }
,但我想首先停止创建重复项。
class Breed < ApplicationRecord
# Some validations and stuff here
has_and_belongs_to_many :tags
end
如果我在rails控制台中创建了一个Breed和Tag。即使连接表上存在数据库级唯一约束,我也可以这样做:
b = Breed.create(name: 'b')
t = Tag.create(name: 't')
b << t
b << t
b.save!
b.tags # outputs the same tag multiple times
编辑:
1)值得注意的是,我发现此stack overflow建议在关联中夸大<<
。但是,这并不能解释为什么我的唯一约束失败。
2)我还发现这个stack overflow建议使用db级别约束,但这对我不起作用。
EDIT2:
以下是数据库中的一些表信息:
table_name | index_name | column_name
-------------------------+-----------------------------------------+-----------------
ar_internal_metadata | ar_internal_metadata_pkey | key
breed_tags | breed_tags_pkey | id
breeds | breeds_pkey | id
breeds_tags | index_breeds_tags_on_breed_id | breed_id
breeds_tags | index_breeds_tags_on_tag_id | tag_id
我跑了\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)
答案 0 :(得分: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
进行关联。请改用引用宏。
这会创建一个按预期工作的唯一性约束:
=> #<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.
但是,如果您需要将连接设置为唯一,则应使用has_many through:
并创建模型,因为has_and_belongs_to
不会为应用在数据库驱动程序爆炸前检查唯一性提供方法。它需要您将代码包装在一些非常脏的救援语句中以捕获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