關聯表練習
練習1-1、1-多、多-多的關聯表
今天來建造三個表單
首先是一對一的has_one
何謂一堆一呢 用店長、商店來舉例(我們先假設今天店長手頭比較緊,只會有一間商店)
一個店長只會有一間店 一間店只會有一個店長
has_one 一對ㄧ
Owner
來先想一下店長表單有包含哪些欄位,店長model的欄位有,姓名、信箱、電話
完整的資訊就像下列表格
欄位名稱 | 資料型態 | 說明 |
---|---|---|
name | string | 姓名 |
string | ||
phone | string | 電話 |
因此我們來產生model
> rails g model Owner name email phone
Store
接著來產生商店表單,一樣先想店家會有哪些欄位,應該會有店家姓名、地址、電話 還有一個最重要的,跟店長(owner_id)的連結 一樣用表格來顯示清楚一點
欄位名稱 | 資料型態 | 說明 |
---|---|---|
title | string | 店名 |
address | string | 地址 |
phone | string | 電話 |
owner_id | string | 這家店的店長 |
整理完後,來產生 model
> rails g model Store title address phone owner:references
兩個都產生完後,記得讓他具現化,也就是輸入
> rails db:migrate
此時我們先來看到schema檔案,這裡有存放所有表格的真實情況
這裡可以看到,我們剛剛在store增加的owner:references欄位,他多了幾行額外的東西
> ActiveRecord::Schema.define(version: 2022_11_16_025143) do
>
> create_table "owners", force: :cascade do |t|
> t.string "name"
> t.string "email"
> t.string "phone"
> t.datetime "created_at", precision: 6, null: false
> t.datetime "updated_at", precision: 6, null: false
> end
>
> create_table "stores", force: :cascade do |t|
> t.string "title"
> t.string "address"
> t.string "phone"
> t.integer "owner_id", null: false # owner:references 增加的東西
> t.datetime "created_at", precision: 6, null: false
> t.datetime "updated_at", precision: 6, null: false
> t.index ["owner_id"], name: "index_stores_on_owner_id" # owner:references 增加的東西
> end
>
> add_foreign_key "stores", "owners" # owner:references 增加的東西
> end
寫進table後,還要記得做一件事,到個表單的model,寫上關聯
首先我們先到store寫上關聯
> store 的 model
> belongs_to :owner # 剛剛 owner:references 指令幫我們做的
> 他會讓store new出來的實體產生兩種方法
> (1)owner
> (2)owner=
接著到owner寫上關聯
> owner 的 model
> has_one :store # 這一行要自己寫
> 上面那一行的意思是,讓owner產生的新實體,新增四種方法
> (1) store
> (2) store=
> (3) create_store
> (4) build_store
不過我們剛剛生出了一堆方法,那些方法是什麼?會用在哪裡?我們實際舉個例子看看
> rails c # 先到rails c的環境
今天我想先新增一個新老闆(owner)
> o1 = Owner.new(name: "o1")
> o1.save # true
> Ps. 用new方法產生物件,他只會暫時存在記憶體,記得.save才能寫進資料庫
接著新增一間商店
> s1 = Store.new(title: "s1")
> s1.save # false, 此時.save會發生錯誤,原因我們可以把它印出來看看
> s1.errors.full_messages # ["Owner must exist"], 系統說此欄位必須存在
會發生上面的錯誤的原因,就是因為我們一開始在設定關聯表的時候
store在寫入資料庫前,一定要有店長(沒有店長,商店怎麼開得成呢!!!?)
因此沒有店長的情況下,當然無法.save,那要怎麼解決這件事
第一個解決方法 -> 在new實體的時候,直接帶店長給他
> s1 = Store.new(title: "s1", owner: o1) # 開新店時,直接帶一個店長給他
> s1.save # true, 這樣就成功寫進資料庫了
第二個解決方法 -> 開好一間店後,在.save前,指派一位店長給這間店
> s1 = Store.new(title: "s1") # 創建一間店
> o1 = Owner.create(name: "o1") # 新增一個店長
> s1.owner = o1 # 指派一位店長給這一間商店
> s1.save # 指派完後,記得寫進資料庫
第三個解決方法 -> 直接從店長的角度,開一間新的店
這是什麼意思呢???我們直接來寫看看
> o1.create_store(title: "s1") # 直接由一號店長創造一間店
–
build_store Vs. create_store
build跟new一樣,都是先暫存在記憶體位置,要.save後,才能存進資料庫
如果今天是用create,會直接把.save一起做完,直接存進資料庫
–
–
rails db:rollback step=2
由於現在資料庫有點多,直接用rollback把資料清掉
Ps. 要記得使用rollback會清掉資料庫,由於現在是在練習,所以才會使用,工作上盡量不要使用rollback
–
has_many 一對多
剛剛我們做好了店長和商店了,現在我們來做產品
一家店會有很多產品對吧!!所以我們現在來新增產品model,並讓產品跟商店的id有連結
一樣先來想一下產品的表格
欄位名稱 | 資料型態 | 說明 |
---|---|---|
name | string | 商品名稱 |
description | text | 商品描述 |
price | decimal | 商品價格 |
store_id | integer | 商店編號 |
設定好後,我們來產生產品的model
> rails g model name description:text price:decimal store:references
輸入好後,記得要具現化表格
> rails db:migrate
現在來看一下schema的變化狀況
> ActiveRecord::Schema.define(version: 2022_11_16_045940) do
>
> create_table "owners", force: :cascade do |t|
> t.string "name"
> t.string "email"
> t.string "phone"
> t.datetime "created_at", precision: 6, null: false
> t.datetime "updated_at", precision: 6, null: false
> end
>
> create_table "products", force: :cascade do |t|
> t.string "name"
> t.text "description"
> t.decimal "price"s
> t.integer "store_id", null: false # store:references 增加的東西
> t.datetime "created_at", precision: 6, null: false
> t.datetime "updated_at", precision: 6, null: false
> t.index ["store_id"], name: "index_products_on_store_id" # store:references 增加的東西
> end
>
> create_table "stores", force: :cascade do |t|
> t.string "title"
> t.string "address"
> t.string "phone"
> t.integer "owner_id", null: false
> t.datetime "created_at", precision: 6, null: false
> t.datetime "updated_at", precision: 6, null: false
> t.index ["owner_id"], name: "index_stores_on_owner_id"
> end
>
> add_foreign_key "products", "stores" # store:references 增加的東西
> add_foreign_key "stores", "owners"
> end
寫進table後,一樣要記得到model那邊寫上關聯
首先我們先到product寫上關聯
> product 的 model
> belongs_to :store # 剛剛 store:references 指令幫我們做的
> 他會讓product new出來的實體產生兩種方法
> (1)store
> (2)store=
接著到store寫上關聯
> store 的 model
> has_many :products # 這一行要自己寫
> 上面那一行的意思是,讓store產生的新實體,新增四種方法
> (1) products
> (2) products=
> (3) create_products
> (4) build_products
寫好了關聯,我們一樣來看看剛剛那些產生的新方法,要怎麼使用
> rails c # 先到rails c的環境
前置動作 -> 先產生一位店長、一家店,等等我們要做的事情就是把商品塞進這家店裡面
> o1 = Owner.create(name: "o1") # 產生店長
> o1.create_store(title: "s1") # 產生店家
> s1 = Store.first # s1 = 剛剛產生的店家
接著來產生商品
> p1 = Product.new(name: "p1")
> p2 = Product.new(name: "p2")
> s1.products = [p1, p2] # 一次把多個商品塞進商店
> s1.products << p1 # 也可以用這個方式,一個一個塞進去
> s1.products << p2
has_many :through 多對多
這邊我們要來改一下商店和產品間的關聯
畢竟一間店會有很多類型的產品
一個產品也一定會在很多商店一起賣(總不可能這件商品只在這一家店上架,除非這家店獨佔)
所以我們要讓兩個商品用多對多的方法來連結
不過這邊建立多對多關聯表比較特別,舉個例子好了
大家在看醫生的時候,應該知道門診人員都有一個約診簿,裡面會紀錄醫生有哪些客人,那又如果你今天只是小感冒的話,
每次去這家診所不會是同一位醫生看病,所以有機會被很多位醫生看過
把上面那一大串簡化就會是
一位醫生會有很多病人,一位病人也會有很多不同醫生看診的紀錄,而這些都記錄在約診簿上
因此我們來設計的這約診簿,不過是產品、商店的約診簿
我們幫這個約診簿取名為Book
欄位名稱 | 資料型態 | 說明 |
---|---|---|
store_id | string | 商品編好 |
product_id | text | 產品編號 |
規劃好後,開始來把model建出來
> rails g model Book store:references product:references
建好後,記得具現化
> rails db:migrate
接著我們看一下schema的樣子,剛剛創立約診簿後,變得怎麼樣了
> ActiveRecord::Schema.define(version: 2022_11_16_053913) do
>
> create_table "books", force: :cascade do |t|
> t.integer "store_id", null: false
> t.integer "product_id", null: false
> t.datetime "created_at", precision: 6, null: false
> t.datetime "updated_at", precision: 6, null: false
> t.index ["product_id"], name: "index_books_on_product_id" # product:references 增加的東西
> t.index ["store_id"], name: "index_books_on_store_id" # store:references 增加的東西
> end
>
> create_table "owners", force: :cascade do |t|
> t.string "name"
> t.string "email"
> t.string "phone"
> t.datetime "created_at", precision: 6, null: false
> t.datetime "updated_at", precision: 6, null: false
> end
>
> create_table "products", force: :cascade do |t|
> t.string "name"
> t.text "description"
> t.decimal "price"
> t.integer "store_id", null: false
> t.datetime "created_at", precision: 6, null: false
> t.datetime "updated_at", precision: 6, null: false
> t.index ["store_id"], name: "index_products_on_store_id"
> end
>
> create_table "stores", force: :cascade do |t|
> t.string "title"
> t.string "address"
> t.string "phone"
> t.integer "owner_id", null: false
> t.datetime "created_at", precision: 6, null: false
> t.datetime "updated_at", precision: 6, null: false
> t.index ["owner_id"], name: "index_stores_on_owner_id"
> end
>
> add_foreign_key "books", "products" # product:references 增加的東西
> add_foreign_key "books", "stores" # store:references 增加的東西
> add_foreign_key "products", "stores"
> add_foreign_key "stores", "owners"
> end
接著重點來了!!!!!我們要來寫關聯表的設定,因為跟前面不太一樣,所以是重點
首先我們到store的model
> store model
> has_many :books # 這一行應該沒啥疑問,因為很多商店都會存在約診簿裡
> has_many :products, through: :books
> 上面比較長的那一行意思就是,因為商店真正關心的是產品有沒有在我們商店賣,並不是約診簿,所以
> 寫上has_many :products,但是商品和產品又沒有直接關聯,所以要透過約診簿才行
首先我們到product的model
> product model # 這一行應該沒啥疑問,因為很多商店都會存在約診簿裡
> has_many :books
> has_many :stores, through: :books
> 上面比較長的那一行意思就是,因為產品真正關心的是在哪些商店被賣,並不是約診簿,所以
> 寫上has_many :stores,但是產品和商店又沒有直接關聯,所以要透過約診簿才行
前置動作 -> 先產生一位店長、一家店,等等我們來開始實際把產品塞進商店中
> o1 = Owner.create(name: "o1") # 產生店長
> o1.create_store(title: "s1") # 產生店家
> s1 = Store.first # s1 = 剛剛產生的店家
創造新商品
> p1 = Product.new(name: "p1")
> p2 = Product.new(name: "p2")
> s1.products = [p1, p2] # 但,此時會噴出下面這個錯誤,寫不進去!!!!!!
> ActiveRecord::RecordInvalid (Validation failed: Store must exist)
為什麼會這樣呢??如果你今天是跟著本篇文章一路做下來,你也會遇到這個問題,也就是
- 先做了 一對一 > 商店對店長做references
- 再來做了 一對多 > 產品對商店做references
- 最後做 多對多 > 產品和商店多做一個約診簿來記錄多對多
為什麼會這樣呢?主要就是因為,在第二步驟的時候,product table的欄位store_id是必填欄位 所以我們在做產品寫進會失敗,原因就是因為在寫入前一定先有資料
實際來看一下schema
> create_table "products", force: :cascade do |t|
> t.string "name"
> t.text "description"
> t.decimal "price"
> t.integer "store_id", null: false # 這個如果沒有填寫的話,是false
> t.datetime "created_at", precision: 6, null: false
> t.datetime "updated_at", precision: 6, null: false
> t.index ["store_id"], name: "index_products_on_store_id"
> end
那要怎麼解決這件事情呢?很簡單,把store_id改成純粹integer就好
> rails db:rollback STEP=2 => 倒退回product表單建立前(我這邊倒退兩步的原因是因為產品表是前前次建立的)
接著我們把當初創造product的migration打開,並看到references :store那一條
> class CreateProducts < ActiveRecord::Migration[6.1]
> def change
> create_table :products do |t|
> t.string :name
> t.text :description
> t.decimal :price
> t.references :store, null: false, foreign_key: true # 這裡
>
> t.timestamps
> end
> end
> end
只要把那一條改掉,就可以把資料寫進去了
> t.integer :store # 改成這樣
改好後,再把表單具現化
> rails db:migrate
前置動作 -> 先產生一位店長、一家店,等等我們來開始實際把產品塞進商店中
> o1 = Owner.create(name: "o1") # 產生店長
> o1.create_store(title: "s1") # 產生店家
> s1 = Store.first # s1 = 剛剛產生的店家
這樣更改後,就可以把商品塞進去拉!!!!
> p1 = Product.new(name: "p1")
> p2 = Product.new(name: "p2")
> s1.products = [p1, p2] # true