Active Record 關聯式設計

ActiveRecord是一種ORM

ORM(Object-Relational-Mapping)讓我們可以利用物件導向的概念來操作關聯式資料庫,不需要透過繁瑣的SQL語法就能達成同樣的效果,對於維護程式也是比較容易的。但根據 抽象滲漏法則 所述:所有重大的抽象機制在某種程式上都是有漏洞的。ActiveRecord簡化了我們操作SQL的方便性,但相對的其效率就不會比直接使用SQL來得高,且可能也有它所無法涵蓋的範圍,甚至是漏洞。但站在網頁開發的角度,能夠快速建立網頁app以及易於處理複雜機制才是比較首要的,因此使用rails所提供的各種便利確實有其必要性。但永遠要記得,「唯一能適當處理漏洞的方法,就是弄懂該抽象原理以及所隱藏的東西。」
ActiveRecord將Ruby物件導向的概念套用到資料庫上運作,以下是其對應關係:

  • class(a Model class) => table
  • instances(a Model object) => rows
  • attributes => columns
  • class methods => operating the table
  • instance methods => operating the row

關聯式資料庫基本操作

一對多關聯 one-to-many

one-to-many

以活動分類Category與活動Event來表示一對多關係。一個活動分類下可以有很多個活動,但一個活動只能被歸類在一個活動分類之下。

建立Model

1
2
3
4
# 在Terminal運行
rails g model category name:string # 建立Category model
rails g model event name:string date:date category_id:integer # 建立Event model
rails db:migrate # 記得資料庫遷移

如此一來就建立了Event與Category兩個model,在Event內我們建立了category_id作為foriegn key,用來連接兩個Model。但目前為止,我們還沒定義出兩者的關聯,還需要在兩個model中加入一些設定:

1
2
3
4
5
6
7
class Category < ApplicationRecord
has_many :events # 記得要複數
end

class Event < ApplicationRecord
belongs_to :category, optional => true # 記得要單數
end

has_many表達了此Model有一對多關係,而belongs_to表達了此Model有多對一關係。 optional => true可以允許event沒有任何category的情況

建立Event物件

假設已經建立了2個Category,若要再建立幾個與Category相關的Event物件,可以有以下作法

  1. 建立Event物件,並關聯到Category
    1
    2
    3
    4
    5
    6
    a = Event.new(name: "BTD party", category: Category.first)
    # 或是 a = Event.new(name: "BTD party", category_id: Category.first.id)
    a.save

    b = Event.create(name: "Picnic")
    Category.first.events << b
  2. 從Category中建立一個Event物件
    1
    2
    3
    4
    5
    x = Category.first.events   # 回傳一個Array
    c = x.build(name: "KTV")
    c.save

    d = x.create(name: "Tennis game") # 不需再save

    查找物件

    1
    2
    3
    4
    5
    x = Category.first
    a = x.events.find(3) # 查詢第一個分類下的活動中的第三個
    b = x.events.where(name: "KTV") # 1.查詢第一個分類中,叫作KTV的活動
    # 2.回傳一個包含所有符合資格的資料的陣列,如果要找出其中的第一項,
    # 就輸入 b[0]

    刪除物件

    1
    2
    3
    x = Category.first
    x.events.destroy_all # 一筆一筆刪除,並觸發每個event的destroy callback
    x.events.delete_all # 一次刪除所有,不會觸發event的destroy callback

一對一關聯 one-to-one

one-to-one

上圖的例子為,一個活動會對應到一個場地,而在此我們限定一個場地只能辦一場活動,因此屬於一對一關係。
為什麼不把location列為Event的其中一個attribute就好?為什麼需要一對一關聯?
因為有些attribute可能並不是每個row都有,或是他只有在較少的情況需要被查詢,那麼我們就不需要找每筆資料時都一定要連這些attribute一起找出來,這時就可以考慮將他們列為一對一關聯。

建立Model

1
2
3
# 在Terminal運行
rails g model location name:string address:string event_id:integer # 建立Location model
rails db:migrate # 記得資料庫遷移

Foriegn key要建立在哪一邊都可以。另外,一對一關聯同樣要建立關聯,才能在兩個Model之間心增一些方法來使用:

1
2
3
4
5
6
7
class Event < ApplicationRecord
has_one :location # 記得要單數
end

class Location < ApplicationRecord
belongs_to :event # 記得要單數
end

可以將一對一關聯視作一對多關聯的特例情況,所以新增、操作的方法都與一對多相同。

多對多關聯 many-to-many

many-to-many

一個活動可以有多位參加者,且一個參加者也能參與多項活動,此為多對多關係。多對多的情況,需要在中間建立一個join table,並將兩者的foriegn key建立在此join table內,專門負責處理兩項多對多資料的關聯。

建立Model

1
2
3
4
# 在Terminal運行
rails g model attendee name:string # 建立Attendee model
rails g model event_attendee event_id:integer attendee_id:integer # 建立join table
rails db:migrate # 記得資料庫遷移

再來是建立關聯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Event < ApplicationRecord
has_many :event_attendees # 記得要複數
has_many :attendees, :through => :event_attendees
end

class EventAttendee < ApplicationRecord
belongs_to :event # 記得要單數
belongs_to :attendee # 記得要單數
end

class Attendee < ApplicationRecord
has_many :event_attendees # 記得要複數
has_many :events, :through => :event_attendees
end
:through =>可以將兩個資料表建立起多對多關聯

操作

1
2
3
4
5
6
7
8
9
10
11
12
13
# 先建立物件
a1 = Attendee.create(name: "Lin")
e1 = Event.create(name: "BTD party", date: "2017/5/3")
e2 = Event.create(name: "Picnic", date: "2017/11/23")

# 從join table建立兩者個關聯
EventAttendee.create(event: e1, attendee: a1)
EventAttendee.create(event: e2, attendee: a1)
# 表示a1參加者參加了e1與e2活動

# 查詢
a1.events # 查找a1參加的所有活動
e1.attendees # 查找參加e1的所有參加者

參考連結

  1. Ruby on Rails實戰聖經 - ActiveRecord 基本操作與關聯設計