我如何制作一个模型,验证存在 has_one 与外键约束的关系

How do i fabricate a model with validate presence on has_one relation with foreign key constraint

我似乎 运行 陷入某种循环关系,gem 的 documentation 中的两个解决方案无法为我解决。请参见下面的示例。这意味着要以不同的方式完成吗?

有人会争辩说,因为一个对象在没有另一个对象的情况下无法真正持久化,所以它们应该只是一个模型。我认为最好将有关身份验证的所有逻辑提取到它的单独模型中,以免使用户膨胀。大多数情况下,凭据仅在创建会话时使用,而用户一直在使用。

create_table "credentials", force: :cascade do |t|
  t.bigint "user_id", null: false
  ...
  t.index ["user_id"], name: "index_credentials_on_user_id"
end

add_foreign_key "credentials", "users"
class Credential < ApplicationRecord
  belongs_to :user, inverse_of: :credential
end

class User < ApplicationRecord
  has_one :credential, inverse_of: :user
  validates :credential, presence: true
end
Fabricator(:user_base, class_name: :user)

Fabricator(:user, from: :user_base) do
  credential
end

Fabricator(:credential) do
  user(fabricator: :user_base)
end
irb(main):001:0> Fabricate(:user)
  TRANSACTION (0.1ms)  BEGIN
  TRANSACTION (0.1ms)  ROLLBACK
Traceback (most recent call last):
        1: from (irb):1:in `<main>'
ActiveRecord::RecordInvalid (Validation failed: Credential can't be blank)
irb(main):002:0> Fabricate(:credential)
Traceback (most recent call last):
        2: from (irb):1:in `<main>'
        1: from (irb):2:in `rescue in <main>'
ActiveRecord::RecordInvalid (Validation failed: Credential can't be blank)
irb(main):003:0> Fabricate.build(:user).save
  TRANSACTION (0.2ms)  BEGIN
  User Create (0.8ms)  INSERT INTO "users" ("email", "created_at", "updated_at") VALUES (, , ) RETURNING "id"  [["email", "fake@mail.com"], ["created_at", "2021-05-29 18:19:09.312429"], ["updated_at", "2021-05-29 18:19:09.312429"]]
  Credential Create (0.9ms)  INSERT INTO "credentials" ("user_id", "created_at", "updated_at") VALUES (, , ) RETURNING "id"  [["user_id", 19], ["created_at", "2021-05-29 18:19:09.319411"], ["updated_at", "2021-05-29 18:19:09.319411"]]
  TRANSACTION (41.2ms)  COMMIT
=> true

具有外键的模型(在本例中为 Credential)是需要具有 user_id 值才能持久化的模型。这意味着在创建凭证之前需要有一个用户(在内存中或在数据库中)。这就是为什么使用构建对您有效的原因。

如果用户存在于内存中,rails 将足够聪明,在创建凭据之前先创建该用户。在我看来,当您将构建与 Fabricate 一起使用时,它正在初始化一个用户和一个凭据,因此当用户被保存时,它会与新创建的用户一起保存凭据。

请注意,文档将此语法用于 belongs_to,而不是 has_one。看来您可能需要参考文档的回调部分来解决此问题。

你解决这个问题的方法肯定行得通。我通常建议人们解决这个问题的方法是覆盖反向关系。在这种情况下,ActiveRecord 会做正确的事情。

Fabricator(:user) do
  credential { Fabricate.build(:credential, user: nil) }
end

Fabricator(:credential) do
  user { Fabricate.build(:user, credential: nil) }
end