无法让 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_verifications
和 phone_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
虽然这与多态性无关。
我有一个用户模型,它可以有一个电子邮件和一个 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_verifications
和 phone_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
虽然这与多态性无关。