我有一个User
模型。一个用户有很多EmailAddresses
,他们选择其中一个作为他们的primary_email_address
,这是我发送电子邮件的那个。用户必须始终拥有至少一个电子邮件地址,并且必须设置一个主电子邮件地址。主电子邮件地址可能会被销毁,但必须为用户分配一个新的主电子邮件地址。
事实证明,这是一个非常难以处理的情况,而且我尝试过的每个解决方案都有一些不令人满意的元素。这似乎是一类非常常见的问题(A 有很多 B,其中一个 B 很特殊)所以我很想知道如何干净利落地解决它。
解决方案 1 - EmailAddress 上的布尔列说明它是否是主要的
就像是:
class User < ActiveRecord::Base
has_many :email_addresses, inverse_of: :user
validates :has_exactly_one_primary_email_address
def primary_email_address
email_addresses.where(is_primary:true).first
end
def has_exactly_one_primary_email_address
# ...
end
end
class EmailAddress < ActiveRecord::Base
belongs_to :user, inverse_of: :email_addresses
before_destroy :check_not_users_only_email_address
after_destroy :reassign_user_primary_email_address_if_necessary
# the logic for both these methods should live on the user but you get the idea
def reassign_user_primary_email_address_if_necessary
# ...
end
def check_not_users_only_email_address
# ...
end
end
这在概念上很尴尬,因为用户只有一个主电子邮件地址非常重要,并且必须跨多个电子邮件地址记录验证这一点似乎很糟糕。虽然我知道 ActiveRecord 事务应该意味着用户不会因为没有主电子邮件地址而陷入困境,但这似乎是灾难的根源。主电子邮件地址本质上是属于用户的东西,将这个逻辑放到 EmailAddress 模型中是不理想的。
解决方案 2 -除了EmailAddress 上的列primary_email_adress_id
之外,用户上的列user_id
就像是:
class User < ActiveRecord::Base
has_many :email_addresses, inverse_of: :user
belongs_to :primary_email_address
validates_presence_of :primary_email_address
end
class EmailAddress < ActiveRecord::Base
belongs_to :user, inverse_of: :email_addresses
before_destroy :check_not_users_only_email_address
after_destroy :reassign_user_primary_email_address_if_necessary
# the logic for both these methods should live on the user but you get the idea
def reassign_user_primary_email_address_if_necessary
# ...
end
def check_not_users_only_email_address
# ...
end
end
这更好,因为验证只有 1 个主电子邮件地址的用户更容易,并且与用户模型的耦合更紧密。然而,现在围绕逆存在令人讨厌的问题。user.primary_email_address
不会将同一实例引用为user.email_addresses
数组中的同一记录,并且需要大量重新加载以确保您的内存实例具有正确的数据。
> u = User.last
> u.email_addresses.map(&:email)
=> ["monkey@hotmail.com", "gorilla@gmail.com"]
> u.primary_email_address.destroy
=> true
> u.email_addresses.map(&:email)
=> ["monkey@hotmail.com", "gorilla@gmail.com"]
> u.reload
> u.email_addresses.map(&:email)
=> ["monkey@hotmail.com"]
after_destroy
这在钩子和其他情况下会导致很多问题。这似乎是由belongs_to :primary_email_address
User 模型中略显尴尬的行引起的。has_many :email_addresses/belongs_to :user
EmailAddresses 和 Users 被这两个不同的 ActiveRecord 关系(和)关联起来有点奇怪belongs_to :primary_email_address
。
两种在技术上都有效的解决方案(我们目前正在使用第二种解决方案),但都存在不直观且耗时的缺陷。我很想听听一些关于如何正确解决这个问题的好主意。谢谢。