代码之家  ›  专栏  ›  技术社区  ›  Daniel Abrahamsson

如何用多态性对has-u-many进行建模?

  •  2
  • Daniel Abrahamsson  · 技术社区  · 16 年前

    我遇到了一个不太确定如何建模的情况。

    编辑:下面的代码现在表示一个有效的解决方案。不过,我还是对好看的解决方案感兴趣。

    假设我有一个用户类,一个用户有许多服务。然而,这些服务是完全不同的,例如 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
    

    那么,这是最好的解决方案吗?甚至是一个好的?你是怎么解决这种问题的?

    2 回复  |  直到 16 年前
        1
  •  3
  •   Dave Sims    16 年前

    我认为你目前的解决办法行不通。如果ServiceImplementation是抽象的,那么关联的类将指向什么?如果ServiceImplementation没有将pk持久化到数据库中,那么另一端是如何工作的?也许我错过了什么。

    编辑:哎呀,我原来的也不行。但这个想法仍然存在。代替一个模块,继续使用带有STI的服务,而不是多态性,并用单独的实现来扩展它。我认为您被STI和跨不同实现的一堆未使用的列困住了,或者重新思考了一般的服务关系。您拥有的委托解决方案可能作为一个单独的ActiveRecord工作,但是如果它必须有一个has\u 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
    
        2
  •  0
  •   Salil    16 年前

    我想下面的办法行得通

      has_many :mail_service, :class_name => 'Service'
      has_many :backup_service, :class_name => '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