has_one
一对一关系:有 A 和 B,A 拥有且仅拥有一个 B
belongs_to
一对一关系:有 A 和 B,A 属于且仅属于一个 B
has_one :through
嵌套式一对一关系:A 拥有一个 B,B 拥有一个 C,那么 A 通过 B 拥有一个 C
在 has_one 和 belongs_to 之间进行选择
当你要在两个模型之间建立一对一关系时,你需要为其中一个模型添加 has_one
,而为另一个添加 belongs_to
,那么如何做出正确的选择呢?
关键之处就在于你把主键安排给哪个模型(拥有主键的模型应该添加 belongs_to
),不过在此之前还应该认真考虑一下在现实中的合理性。
has_one
关系意味着某样东西(实例化的对象)是你的。比如,我们会说“一个用户拥有一个账号”,而不是“一个账号拥有一个用户”。那么在这里,主键应该安排在账号模型内,也就是说“账号属于(belongs_to
)模型”:
class User < ActiveRecord::Base
has_one :account
end
class Account < ActiveRecord::Base
belongs_to :user
end
与之对应的 migration 即是:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.timestamps
end
create_table :accounts do |t|
t.integer :user_id
# t.references :user_id 是一样的
t.string :account_name
t.timestamps
end
end
end
has_many
一对多关系:有 A 和 B,A 拥有零个或多个 B
has_many :through
多对多关系:有 A 和 C,(通过 B)产生多个 A 与 多个 C 之间的两两关系
嵌套式多对多关系
还有一种多对多关系:A 拥有多个 B,B 拥有多个 C,那么 A 通过 B 拥有多个 C
class Article < ActiveRecord::Base
has_many :sections
has_many :paragraphs, through: :sections
end
class Section < ActiveRecord::Base
belongs_to :article
has_many :paragraphs
end
class Paragraph < ActiveRecord::Base
belongs_to :section
end
此时,Rails 可以明白这样的关系:
@article.paragraphs
has_and_belongs_to_many
直接多对多关系:每一个 A 拥有或属于多个 B,每一个 B 属于或拥有 多个 A
在 has_and_belongs_to_many 和 has_many :through 之间进行选择
Rails 为我们提供了两种多对多的模型关系,它们的区别在于其中间关系是隐性的还是显性的。
has_and_belongs_to_many
是一种直接化的多对多关系,不需要单独建立中间关系的模型(但是中间关系表需要人工创建),所以是隐性的。而 has_many :through
则需要创建中间关系的模型。
所以,选择哪一种的关键之处就在于你是否需要对中间关系进行进一步的控制,比如说添加额外属性和模型验证,或者添加回调方法等等;换句话说就是要看中间关系是否是业务逻辑里需要出现的实体。
polymorphic
多态关系:有 A, B 和 C,为 A 定义一个接口,于是 B 可以拥有 多个 A,C 也可以拥有多个 A
此时,Rails 将理解以下关系:
@employee.pictures
# or
@product.pictures
并且可以通过 @picture.imageable
访问 Picture
实例的父级关系,当然你需要指明数据库里的类型字段:
class CreatePictures < ActiveRecord::Migration
def change
create_table :pictures do |t|
t.string :name
t.integer :imageable_id
t.string :imageable_type
# 以上两句可以简化为:t.references :imageable, polymorphic: true
t.timestamps
end
end
end
Self Joins
有时候你会发现一个模型需要拥有针对自身的关系,比如说你想要把所有的员工保存在一张表里,并且能够跟踪他们之间的相互关系(经理、下属),此时你可以使用 self-joining:
class Employee < ActiveRecord::Base
has_many :subordinates, class_name: 'Employee', foreign_key: 'manager_id'
belongs_to :manager, class_name: 'Employee'
end