I have a model User. The user has a lot EmailAddresses, and they choose one of them as their own primary_email_address, to which I send emails. A user must have at least one email address and must have a primary email address. The primary email address may be destroyed, but the new primary email address must be assigned to the user.
This turned out to be a surprisingly difficult situation, and every solution I tried has some unsatisfactory elements. This seems like a very common class of problems (A has a lot of B, and one of their Bs is special), so I would like to know how to solve it cleanly.
Solution 1 - A logical column in EmailAddress saying whether it is primary or not
Sort of:
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
def reassign_user_primary_email_address_if_necessary
end
def check_not_users_only_email_address
end
end
This is conceptually inconvenient because it is so important that the user has exactly one primary email address, and checking it against multiple email addresses seems to be bad. And although I know that ActiveRecord transactions should mean that the user is not stuck without a primary email address, this seems like a recipe for disaster. The primary email address is basically what belongs to the user, and the inclusion of this logic in the EmailAddress model is unideal.
Solution 2 column - user_id in EmailAddress
Sort of:
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
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. , ActiveRecord (has_many :email_addresses/belongs_to :user belongs_to :primary_email_address).
2 , ( ), . , . .