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

时间:2018-12-02 06:42:01

标签: ruby-on-rails polymorphic-associations sti

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

所以我想做的是将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

1 个答案:

答案 0 :(得分:0)

When using STI you don't need (or want) polymorphic associations.

Polymorphic associations are a hack around the object-relational impedance mismatch problem used to setup a single association that points to multiple tables. For example:

class Video
  has_many :comments, as: :commentable
end

class Post
  has_many :comments, as: :commentable
end

class Comment
  belongs_to :commentable, polymorphic: true
end

The reason they should be used sparingly is that there is no referential integrity and there are numerous problems related to joining and eager loading records which STI does not have since you have a "real" foreign key column pointing to a single table.

STI in Rails just uses the fact that ActiveRecord reads the type column to see which class to instantiate when loading records which is also used for polymorphic associations. Otherwise it has nothing to do with polymorphism.

When you setup an association to a STI model you just have to create an association to the base inheritance class and rails will handle resolving the types by reading the type column when it loads the associated records:

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

You should also nest your model in modules and not classes. This is partially due to a bug in module lookup that was not resolved until Ruby 2.5 and also due to convention.

If you then want to create more specific associations to the subtypes of Verification you can do it by:

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

If you want to alias the association user and call it initiator you do it by providing the class name option to the belongs_to association and specifying the foreign key in the has_many associations:

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

This has nothing to do with polymorphism though.