【问题标题】:Polymorphic habtm relationships with Rails/ActiveRecord与 Rails/ActiveRecord 的多态 habtm 关系
【发布时间】:2009-07-14 03:46:15
【问题描述】:

如何使用 Rails/ActiveRecord 创建多态 has_and_belongs_to_many 关系?

我看到的大多数示例都涉及创建belongs_to 关系,这将我的多态端限制为仅与一个父级相关:

表:任务

表:Tasks_Targets

表:CustomerStore

表:软件系统

在这种情况下,CustomerStore 和 SoftwareSystem 都属于“Targetable”类型。据我了解,如果我像大多数示例所示那样实现多态关系,我只能将 Targetable 与任务相关联一次

一些澄清可能会有所帮助,因为大多数在线搜索仍然无法解释这种关系背后的一些理论......

谢谢!

【问题讨论】:

    标签: ruby-on-rails database-design polymorphic-associations relationships


    【解决方案1】:

    鉴于您对您的域的解释,我制作了一个小型测试驱动示例,说明您如何解决您的问题。如果您发现任何域不一致,请随时进一步澄清(我正在使用我的 acts_as_fu gem 即时创建测试模型)。

    require 'acts_as_fu'
    
    # class Task < ActiveRecord::Base
    build_model(:tasks) do
      integer :task_target_id
    
      has_many :task_targets
      has_many :customer_stores, :through => :task_targets, :source => :targetable, :source_type => 'CustomerStore'
      has_many :software_systems, :through => :task_targets, :source => :targetable, :source_type => 'SoftwareSystem'
    end
    
    # class TaskTarget < ActiveRecord::Base
    build_model(:task_targets) do
      string  :targetable_type
      integer :targetable_id
      integer :task_id
    
      belongs_to :targetable, :polymorphic => true
      belongs_to :task
    end
    
    # class CustomerStore < ActiveRecord::Base
    build_model(:customer_stores) do
      has_many :task_targets, :as => :targetable
      has_many :tasks, :through => :task_targets
    end
    
    # class SoftwareSystem < ActiveRecord::Base
    build_model(:software_systems) do
      has_many :task_targets, :as => :targetable
      has_many :tasks, :through => :task_targets
    end
    
    require 'test/unit'
    
    class PolymorphicDomainTest < Test::Unit::TestCase
      # Test that customer stores can have multiple tasks
      def test_customer_store_gets_task
        task = Task.create!
        customer_store = CustomerStore.create!
        customer_store.task_targets.create! :task => task
        assert customer_store.tasks.include?(task)
      end
    
      def test_many_customer_stores_get_task
        task_a = Task.create!
        task_b = Task.create!
        customer_store = CustomerStore.create! :tasks => [task_a, task_b]
        assert customer_store.tasks.include?(task_a)
        assert customer_store.tasks.include?(task_b)
      end
    
      # Test that software systems can have multiple tasks
      def test_software_system_gets_task
        task = Task.create!
        software_system = SoftwareSystem.create!
        software_system.task_targets.create! :task => task
        assert software_system.tasks.include?(task)
      end
    
      def test_many_software_systems_get_task
        task_a = Task.create!
        task_b = Task.create!
        software_system = SoftwareSystem.create! :tasks => [task_a, task_b]
        assert software_system.tasks.include?(task_a)
        assert software_system.tasks.include?(task_b)
      end
    
      # Test that Tasks can have multiple customer stores
      def test_task_has_many_customer_stores
        task = Task.create!
        customer_store_a = CustomerStore.create!
        customer_store_b = CustomerStore.create!
        task.customer_stores = [customer_store_a, customer_store_b]
        task.save!
        task.reload
        assert task.customer_stores.include?(customer_store_a)
        assert task.customer_stores.include?(customer_store_b)
      end
    
      # Test that Tasks can have multiple software systems
      def test_task_has_many_software_systems
        task = Task.create!
        software_system_a = SoftwareSystem.create!
        software_system_b = SoftwareSystem.create!
        task.software_systems = [software_system_a, software_system_b]
        task.save!
        task.reload
        assert task.software_systems.include?(software_system_a)
        assert task.software_systems.include?(software_system_b)
      end
    end
    

    【讨论】:

    • 这看起来不错!我唯一担心的是我被困在“任务”中定义每个“目标”。有没有办法解决这个问题? Task.targets/targetables 呢?
    • 我今天大部分时间都在啃这个,但我仍然对我所拥有的不太满意......它源于:“has_many :customer_stores, :through => :task_targets, :source => :targetable, :source_type => 'CustomerStore' has_many :software_systems, :through => :task_targets, :source => :targetable, :source_type => 'SoftwareSystem'" 这对我来说完全违背了多态关系的目的一起。有什么方法可以设置它,这样我就可以通过 Task.target 获得所有 targetables 并且能够到 Targetable.tasks?
    【解决方案2】:

    为了补充 nakajima 关于您的问题的回答,我将这样做:

    class Task < ActiveRecord::Base
      def targets
        # Get Array of all targetables
        tt = TaskTarget.select_all("SELECT targetable_type, targetable_id FROM task_targerts WHERE task_id = #{self[:id]}")
    
        # Build Hash of targetable_type => Array of targetable_ids
        targetables = Hash.new { |hash, key| hash[key] = [] }
        tt.each do |targetable|
          targetables[targetable.targetable_type] << targetable.targetable_id
        end
    
        # Query each "targetable" table once and merge all results
        targetables.keys.map{|key| (eval key).find(targetables[key])}.flatten
      end
    end
    

    确保在表task_targets 中索引task_id

    【讨论】:

      猜你喜欢
      • 2013-06-21
      • 1970-01-01
      • 1970-01-01
      • 2011-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多