Ruby on rails 数据库唯一性约束不停止通过关联创建重复记录

Ruby on rails 数据库唯一性约束不停止通过关联创建重复记录,ruby-on-rails,ruby,postgresql,ruby-on-rails-5,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

我正在尝试向数据库中添加唯一性约束,以阻止向联接表中添加重复条目。然而,它似乎不起作用。我没有连接表的模型,因此我不添加模型级验证

以下是迁移:

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
如果我在rails控制台中创建一个品种和标记。即使联接表上存在数据库级别的唯一约束,我也可以这样做:

b = Breed.create(name: 'b')
t = Tag.create(name: 't')
b << t
b << t
b.save!
b.tags # outputs the same tag multiple times
我运行了一个
\d\u标签

  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
事实上,您几乎不应该使用
t.integer
进行关联。改用references宏

这将创建一个按预期工作的唯一性约束:

=> #<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.

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

# 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
事实上,您几乎不应该使用
t.integer
进行关联。改用references宏

这将创建一个按预期工作的唯一性约束:

=> #<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.

您是否通过
psql
查看了数据库中的连接表(即,没有所有ActiveRecord内容的影响)?@muistooshort i使用一些数据库信息更新了我的答案。看起来它创建了两个索引,但我没有看到唯一的约束?有als oa
品种标签
表格!我基本上是从这个@Dbz运行查询的:你是对的。Rails创建了两个索引;它没有创建唯一的约束。“在保存对象之前验证属性的值是否唯一。它不会在数据库中创建唯一性约束…”@MikeSherrill'CatRecall'我实际使用的不是唯一性帮助器,而是数据库约束。我将创建一个连接模型,这样我也可以有一个唯一性助手。然而,我觉得奇怪的是,这个数据库约束不起作用。你没有你认为你有的数据库约束。你有没有通过
psql
查看数据库中的连接表(即没有所有ActiveRecord内容)?@muistooshort我用一些数据库信息更新了我的答案。看起来它创建了两个索引,但我没有看到唯一的约束?有als oa
品种标签
表格!我基本上是从这个@Dbz运行查询的:你是对的。Rails创建了两个索引;它没有创建唯一的约束。“在保存对象之前验证属性的值是否唯一。它不会在数据库中创建唯一性约束…”@MikeSherrill'CatRecall'我实际使用的不是唯一性帮助器,而是数据库约束。我将创建一个连接模型,这样我也可以有一个唯一性助手。但是,我觉得奇怪的是,这个db约束不起作用。你没有你认为有的数据库约束。这并不能确切地回答为什么你的代码会创建两个索引。但我无法复制这个问题,这在Rails 5应用程序中实际运行。嘿,max,谢谢你的回答。我同意使用
引用
(并且不需要连接表),但我正在疯狂地测试任何东西!我会给你的建议一些想法和测试。再次感谢您这并不能确切回答为什么您的代码会创建两个索引。但我无法复制这个问题,这在Rails 5应用程序中实际运行。嘿,max,谢谢你的回答。我同意使用
引用
(并且不需要连接表),但我正在疯狂地测试任何东西!我会给你的建议一些想法和测试。再次感谢你
=> #<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.
# 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