2010-06-02 40 views
2

我遇到了一個我不太確定如何建模的情況。如何用多態性建模has_many?

編輯:下面的代碼現在代表一個工作解決方案。不過,我仍然對更好看的解決方案感興趣。

假設我有一個用戶類,並且用戶有很多服務。但是,這些服務非常不同,例如MailServiceBackupService,因此單表繼承將不會執行。相反,我想使用多態關聯與抽象基類在一起:

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 

請注意,我想,當我創建一個新的服務被implictly創建的服務實例。

那麼,這是最好的解決方案嗎?甚至是一個好的?你是如何解決這類問題的?

+0

紅寶石是否有接口?像這樣的東西:http://msdn.microsoft.com/en-us/library/87d83y5b%28VS.80%29.aspx – 2010-06-02 10:43:08

回答

3

我不認爲你目前的解決方案會奏效。如果ServiceImplementation是抽象的,那麼關聯的類將指向什麼?如果ServiceImplementation沒有pk保存到數據庫,has_one的另一端如何工作?也許我錯過了一些東西。

編輯:哎呀,我原來也沒有工作。但這個想法仍然存在。不要使用模塊,請繼續使用Service with STI而不是多態,並使用單獨的實現對其進行擴展。我認爲你在STI和一堆跨不同實現的未使用列,或者重新考慮服務關係時會遇到困難。您擁有的代表團解決方案可以作爲單獨的ActiveRecord工作,但是如果它必須具有has_one關係,我不會看到它是如何工作的抽象。

編輯:所以,而不是你原來的抽象的解決方案,爲什麼不堅持分解?你必須爲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 
+0

不錯的解決方案!不過,我現在有自己的建議解決方案,可以很好地工作。它基本上和你的一樣,除了我使用抽象類而不是模塊。我會盡快更新代碼,所以比較容易。 – 2010-06-03 06:09:22

+0

我接受你的答案,因爲它最接近我自己的最終解決方案。 – 2010-06-24 11:15:22

0

我覺得下面的工作

在user.rb

has_many :mail_service, :class_name => 'Service' 
    has_many :backup_service, :class_name => 'Service' 

在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 
+0

這不會做。我不希望用戶知道關於服務的細節,只是它們符合特定的接口。 – 2010-06-02 10:56:50