Rails 5 - 对象关系阻抗以及如何构造多重继承 classes/tables

Rails 5 - Object Relation Impedence and how to structure multiple inherited classes/tables

编辑 为了便于理解,我对原文进行了编辑。


我了解对象关系阻抗问题。我了解 Rails STI 和多态性(Rails 方式并且它不是真正的 OO 多态性)。我已经阅读了大量的博客和问题,但仍然找不到这个问题的答案。

class Person < ApplicationRecord (ie what was ActiveRecord::Base)
end

class Employee < Person
end

class Customer < Person
end

...多种其他类型的人

现在假设 'client' 要求扩展系统,并创建一些新内容。我们将其称为项目,我们可以为其分配员工:

好的,让我们使用第三范式创建多对多:

class Project < ApplicationRecord
  has_many :assignments
  has_many :employees, through: :assignments  
end

class Employee < Person
  has_many :assignments
  has_many :projects, through: :assignments
end

class Assignment < ApplicationRecord
  belongs_to :employee
  belongs_to :project
end

这行不通。迁移将失败,因为没有 table 调用 Employee 来创建外键约束。 STI 意味着 'base class' 是人民 table.

两个问题:

1 你是怎么解决这个问题的? (为此你可能还想加入 here

2 如何为项目正确创建序列化数据(应包括员工,但不包括人员或人员的其他子类型)?

ProjectSerializer < ActiveModelSerializers

  has_many :employees
  has_many :employees, through: :assignments

end

行不通,所以你必须连载人物。


更新


在迁移中我创建了 Project 和 Assignment tables(Person 已经存在)。

所以现在数据库有这些 tables:

Project
Person
Assignment

Assignment 有两个外键(引用 Person,因为那是存在的 table,而不是 Employee):

person_id   
project_id

每次我尝试创建作业时,都会抛出此错误,这当然是我意料之中的:

ActiveModel::UnknownAttributeError (unknown attribute 'employee_id' for Assignment.)

根据 Rails 文档 (section 4.1.2.5) 和我在任何地方都能找到的针对这种情况的所有其他答案的解决方案是告诉 Rails foreign_key 是什么。像这样:

class Assignment < ApplicationRecord
  belongs_to :employee, foreign_key: "person_id"
  belongs_to :project
end

但是我找到的每个示例(甚至在文档中)都假设没有继承 - 所有模型都继承自 ActiveRecord:Base(或 [中的 ApplicationRecord) =86=] 5).

即使我明确告诉 Rails 作业 table 有一个名为 'person_id' 的 foreign_key 保存员工的 ID,它仍然找不到它.

最后我尝试了这个(感谢@camonz 的回答)

class Assignment < ApplicationRecord
  belongs_to :person, foreign_key: "person_id", foreign_type: "employee"
  belongs_to :project
end

同样的错误。

这真的是Rails无法处理的模型设置吗?

这是 Rails 日志:

I, [2016-09-22T22:54:55.088466 #12182]  INFO -- : Started POST "/assignments" for ::1 at 2016-09-22 22:54:55 +0200
I, [2016-09-22T22:54:55.095768 #12182]  INFO -- : Processing by AssignmentsController#create as JSON
I, [2016-09-22T22:54:55.096007 #12182]  INFO -- :   Parameters: {"data"=>{"attributes"=>{"status"=>"pending"}, "relationships"=>{"project"=>{"data"=>{"type"=>"projects", "id"=>"601"}}, "employee"=>{"data"=>{"type"=>"employees", "id"=>"143"}}}, "type"=>"assignments"}, "assignment"=>{}}
I, [2016-09-22T22:54:55.098032 #12182]  INFO -- : {:status=>"pending", :project_id=>"601", :employee_id=>"143"}
I, [2016-09-22T22:54:55.117411 #12182]  INFO -- : Completed 500 Internal Server Error in 21ms (ActiveRecord: 8.8ms)


F, [2016-09-22T22:54:55.119116 #12182] FATAL -- :   
F, [2016-09-22T22:54:55.119246 #12182] FATAL -- : ActiveModel::UnknownAttributeError (unknown attribute 'employee_id' for Assignment.):
F, [2016-09-22T22:54:55.119283 #12182] FATAL -- :   
F, [2016-09-22T22:54:55.119313 #12182] FATAL -- : app/controllers/assignments_controller.rb:18:in `create'
  1. 在迁移中,删除 addressses table 上的 FK 约束。在 child 类 上重新定义 has_many 关系并指定 :foreign_key & :foreign_type.

  2. 在您的 Assignment 序列化器上,指定 belongs_to :employee,AMS 应该会正确处理它。

    另请查看 has_many 关联的 :source:source_type 选项。

我找到了第一个问题的解决方案。保留问题中的模型设置,我更改数据库以查看它是否有效。

我更改了作业 table,因此 foreign_key 现在称为 'employee_id',因为 Rails 似乎想要坚持这一点。

然后我更改了约束,现在显示为:

ALTER TABLE public.assignments
  ADD CONSTRAINT fk_rails_52f37556f9 FOREIGN KEY (employee_id)
      REFERENCES public.people (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION;

然后代码就可以工作了——但当然我现在必须编写一个迁移脚本来创建这样的约束。

原始迁移不起作用,因为它创建了 'person_id':

  t.references :person, foreign_key: true

This answer 明确表示 Rails 不正确支持外键。 叹息

此解决方案的另一个问题是,如果 Person 的其他子类型在将来的某个时候也需要分配给 Projects,则它们不能。所以真的不是一个很好的解决方案。最好留下与 Person 直接相关的作业。

我会回答你的第一个问题,因为它对我来说是主要问题。对我来说,当它要求员工 table 进行迁移时,有一个线索。

This won't work. The migration will fail, since there is no table called Employee to create the foreign-key constraints on. STI means that the 'base class' is the People table.

这会向我表明您关于人员的架构 table 缺少 "type" 列名称,该名称将存储对象的 class 名称并触发 STI 行为Rails。

STI means that the 'base class' is the People table.

有点,但您的错误表明发生的一切都是正常的 ruby 继承。所以你基本上在 STI 中有 I 但没有 ST 部分。

STI 的重点不只是继承父 class,这是正常的 ruby 继承,重点是多个 class 使用单个 table 在数据库中,因为它们本质上是相同的模式。然而,它需要看似不可见的 "type" 列名来触发数据库中 STI 的 Rails 约定。没有这个它的正常 ruby 继承。

注意:过去曾走过这条路,我会警告你,这条路是疯狂的。特别是当 table 是自我引用时,正如您在这里提到的外键,即相同 table 的外键。

当你进入第 2 步时要小心,因为如果你太急切地开始调用关联,你正在设置一个无限的参考可能性(竞争条件)或者至少是 Big O 查找。例如,JSON 可以为每个人建立员工关联。因此,它必须多次调用相同的 table "people" 来构建每个员工的员工,即它一直向下成为员工。 这会很慢。