gpt4 book ai didi

ruby-on-rails - 一个异常复杂的 ActiveRecord 关系

转载 作者:数据小太阳 更新时间:2023-10-29 07:19:53 25 4
gpt4 key购买 nike

我有一个 User 模型。一个用户有很多 EmailAddresses,他们选择其中之一作为他们的 primary_email_address,也就是我向其发送电子邮件的地址。一位用户必须始终至少拥有一个电子邮件地址,并且必须设置一个主要电子邮件地址。主电子邮件地址可能会被销毁,但必须为用户分配一个新的主电子邮件地址。

这已被证明是一个令人惊讶的棘手情况,我尝试过的每个解决方案都有一些不尽如人意的因素。这似乎是一类非常常见的问题(A 有很多 B,其中一个 B 很特殊)所以我很想知道如何干净利落地解决它。

解决方案 1 - EmailAddress 上的 bool 列说明它是否是主地址

类似于:

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 上的 user_id 列之外,User 上的 primary_email_adress_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_addressuser.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 hooks等情况出现很多问题。这似乎是由用户模型中略微尴尬的 belongs_to :primary_email_address 行引起的。通过这两种不同的 ActiveRecord 关系(has_many :email_addresses/belongs_to :userbelongs_to :primary_email_address)关联 EmailAddresses 和 Users 有点奇怪。

2 种技术上都可行的解决方案(我们目前使用的是第二种),但都存在不直观且耗时的缺陷。我很想听听一些关于如何正确解决这个问题的好主意。谢谢。

最佳答案

我建议使用第一种方法,尽管略有不同。实际上没有任何方法可以避免在电子邮件地址上设置触发器回调,因为这是进行修改的地方。但是,您无需将其设置得像以前那样复杂。

由于需要对用户的整个电子邮件地址集合进行检查,因此应使用数据库内容来确定状态是否有效 - 使用 after_saveafter_destroy 是执行此操作的简单方法:

class EmailAdress < AR::Base
belongs_to :user, :inverse_of :email_addresses

scope :primary, where(:primary => true)

after_save :ensure_single_primary_email
after_destroy :ensure_primary_email_exists

def ensure_single_primary_email
user.verify_primary_email(self) if new_record? || primary_changed?
end

def ensure_primary_email_exists
user.ensure_primary_email
end
end

class User < AR::Base
has_many :email_addresses, :inverse_of => :user, :dependent => :destroy
attr_accessible :primary_email_address

validates_presence_of :primary_email_address

def primary_email_address
if association(:email_addresses).loaded?
email_addresses.detect(&:primary?)
else
email_addresses.primary.first
end
end

def primary_email_address=(email)
email.primary = true
email_addresses << email
end

def verify_primary_email(email)
if email.primary? && email_addresses.primary.count > 1
raise "Only one primary email can exist for a user"
elsif !email.primary? && !email_addresses.primary.exists?
raise "A user must have one primary email"
end
end

def ensure_primary_email
return if email_addresses.primary.exists?
raise 'Missing primary email' if !email_addresses.exists?
email_addresses.first.update_attribute(:primary, true)
end
end

关于ruby-on-rails - 一个异常复杂的 ActiveRecord 关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16219861/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com