iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 9
0
Modern Web

關於 Ruby on Rails,我想說的是系列 第 14

[Day 14] 資料表關聯,以一對多為例

  • 分享至 

  • xImage
  •  

昨天建立了Product Model,今天要繼續建立商品款式 ProductStyle Model ,並讓這兩個 Model 產生一對多的關聯(一個product可以有多個product_style )

大綱

  • 資料表關聯
  • 一對多(has_many)
    • 建立資料表
    • has_many
      1.建立商品並查看它的商品款式
      2.來建立關聯吧
      • 使用create方法建立 product1 的商品款式 style1
      • 使用 product_styles=方法建立商品款式 style2
  • belongs_to
  • Foreign key
    • belongs_to 的 foreign_key 為必填
  • 客製化has_many
    1.增加條件
    2.改變關聯名稱

資料表關聯

Model之間的關聯,主要有三種

  • 一對一 (has_one)
  • 一對多 (has_many)
  • 多對多 (has_many ... through ...)

因為has_onehas_many性質差不多,而且實務上has_many最常用,今天就只介紹has_many (ㄧ對多)

一對多(has_many)

建立資料表

昨天透過:

rails generate model product title price:float description:text 

產生了products這個 model,又在migration修改了一些欄位屬性

今天一樣的起手式,先產生ProductStyle Model

rails generate model product_style title price:float sku product_id:integer 

上面這段code,多了sku商品編號,還有product_id。其中product_id是為了讓belongs_tohas_many關係成立,而設置的外部鍵(foreign key)欄位,這時關聯還沒建立,要在model檔案設定後才真的建立好關聯。

一樣記得跑rake db:migrate,就建立好product_style資料表了。

打開schema.rb來看:

create_table "product_styles", force: :cascade do |t|
  t.string "title"
  t.float "price"
  t.string "sku"
  t.integer "product_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

成了!

has_many

一對多關聯,畫圖給大家參考:

app/models/product.rb,加上類別方法has_many

class Product < ApplicationRecord
  has_many :product_styles
end

設定has_many :product_styles後會多了以下幾個方法:

  • product_styles
  • product_styles=
  • build
  • create

建立商品並查看它的商品款式

product1 = Product.create(title: '果乾', price: 995)
#=> #<Product id: 3, title: "果乾", price: 995.0, description: "要買要快", created_at: "2019-09-29 23:57:54", updated_at: "2019-09-29 23:57:54"> 
styles = product1.product_styles
# => #<ActiveRecord::Associations::CollectionProxy []>
styles.size #=> 0

如果不加has_many :product_styles,第二行就會噴錯undefined method 'product_styles'
又因為product 1 還沒有任何商品款式,所以第三行回傳0,

來建立關聯吧

一.使用create方法建立 product1 的商品款式 style1
style1 = product1.product_styles.create(title: '芒果乾', price: 1450)
# => #<ProductStyle id: 1, title: "芒果乾", price: 1450.0, sku: nil, product_id: 3, created_at: "2019-09-30 00:14:05", updated_at: "2019-09-30 00:14:05"> 

外部鍵product_id自動帶入3(product1的id)。外部鍵的作用就是當我輸入

product1.product_styles

會先產生一段SQL:

# =>   ProductStyle Load (0.3ms)  SELECT  "product_styles".* FROM "product_styles" WHERE "product_styles"."product_id" = $1 LIMIT $2  [["product_id", 3], ["LIMIT", 11]]

這段SQL目的是在product_styles資料表裡,找出product_id欄位跟product1一樣是3的每一筆資料。

二. 使用product_styles= 方法建立商品款式 style2
style2 = ProductStyle.new(title: '香蕉乾', price: '462')
# => #<ProductStyle id: nil, title: "香蕉乾", price: 462.0, sku: nil, product_id: nil, created_at: nil, updated_at: nil> 
product1.product_styles=[style1, style2]
# => [#<ProductStyle id: 1, title: "芒果乾", price: 1450.0, sku: nil, product_id: 3, created_at: "2019-09-30 00:14:05", updated_at: "2019-09-30 00:14:05">, #<ProductStyle id: 2, title: "香蕉乾", price: 462.0, sku: nil, product_id: 3, created_at: "2019-09-30 00:26:57", updated_at: "2019-09-30 00:26:57">]

belongs_to

如果需要透過product_styles 裡的資料來反查相對應的products資料,我們要在app/models/product_style.rb加上belongs_to方法。

class ProductStyle < ApplicationRecord
  belongs_to :product
end

如此一來在console就可以找到style1是屬於哪個商品的款式

style1.product
# => #<Product id: 3, title: "果乾", price: 995.0, description: "要買要快", created_at: "2019-09-29 23:57:54", updated_at: "2019-09-29 23:57:54"> 

Foreign key

Foreign key 幫助找出其他資料表關聯的資料

一年前面試的時候,被問到Foreign key該放在哪個table?
products 還是product_styles ?

記住口訣:有Foreign Key的Model,就是設定belongs_to的Model。
剛才是product_style belongs_to product,所以是放在ProductStyle Model喔

belongs_to的foreign_key為必填

在Rails 5.1以後,如果是belongs_to的Model,每一筆資料存入DB前,必須要有foreign key,不然會噴錯。像這樣:

style2 = ProductStyle.create!(title: '香蕉乾', price: '462')
# => ActiveRecord::RecordInvalid (Validation failed: Product must exist)

所以前面的style2先用.new來建立,這時還沒有存入DB,指定給product1才真正存進去。或是在create時指定product_id3

style3 = ProductStyle.create!(title: '香蕉乾', price: '462', product_id: 3)

客製化has_many

has_many還有一些方便的參數,可以讓每次查找更有效率

1.增加條件

原本

product1.product_styles

順序會是流水編號id小的在前面,改成用order指定id順序大排到小:

class Product < ApplicationRecord
  has_many :product_styles, ->{ order("id DESC") }
end

參數->{ order("id DESC") }是lambda,之後會介紹。

也可以串連where條件:

class Product < ApplicationRecord
  has_many :product_styles, ->{ where(["created_at > ?", Time.now - 7.days]).order("id DESC") }
end

2.改變關聯名稱

覺得每次要找product的款式都要輸入.product_styles,很麻煩,也可以使用class_name參數,has_many 的第一個參數改用:styles

class Product < ApplicationRecord
  has_many :styles, class_name: 'ProductStyle'
end

上一篇
[Day 13] Model 生成與資料庫遷移
下一篇
[Day 15] 效能殺手 N+1 Query
系列文
關於 Ruby on Rails,我想說的是23
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言