我遇到了一种我不太确定如何建模的情况。
编辑:下面的代码现在代表了一个有效的解决方案。不过,我仍然对更好看的解决方案感兴趣。
假设我有一个User类,并且用户有很多服务。但是,这些服务完全不同,例如MailService
和BackupService
,因此单表继承不起作用。相反,我正在考虑将多态关联与抽象基类一起使用:
class User < ActiveRecord::Base
has_many :services
end
class Service < ActiveRecord::Base
validates_presence_of :user_id, :implementation_id, :implementation_type
validates_uniqueness_of :user_id, :scope => :implementation_type
belongs_to :user
belongs_to :implementation, :polymorphic => true, :dependent => :destroy
delegate :common_service_method, :name, :to => :implementation
end
#Base class for service implementations
class ServiceImplementation < ActiveRecord::Base
validates_presence_of :user_id, :on => :create
#Virtual attribute, allows us to create service implementations in one step
attr_accessor :user_id
has_one :service, :as => :implementation
after_create :create_service_record
#Tell Rails this class does not use a table.
def self.abstract_class?
true
end
#Name of the service.
def name
self.class.name
end
#Returns the user this service
#implementation belongs to.
def user
unless service.nil?
service.user
else #Service not yet created
@my_user ||= User.find(user_id) rescue nil
end
end
#Sets the user this
#implementation belongs to.
def user=(usr)
@my_user = usr
user_id = usr.id
end
protected
#Sets up a service object after object creation.
def create_service_record
service = Service.new(:user_id => user_id)
service.implementation = self
service.save!
end
end
class MailService < ServiceImplementation
#validations, etc...
def common_service_method
puts "MailService implementation of common service method"
end
end
#Example usage
MailService.create(..., :user => user)
BackupService.create(...., :user => user)
user.services.each do |s|
puts "#{user.name} is using #{s.name}"
end #Daniel is using MailService, Daniel is using BackupService
请注意,我希望在创建新服务时隐式创建Service实例。
那么,这是最好的解决方案吗?甚至是一个好的?你是怎么解决这类问题的?
答案 0 :(得分:3)
我认为您当前的解决方案无效。如果ServiceImplementation是抽象的,那么关联的类指向什么?如果ServiceImplementation没有pk持久存储到数据库,那么has_one的另一端是如何工作的?也许我错过了什么。
编辑:哎呀,我原来也没用。但这个想法仍然存在。而不是模块,继续使用Service with STI而不是多态,并使用单独的实现扩展它。我认为你在不同的实现中坚持使用STI和一堆未使用的列,或者重新思考一般的服务关系。您拥有的委派解决方案可能作为单独的ActiveRecord工作,但如果必须具有has_one关系,我不会看到它如何工作为抽象。编辑:那么为什么不坚持delgates而不是你原来的抽象解决方案呢?你必须为MailServiceDelegate和BackupServiceDelegate提供单独的表 - 如果你想避免使用纯STI的所有空列,不知道如何解决这个问题。您可以使用带有delgate类的模块来捕获常见的关系和验证等。抱歉,我花了几个通行证来解决您的问题:
class User < ActiveRecord::Base
has_many :services
end
class Service < ActiveRecord::Base
validates_presence_of :user_id
belongs_to :user
belongs_to :service_delegate, :polymorphic => true
delegate :common_service_method, :name, :to => :service_delegate
end
class MailServiceDelegate < ActiveRecord::Base
include ServiceDelegate
def name
# implement
end
def common_service_method
# implement
end
end
class BackupServiceDelegate < ActiveRecord::Base
include ServiceDelegate
def name
# implement
end
def common_service_method
# implement
end
end
module ServiceDelegate
def self.included(base)
base.has_one :service, :as => service_delegate
end
def name
raise "Not Implemented"
end
def common_service_method
raise "Not Implemented"
end
end
答案 1 :(得分:0)
我认为以下工作
在user.rb
中 has_many :mail_service, :class_name => 'Service'
has_many :backup_service, :class_name => 'Service'
in service.rb
belongs_to :mail_user, :class_name => 'User', :foreign_key => 'user_id', :conditions=> is_mail=true
belongs_to :backup_user, :class_name => 'User', :foreign_key => 'user_id', :conditions=> is_mail=false