ruby-on-rails - 数据库唯一性约束不会阻止通过关联创建重复记录

标签 ruby-on-rails ruby postgresql ruby-on-rails-5

我正在尝试向我的数据库添加唯一性约束,以阻止将重复条目添加到连接表。但是,它似乎没有用。我没有连接表的模型,所以我没有添加模型级别的验证。

这是迁移:

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它推荐了数据库级别的约束,但这对我不起作用。

编辑2:

这是数据库中的一些表信息:

       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)

最佳答案

每个迁移最多应该创建或更改一个表。每次迁移都应该是对数据库的原子和可逆更改。如果您在同一迁移中同时创建表和引用相同表的外键,如果您尝试反转它会发生什么?

# 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

关于ruby-on-rails - 数据库唯一性约束不会阻止通过关联创建重复记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47105478/

相关文章:

javascript - 对线程对话进行排序

ruby-on-rails - PG::错误:错误:列引用 "status"在 active_admin 中不明确

ruby-on-rails - ruby on Rails 中未定义的方法错误 ^

ruby-on-rails - 如何按 ruby​​ 中的多个属性对数组进行排序?

postgresql - 我如何创建一个有效的索引来快速检索最后一天的数据?

sql - 如何从 SQL 中的两个表创建矩阵?

ruby-on-rails - 学习 Ruby on Rails。网上商店/在线购物车网络应用程序

ruby-on-rails - 匹配器应该在验证唯一性时尝试插入 `nil` 值

ruby - 仅当字符串具有值时才对字符串进行 Gsub

ruby - 使用可枚举获取所有项目#take