首页 > 解决方案 > 无法让 STI 在模型上充当多态关联

问题描述

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

所以我想做的是附加Verification::EmailVerificationasemail_verificationsVerification::PhoneVerificationas phone_verifications,它们都是Verification.

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_verifications我如何构建它以便我可以访问phone_verifications用户模型?

标签: ruby-on-railspolymorphic-associationssti

解决方案


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

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

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 没有的连接和急切加载记录相关的问题,因为您有一个指向单个表的“真实”外键列。

Rails 中的 STI 仅使用 ActiveRecord 读取type列以查看在加载记录时要实例化哪个类的事实,这也用于多态关联。否则它与多态无关。

当您设置与 STI 模型的关联时,您只需创建与基本继承类的关联,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

您还应该将模型嵌套在模块而不是类中。这部分是由于模块查找中的错误直到 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关联中指定外键来实现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

但是,这与多态性无关。


推荐阅读