【问题标题】:rails database constraint for self referential association用于自引用关联的 rails 数据库约束
【发布时间】:2015-10-13 19:12:34
【问题描述】:

我正在建立一个涉及定向友谊关系的 Rails 网站。我知道在模型级别,它是一个自引用关联。并且有类似has_and_belongs_to 的方法用于该关联。

我的问题是:如何为这个关系设置数据库级别的约束。我猜迁移会是这样的,它使用外键来保证引用完整性:

class CreateFriendships < ActiveRecord::Migration
  def change
    create_table :friendships do |t|
      t.belongs_to :user, null: false, foreign_key: true
      t.belongs_to :user, null: false, foreign_key: true

      t.integer :accepted, null: false, default: 0
    end
  end

但是当我运行rake db:migrate 时,它有错误:

PG::DuplicateObject: ERROR:  constraint "fk_friendships_user_id" for relation "friendships" already exists

事实上,我什至不确定在这种情况下是否需要设置数据库约束,因为我看到有些人的友谊关系实现没有数据库约束:

create_table :friendships do |t|
  t.integer :user_id
  t.integer :friend_id
  t.timestamps
end

根据 Rails 指南

Active Record 方式声称智能属于您的模型,而不是数据库。因此,触发器或约束等将部分智能推送回数据库的功能并未得到大量使用。

我不确定在这种情况下是否大量使用了数据库约束。

那么在这种情况下,我真的有必要设置数据库级别的约束(使用外键)吗?或者我只需要实现模型级别的约束?谢谢!!

【问题讨论】:

  • 你说不行。什么不起作用?执行特定操作后是否收到特定的错误消息?
  • 感谢您的评论。我已将该错误消息添加到我的问题中。

标签: ruby-on-rails ruby database


【解决方案1】:

您已经两次声明user 关系:

  t.belongs_to :user, null: false, foreign_key: true
  t.belongs_to :user, null: false, foreign_key: true

好像应该是这样的:

  t.belongs_to :user, null: false, foreign_key: true
  t.belongs_to :friend, null: false, foreign_key: true

回答您的问题:如何为该关系设置数据库级别的约束? 答:就像你已经拥有的一样。

开发人员通常采用 rails 方式并在模型中设置这些约束,但在数据库中设置它们是完全合理的。

编辑: 这将允许您使用friend_id 创建一个表

class CreateFriendships < ActiveRecord::Migration
  def change
    create_table :friendships do |t|
      t.belongs_to :user, null: false, foreign_key: true
      t.integer :friend_id, null: false

      t.integer :accepted, null: false, default: 0
    end

    add_foreign_key :friendships, :users, column: :friend_id
  end
end

【讨论】:

  • 感谢您的回答。您解决了我对是否有必要在数据库中添加这样的约束的困惑。但是对于迁移代码,Friendship 表中的两个外键都是指 User 表。而且我没有名为 Friend 的表。所以你的代码不起作用......我想我应该在这里使用一些别名,但我找不到解决方案
  • 你不能有两个同名的列。 SQL 如何知道您在查询中指的是哪个user_id?您不需要拥有friend 模型。这将简单地创建一个friend_id 关联。您稍后将其指定给 rails 中的特定关联模型。
  • 但是在我的迁移文件中,如果我使用belongs_to :friend,它会显示错误PG::UndefinedTable: ERROR: relation "friends" does not exist。 @马格努斯
  • 感谢您的耐心等待。但是代码仍然不起作用....错误消息是ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: relation "friends" does not exist 我认为add_foreign_key 正在尝试设置对名为“friends”的表的引用,该表不存在。 @马格努斯
【解决方案2】:

我认为您对 foreign_keys 在您的数据库架构中的角色感到困惑。


ActiveRecord 只是 SQL 的“涂层”。

它能够形成查询等,允许您构建关联的对象,因此您可以做的最重要的事情是正确关联这些对象。

在 SQL 中执行此操作的方法是使用 foreign_key,它基本上显示了 ActiveRecord 之类的内容(如果您使用 join 查询,则显示 SQL)与哪些数据相关联:

外键是关系数据库结构的标准元素,你可能知道。


您的数据结构失败的原因是您在friendships 表中复制了user_id 外键。

您需要参考以下内容: Rails: self join scheme with has_and_belongs_to_many?

这说明如果你想创建一个自引用连接表(比如你正在做的),你需要使用如下:

#app/models/user.rb
class User < ActiveRecord::Base
  has_and_belongs_to_many :friends, 
              class_name: "User", 
              join_table: :friendships, 
              foreign_key: :user_id, 
              association_foreign_key: :friend_user_id
end

#db/migrate/______.rb
class CreateFriendships < ActiveRecord::Migration
  def self.up
    create_table :friendships, id: false do |t|
      t.integer :user_id
      t.integer :friend_user_id
    end

    add_index(:friendships, [:user_id, :friend_user_id], :unique => true)
    add_index(:friendships, [:friend_user_id, :user_id], :unique => true)
  end

  def self.down
      remove_index(:friendships, [:friend_user_id, :user_id])
      remove_index(:friendships, [:user_id, :friend_user_id])
      drop_table :friendships
  end
end

注意user_idfriend_user_id 的引用是怎样的?

这是确保has_and_belongs_to_many 能够关联同一模型的两个对象所需的两个外键。

【讨论】:

  • 感谢您的回答!但似乎在数据库级别,不能保证引用完整性,因为迁移文件中的代码没有设置任何外键。我不确定我是否理解正确? @Rich Peck
  • 那么,这是否意味着在这种情况下,在模型中设置约束就足够了?在数据库中设置约束是不必要的? @Rich Peck
  • 是的。我们使用 MYSQL,我们在模型中没有约束
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多