ActiveRecord-连接多张表之单表继承
1. 基本概念
Rails提供了两种机制,可以将复杂的面向对象模型映射为关系模型,即所谓的单表继承(single-table inheritance)和多态关联(polymorphic associations,也有人称为多表继承)。
2. 单表继承
在使用面向对象开发时,经常会用到类和继承,如应用程序中涉及不同角色的人员(People):顾客(Customer)、员工(Employee)和经理(Manager)等等。其中有一些属性是共有的,另一些属性是特有的。因此,创建模型:Customer类和Employee类,且都是People的子类,Manager类是Employee的子类。子类会继承父类的所有属性和行为。
这些都是代码中的模型体现,在关系型数据库是如何体现的?见如下代码:
# model的定义
class People < ActiveRecord::Base
end
class Customer < People
end
class Employee < People
belongs_to :boss, class_name: 'Employee', foreign_key: 'reports_to'
end
class Manager < Employee
end
# 迁移文件的定义,即关系型数据库的体现
create_table :people, :force => true do |t|
t.column :type, :string
# common attributes
t.column :name, :string
t.column :email, :string
# attributes for type=Customer
t.column :balance, :decimal, :precision => 10, :scale => 2
# attributes for type=Employee
t.column :reports_to, :integer
t.column :dept, :integer
# attributes for type=Manager
# none
end
# 添加人员
boss = Manager.create({name: 'Jobs', email: 'jobs@apple.com', dept: 1})
empl = Employee.new({name: 'David', email: 'david@apple.com', dept: 2})
empl.boss = boss
empl.save
user = Customer.create({name: 'Jim', email: 'jim@rails.com', balance: 100})
由上面的代码可以看出类定义中使用了继承,而对于不同角色的人员,均保存于people表,对于boss和empl对象,都是没有user的balance属性的,即这两个对象的balance字段都是null,而对于user对象的dept和reports_to字段同样是null,这样就实现了一个单表继承,相比于我们使用各种其它方法会简单很多。但是问题是:
2.1 ActiveRecord如何如此简单的提供了单表继承?
通过迁移文件中可以看到,在people表中添加了type字段。通过上图也可以看到,每条记录的type字段都有值,而且为用户的角色。ActiveRecord是约定好了使用type字段来描述对象所属的类型。
针对此问题,可以详见Martin Fowler在《企业应用架构模式》的介绍。
2.2 如果一个新加入的开发者使用People添加了人员呢?
是的,也许一个新加入项目组的同学使用People添加了人员,即使是测试,也会感觉到代码不是那么严谨,例如:
god = Person.create({name: 'God', email: 'god@god.god'})
发生了什么?God真的是神啊,type为空,所以,我们一定不想在表中见到神,按如下三种方法均可以解决:
- 在People中实现一个名为abstract_class?的类方法,并使其返回true,这样,就可以达到目的了。不过它带来的问题是:1.ActiveRecord永远不会尝试寻找对应于抽象类的数据库表,这是对我们有利的,2.抽象类的子类会被当作各自独立的ActiveRecord模型类,即各自映射到一张独立的数据库表。这就达不到我们对公共属性抽取的目的了。这种方法不完美
- 使用Ruby模块来包含这些需要共享的功能,然后将模块混入所谓的子类。这是书中提供的方法,也感觉不完美,没有了继承的感觉
- 能否在父类中添加什么使其只可读不可写呢?
2.3 单表继承的优点和缺点
单表继承中所有的属性都存在于一张表中,这样真的好吗?如果子类存在的差异较大,且属性数据较大,如果仍然存在于同一张表,就会产生很多问题。这时可以学习多态继承了。
5. 参考
- 《Web开发敏捷之道》第2版#P341-连接多张表