ActiveRecord - 资料表关联
在「ActiveRecord - 基本操作与关联设计」一章我们已经有了关联设计的基本概念,这一章我们将进一步深入了解细节的设定,以及多型关联设计。
在关联的集合上,我们有以下方法可以使用:
- «(*records) and create
- any? and empty?
- build and new
- count
- delete_all
- destroy_all
- find(id)
- ids
- include?(record)
- first, last
- reload
例如:
has_many 的设定
可以变更关联的类别名称,例如以下新增了关联,和另一个has_many :attendees
都关联到同一个_attendees table:
class Event < ApplicationRecord
has_many :attendees
has_many :paid_attendees, :class_name => "Attendee"
#...
end
foreign_key
可以变更Foreign Key的字段名称,例如改成paid_user_id
:
class Event < ApplicationRecord
belongs_to :paid_user, :class_name => "User", :foreign_key => "paid_user_id"
#...
end
scope
在第二个参数传入匿名函式,可以设定关联的范围条件,例如:
class Event < ApplicationRecord
has_many :attendees
#...
end
这个语法跟我们之前学过的Arel串接写法是一样的,所以可以继续串接加上排序等其他条件:
class Event < ApplicationRecord
has_many :attendees
has_many :paid_attendees, -> { where(:status => "paid").order("id DESC") }, :class_name => 'Attendee'
#...
end
可以设定当物件删除时,怎么处理依赖它的资料,例如:
class Event < ApplicationRecord
has_many :attendees, :dependent => :destroy
end
其中:dependent
可以设定有几种不同的处理方式,例如:
:destroy
把依赖的attendees也一并删除,并且执行Attendee的destroy回呼:delete
把依赖的attendees也一并删除,但不执行Attendee的destroy回呼:nullify
不会帮忙删除attendees,但会把attendees的外部键event_id
都设成NULL
:restrictwith_exception
如果有任何依赖的_attendees资料,则连event都不允许删除。执行删除时会丢出错误例外ActiveRecord::DeleteRestrictionError
。:restrict_with_error
不允许删除。执行删除时会回传false
,在@event.errors
中会留有错误讯息。
如果没有设定:dependent
的话,就不会特别去处理。
through
class Event < ApplicationRecord
has_many :event_groupships
has_many :groups, :through => :event_groupships
end
source
搭配through
设定使用,当关联的名称不一致的时候,需要加上指名是哪一种物件。
class Event < ApplicationRecord
has_many :event_groupships
has_many :classifications, :through => :event_groupships, :source => :group
end
has_one 的集合物件
多了两个方法可以新增关联物件:
build_{association_name}
create_{association_name}
例如:
classname
、dependent
、_scope条件等设定,都和has_many一样。
belongs_to 的设定
在 Rails 5.1 之后的版本,belongs_to 关联的 model 默认改成必填了,也就是一定要有。透过 optional => true
可以允许 event 没有 category 的情况。
class Event < ApplicationRecord
belongs_to :category, :optional => true
end
class_name
可以变更关联的类别名称,例如:
class Event < ApplicationRecord
belongs_to :manager, :class_name => "User" # 默认的外部键叫做 manager_id
end
foreign_key
可以变更Foreign Key的字段名称,例如改成user_id
:
class Event < ApplicationRecord
belongs_to :manager, :class_name => "User", :foreign_key => "user_id"
end
这会在修改时,也顺道修改关联资料的updated_at
时间:
class Attendee < ApplicationRecord
belongs_to :event, :touch => true
end
counter_cache
针对关联作计数的快取,假设Event身上有attendees_count这个字段,那么:
class Attendee < ApplicationRecord
belongs_to :event, :counter_cache => true
end
这样ActiveRecord就会自动更新attendees_count的数字。
joins 和 includes 查询
Event.joins(:category)
# SELECT "events".* FROM "events" INNER JOIN "categories" ON "categories"."id" = "events"."category_id"
可以一次关连多个:
透过joins抓出来的event物件是没有包括其关连物件的,因为Rails默认只有select event.
而没有select categories.
,因此joins主要的用途是来搭配where的条件查询,帮忙过滤events资料:
如果需要其关连物件的资料,例如上述的categories,我们会偏好使用includes
。includes会将关连物件的资料也一并读取出来,避免N+1问题(见效能一章),例如:
Event.includes(:category)
# SELECT * FROM events
# SELECT * FROM categories WHERE categories.id IN (1,2,3...)
同理,也可以一次加载多个关连:
Event.includes(:category, :attendees)
# SELECT "events".* FROM "events"
# SELECT "attendees".* FROM "attendees" WHERE "attendees"."event_id" IN (4, 5, 6, 7, 8...)
includes
方法也可以加上条件:
Event.includes(:category).where( :category => { :position => 1 } )
多型关联(Polymorphic Associations)
多型关连(Polymorphic Associations)可以让一个 Model 不一定关连到某一个特定的 Model,秘诀在于除了整数的id
外部键之外,再加一个字串的_type
字段说明是哪一种_Model。
例如一个Comment model
,我们可以透过多型关连让它belongsto
到各种不同的 _Model上,假设我们已经有了Article与Photo这两个Model,然后我们希望这两个Model都可以被留言。不用多型关连的话,你得分别建立ArticleComment和PhotoComment的model。用多型关连的话,无论有多少种需要被留言的Model,只需要一个Comment model即可:
rails g model comment content:text commentable_id:integer commentable_type
这样会产生下面的 Migration 档案:
class CreateComments < ActiveRecord::Migration[5.1]
def change
create_table :comments do |t|
t.text :content
t.integer :commentable_id
t.string :commentable_type
t.timestamps
end
end
end
这个Migration档案中,我们用content这个字段来储存留言的内容,commentable_id用来储存被留言的物件的id而commentable_type则用来储存被留言物件的种类,以这个例子来说被留言的对象就是Article与Photo这两种Model,这个Migration档案也可以改写成下面这样:
class CreateComments < ActiveRecord::Migration[5.1]
def change
create_table :comments do |t|
t.text :content
t.belongs_to :commentable, :polymorphic => true
t.timestamps
end
end
end
回到我们的Model,我们必须指定他们的关联关系:
class Comment < ApplicationRecord
belongs_to :commentable, :polymorphic => true
end
class Article < ApplicationRecord
has_many :comments, :as => :commentable
end
class Photo < ApplicationRecord
has_many :comments, :as => :commentable
end
这样会告诉Rails如何去设定你的多型关系,现在让我们进console实验看看: