Как смоделировать has_many с полиморфизмом? - PullRequest
2 голосов
/ 02 июня 2010

Я столкнулся с ситуацией, в которой я не совсем уверен, как моделировать.

РЕДАКТИРОВАТЬ: код ниже теперь представляет собой рабочее решение. Тем не менее, я все еще заинтересован в более привлекательных решениях.

Предположим, у меня есть класс 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

Обратите внимание, что я хочу, чтобы экземпляр службы неявно создавался при создании новой службы.

Итак, это лучшее решение? Или даже хороший? Как вы решили эту проблему?

Ответы [ 2 ]

3 голосов
/ 02 июня 2010

Я не думаю, что ваше текущее решение будет работать. Если ServiceImplementation является абстрактным, на что будут ссылаться связанные классы? Как работает другой конец has_one, если ServiceImplementation не имеет pk, сохраненного в базе данных? Может быть, я что-то упустил.

РЕДАКТИРОВАТЬ: Ой, мой оригинал тоже не работал. Но идея все еще там. Вместо модуля идите вперед и используйте Service с 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
0 голосов
/ 02 июня 2010

Я думаю, что следующие будут работать

в user.rb

  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
...