如何使用多态性对 has_many 进行建模?
我遇到了一种不太确定如何建模的情况。
编辑:下面的代码现在代表一个工作解决方案。不过,我仍然对外观更好的解决方案感兴趣。
假设我有一个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 实例。
那么,这是最好的解决方案吗?或者甚至是一个好的?你是如何解决此类问题的?
I've run into a situation that I am not quite sure how to model.
EDIT: The code below now represent a working solution. I am still interested in nicer looking solutions, though.
Suppose I have a User class, and a user has many services. However, these services are quite different, for example a MailService
and a BackupService
, so single table inheritance won't do. Instead, I am thinking of using polymorphic associations together with an abstract base class:
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
Notice that I want the Service instance to be implictly created when I create a new service.
So, is this the best solution? Or even a good one? How have you solved this kind of problem?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我认为您当前的解决方案不起作用。如果 ServiceImplementation 是抽象的,那么关联的类将指向什么?如果 ServiceImplementation 没有将 pk 持久保存到数据库中,那么 has_one 的另一端如何工作?也许我错过了一些东西。
编辑:哎呀,我原来的也不起作用。但这个想法仍然存在。继续使用具有 STI 的服务而不是多态性,而不是模块,并通过单独的实现来扩展它。我认为您被 STI 和跨不同实现的一堆未使用的列所困扰,或者重新思考一般的服务关系。您拥有的委派解决方案可能作为单独的 ActiveRecord 工作,但如果它必须具有 has_one 关系,我不知道它如何抽象地工作。
编辑:那么为什么不保留委托,而不是原来的抽象解决方案呢?您必须为 MailServiceDelegate 和 BackupServiceDelegate 建立单独的表——如果您想避免纯 STI 的所有空列,则不确定如何解决这个问题。您可以使用带有 delgate 类的模块来捕获常见关系和验证等。抱歉,我花了几遍才赶上您的问题:
I don't think your current solution will work. If ServiceImplementation is abstract, what will the associated classes point to? How does the other end of the has_one work, if ServiceImplementation doesn't have a pk persisted to the database? Maybe I'm missing something.
EDIT: Whoops, my original didn't work either. But the idea is still there. Instead of a module, go ahead and use Service with STI instead of polymorphism, and extend it with individual implementations. I think you're stuck with STI and a bunch of unused columns across different implementations, or rethinking the services relationship in general. The delegation solution you have might work as a separate ActiveRecord, but I don't see how it works as abstract if it has to have a has_one relationship.
EDIT: So instead of your original abstract solution, why not persist the delgates? You'd have to have separate tables for MailServiceDelegate and BackupServiceDelegate -- not sure how to get around that if you want to avoid all the null columns with pure STI. You can use a module with the delgate classes to capture the common relationships and validations, etc. Sorry it took me a couple of passes to catch up with your problem:
我认为以下内容适用
user.rb
于service.rb 中的
I think following will work
in user.rb
in service.rb