无法让 STI 充当模型的多态关联

Can't get STI to act as polymorphic association on model

我有一个用户模型,它可以有一个电子邮件和一个 phone 号码,这两个模型都是它们自己的模型,因为它们都需要某种形式的验证。

所以我想做的是将 Verification::EmailVerification 附加为 email_verifications 并将 Verification::PhoneVerification 附加为 phone_verifications,它们都是 Verification 的 STI。

class User < ApplicationRecord
  has_many :email_verifications, as: :initiator, dependent: :destroy
  has_many :phone_verifications, as: :initiator, dependent: :destroy

  attr_accessor :email, :phone

  def email
    @email = email_verifications.last&.email
  end

  def email=(email)
    email_verifications.new(email: email)
    @email = email
  end

  def phone
    @phone = phone_verifications.last&.phone
  end

  def phone=(phone)
    phone_verifications.new(phone: phone)
    @phone = phone
  end
end

class Verification < ApplicationRecord
  belongs_to :initiator, polymorphic: true
end

class Verification::EmailVerification < Verification
  alias_attribute :email, :information
end

class Verification::PhoneVerification < Verification
  alias_attribute :phone, :information
end

但是,通过上述设置我得到了错误 uninitialized constant User::EmailVerification。我不确定我哪里出错了。

我如何构造它以便我可以在用户模型上访问 email_verificationsphone_verifications

使用 STI 时,您不需要(或不需要)多态关联。

多态关联是围绕对象关系阻抗不匹配问题的一种破解,用于设置指向多个 table 的单个关联。例如:

class Video
  has_many :comments, as: :commentable
end

class Post
  has_many :comments, as: :commentable
end

class Comment
  belongs_to :commentable, polymorphic: true
end

应谨慎使用它们的原因是没有参照完整性,并且存在许多与连接和预加载记录相关的问题,而 STI 没有这些问题,因为您有一个 "real" 外键列指向单个 table.

Rails 中的

STI 只是利用 ActiveRecord 读取 type 列来查看在加载记录时要实例化哪个 class 的事实,这也用于多态关联。否则与多态无关。

当您设置与 STI 模型的关联时,您只需创建与基础继承的关联 class 并且 rails 将在加载时通​​过读取类型列来处理类型解析相关记录:

class User < ApplicationRecord
  has_many :verifications
end

class Verification < ApplicationRecord
  belongs_to :user
end

module Verifications 
  class EmailVerification < ::Verification
    alias_attribute :email, :information
  end
end

module Verifications 
  class PhoneVerification < ::Verification
    alias_attribute :email, :information
  end
end

您还应该将模型嵌套在模块中,而不是 classes。这部分是由于 bug in module lookup 直到 Ruby 2.5 才解决,也是由于惯例。

如果您随后想为验证的子类型创建更具体的关联,您可以通过以下方式完成:

class User < ApplicationRecord
  has_many :verifications
  has_many :email_verifications, ->{ where(type: 'Verifications::EmailVerification') },
          class_name: 'Verifications::EmailVerification'
  has_many :phone_verifications, ->{ where(type: 'Verifications::PhoneVerification') },
          class_name: 'Verifications::PhoneVerification'
end

如果您想为协会 user 起别名并将其命名为 initiator,您可以通过向 belongs_to 协会提供 class 名称选项并指定外部名称来实现has_many 关联中的键:

class Verification < ApplicationRecord
  belongs_to :initiator, class_name: 'User'
end

class User < ApplicationRecord
  has_many :verifications, foreign_key: 'initiator_id'
  has_many :email_verifications, ->{ where(type: 'Verifications::EmailVerification') },
          class_name: 'Verifications::EmailVerification',
          foreign_key: 'initiator_id'
  has_many :phone_verifications, ->{ where(type: 'Verifications::PhoneVerification') },
          class_name: 'Verifications::PhoneVerification',
          foreign_key: 'initiator_id'
end

虽然这与多态性无关。