34

请参阅评论以获取更新。

我一直在努力得到一个清晰而直接的答案,我希望这次我能得到它!:D 我肯定还有很多东西要学习 Rails,但是我确实理解我面临的问题,并且非常感谢额外的帮助。

  • 我有一个名为“任务”的模型。
  • 我有一个名为“目标”的抽象模型。
  • 我想将 Target 子类的多个实例与 Task 关联起来。
  • 我没有使用单表继承。
  • 我想查询多态关系以返回 Target 子类的混合结果集。
  • 我想查询 Target 子类的各个实例以获取与它们有关系的任务。

所以,我认为任务和目标子类之间的多态多对多关系是有序的。更详细地说,我将能够在控制台中(当然还有其他地方)做这样的事情:

task = Task.find(1)
task.targets
[...array of all the subclasses of Target here...]

但!假设存在模型“Store”、“Software”、“Office”、“Vehicle”,它们都是“Target”的子类,那么在另一个方向上也可以遍历关系:

store = Store.find(1)
store.tasks
[...array of all the Tasks this Store is related to...]
software = Software.find(18)
software.tasks
[...array of all the Tasks this Software is related to...]

多态关系隐含的数据库表似乎能够进行这种遍历,但我看到一些反复出现的主题,试图找到一个对我来说违背多态关系精神的答案:

  • 仍然使用我的示例,人们似乎想要在 Task 中定义 Store、Software、Office、Vehicle,我们可以立即看出这不是多态关系,因为它只返回一种类型的 model
  • 与最后一点类似,人们仍然希望以一种形式或形式在 Task 中定义 Store、Software、Office 和 Vehicle。这里重要的一点是关系对子类化是盲目的。我的多态最初只会作为目标进行交互,而不是作为它们各自的子类类型。在 Task 中定义每个子类再次开始侵蚀多态关系的目的。
  • 我看到连接表的模型可能是有序的,这对我来说似乎有些正确,只是它增加了一些我认为 Rails 愿意放弃的复杂性。我恳求在这方面没有经验。

这似乎是 Rails 功能或集体社区知识中的一个小漏洞。所以希望stackoverflow可以记录我对答案的搜索!

感谢所有帮助的人!

4

7 回答 7

58

您可以结合多态性并has_many :through获得灵活的映射:

class Assignment < ActiveRecord::Base
  belongs_to :task
  belongs_to :target, :polymorphic => true
end

class Task < ActiveRecord::Base
  has_many :targets, :through => :assignment
end

class Store < ActiveRecord::Base
  has_many :tasks, :through => :assignment, :as => :target
end

class Vehicle < ActiveRecord::Base
  has_many :tasks, :through => :assignment, :as => :target
end

...等等。

于 2009-10-08T19:52:12.000 回答
15

虽然 SFEley 提出的答案很好,但也存在一些缺陷:

  • 从目标(商店/车辆)检索任务是有效的,但向后不会。这基本上是因为您无法遍历 :through 与多态数据类型的关联,因为 SQL 无法判断它在哪个表中。
  • 每个具有 :through 关联的模型都需要与中间表直接关联
  • :through 赋值关联应该是复数形式
  • :as 语句不能与 :through 一起使用,您需要先使用中间表所需的直接关联来指定它

考虑到这一点,我最简单的解决方案是:

class Assignment < ActiveRecord::Base
  belongs_to :task
  belongs_to :target, :polymorphic => true
end

class Task < ActiveRecord::Base
  has_many :assignments
  # acts as the the 'has_many targets' needed
  def targets
    assignments.map {|x| x.target}
  end
end

class Store < ActiveRecord::Base
  has_many :assignments, as: :target
  has_many :tasks, :through => :assignment
end

class Vehicle < ActiveRecord::Base
  has_many :assignments, as: :target
  has_many :tasks, :through => :assignment, :as => :target
end

参考资料: http ://blog.hasmanythrough.com/2006/4/3/polymorphic-through

于 2015-09-19T15:57:22.293 回答
1

您提到的 has_many_polymorphs 解决方案还不错。

class Task < ActiveRecord::Base
  has_many_polymorphs :targets, :from => [:store, :software, :office, :vehicle]
end

似乎做你想做的一切。

它提供了以下方法:

到任务:

t = Task.first
t.targets   # Mixed collection of all targets associated with task t
t.stores    # Collection of stores associated with task t
t.softwares # same but for software
t.offices   # same but for office
t.vehicles  # same but for vehicles

到软件、商店、办公室、车辆:

s = Software.first    # works for any of the subtargets.
s.tasks               # lists tasks associated with s

如果我正确地遵循了评论,唯一剩下的问题是您不想每次创建新类型的子目标时都修改 app/models/task.rb。Rails 方式似乎要求您修改两个文件以创建双向关联。has_many_polymorphs 只需要您更改任务文件。对我来说似乎是一场胜利。或者至少如果您无论如何都不必编辑新的模型文件。

有几种方法可以解决这个问题,但它们似乎工作量太大,无法避免每隔一段时间更改一个文件。但是,如果您对自己修改 Task 以添加到多态关系中抱有死心,那么这是我的建议:

保留一个子目标列表,我将建议在 lib/subtargets 中格式化每行一个条目,本质上是 table_name.underscore。(大写字母有一个下划线前缀,然后一切都变成小写)

store
software
office
vehicle

创建 config/initializers/subtargets.rb 并用以下内容填充它:

SubtargetList = File.open("#{RAILS_ROOT}/lib/subtargets").read.split.reject(&:match(/#/)).map(&:to_sym)

接下来,您将要创建自定义生成器或新的 rake 任务。生成新的子目标并将模型名称添加到上面定义的子目标列表文件中。您可能最终会做一些简单的事情来进行更改并将参数传递给标准生成器。

抱歉,我现在不想带您了解这些内容,但这里有一些 资源

最后用 SubtargetList 替换 has_many_polymorphs 声明中的列表

class Task < ActiveRecord::Base
  has_many_polymorphs :targets, :from => SubtargetList
end

从此时起,您可以添加一个新的子目标

$ script/generate subtarget_model home

一旦您重新加载控制台或重新启动生产服务器,这将自动更新您的多态列表。

正如我所说,自动更新子目标列表需要做很多工作。但是,如果您确实走这条路,您可以调整自定义生成器,以确保在生成它时子目标模型的所有必需部分都在那里。

于 2009-10-11T07:42:30.443 回答
1

使用性病:

class Task < ActiveRecord::Base
end

class StoreTask < Task
  belongs_to :store, :foreign_key => "target_id"
end

class VehicleTask < Task
  belongs_to :vehicle, :foreign_key => "target_id"
end

class Store < ActiveRecord::Base
  has_many :tasks, :class_name => "StoreTask", :foreign_key => "target_id"
end

class Vehicle < ActiveRecord::Base
  has_many :tasks, :class_name => "VehicleTask", :foreign_key => "target_id"
end

在您的数据库中,您需要: Task type:stringTask target_id:integer

优点是现在您可以为每种特定的任务类型提供一个贯穿模型。

另请参阅STI 和多态模型

干杯!

于 2012-01-12T22:11:38.910 回答
0

这可能不是一个特别有用的答案,但简单地说,我认为没有一种简单或自动的方法可以做到这一点。至少,不像简单的一对一或多对关联那么容易。

我认为为连接表创建一个 ActiveRecord 模型是解决问题的正确方法。正常has_and_belongs_to_many关系假定两个指定表之间存在连接,而在您的情况下,听起来您想在 , , 或中的任何一个之间连接(顺便说tasks一句,这里有理由不使用 STI 吗?看起来像将通过限制您拥有的表格数量来帮助降低复杂性)。因此,在您的情况下,连接表还需要知道所涉及的子类的名称。就像是storessoftwaresofficesvehiclesTarget

create_table :targets_tasks do |t|
  t.integer :target_id
  t.string :target_type
  t.integer :task_id
end

然后,在您的Task类、Target子类和类中,您可以使用ActiveRecord::Associations::ClassMethods rdoc pages中记录的关键字TargetsTask设置has_many关联。:through

但是,这只能让您部分获得成功,因为您:through不知道使用该target_type字段作为Target子类名称。为此,您可能可以编写一些自定义选择/查找 SQL 片段,也记录在ActiveRecord::Associations::ClassMethods中。

希望这能让你朝着正确的方向前进。如果您找到完整的解决方案,我很乐意看到它!

于 2009-07-14T22:52:04.320 回答
0

您是否采用过这种蛮力方法:

class Task 
  has_many :stores
  has_many :softwares
  has_many :offices
  has_many :vehicles

  def targets
    stores + softwares + offices + vehicles
  end
  ...

它可能没有那么优雅,但老实说,它并没有那么冗长,而且代码本身并没有什么低效的地方。

于 2009-09-26T03:32:00.800 回答
0

我同意其他人的观点,我会寻求一种使用 STI 和授权混合的解决方案,这样更容易实施。

问题的核心是在哪里存储 Target 的所有子类的记录。ActiveRecord 通过 STI 模型选择数据库。

您可以将它们存储在 Target 中的类变量中,并使用继承的回调向其中添加新的。然后,您可以从该数组的内容中动态生成所需的代码并利用 method_missing。

于 2009-08-22T08:41:47.433 回答