บทเรียน 106 · Rails · Day 2

Active Record คืออะไร — แฟ้มที่เดินเข้าลิ้นชักเองได้

~20 นาที (รวมภารกิจ) · บทแรกของ Day 2 · ควรผ่าน บทเรียน 99 (Post กับ posts) และ บทเรียน 104 (console) มาก่อน · มีเทียบกับ GORM จาก บทเรียน 27

Day 1 เราทำเว็บที่ข้อความทุกตัวเขียนตายตัวในโค้ด · Day 2 ทั้งวันคือเรื่องข้อมูลที่เก็บไว้จริง
อ่านจบบทนี้ คุณจะตอบได้ว่าทำไม class Post < ApplicationRecord ที่ว่างเปล่าถึงรู้จักช่อง title · อ่านโค้ด Rails ที่บันทึกบทความแล้วเทียบกับ GORM ได้ทีละบรรทัด · และเข้าใจว่า "Active Record" คือวิธีคิดแบบหนึ่ง ที่มีทั้งข้อดีและราคาที่ต้องจ่าย

🔁 ทวนก่อนเริ่ม — คำพวกนี้จะโผล่ในบทนี้

🧰 เวอร์ชันที่ใช้ตรวจ: Ruby 4.0.9 · Rails 8.1.4 · PostgreSQL 17 · Go 1.26 + GORM v1.31.2 (สไลด์ใช้ Ruby 4.0.6 · Rails 8.1.3 — เรื่องในบทนี้ไม่ต่างกัน) · ผลรันทุกกล่องก็อปมาจากเครื่องจริง · เวลา (ms) · วันเวลา · เลข 0x… ของคุณจะไม่ตรงกับในบท

🗂 ข้อมูลตัวอย่าง: บทนี้ใช้แค่ตาราง posts ที่มีช่อง title กับ body · สร้างด้วยคำสั่งเดียวกับบท 99 (bin/rails generate model Post title:string body:text แล้ว bin/rails db:migrate) · ถ้าในเครื่องคุณยังไม่มี ภารกิจท้ายบทพาสร้างทีละขั้น — อ่านเนื้อหาตามไปก่อนได้เลย

1 · ทำไมทั้งวันพูดเรื่องข้อมูล

สไลด์หน้าแรกของ Day 2 เปิดด้วยประโยคนี้: "แอปพังเพราะโค้ดผิด แก้ 10 นาที · แอปพังเพราะข้อมูลผิด แก้เป็นอาทิตย์"

โค้ดผิดแก้ง่าย เพราะแก้ไฟล์เดียวแล้ว deploy ใหม่ (เอาโค้ดใหม่ขึ้นเครื่องที่ให้บริการจริง) ทุกคนได้โค้ดที่ถูกทันที · แต่ข้อมูลผิดถูกเก็บลงไปแล้ว อาจเป็นพันรายการ และบางรายการมีคนใช้ต่อไปแล้วด้วย
เช่น ระบบยอมให้สมัครด้วยอีเมลซ้ำมาหลายเดือน · วันที่รู้ตัว คุณต้องไล่ดูทีละคู่ว่าบัญชีไหนของจริง แล้วรวมบทความของสองบัญชีเข้าด้วยกัน

🍲 เทียบให้เห็นภาพ — สูตรผิด กับ ของเสียเข้าตู้

โค้ดผิด = สูตรอาหารพิมพ์ผิด ใส่เกลือ 5 ช้อน · แก้สูตรแล้ว จานถัดไปก็ถูก จานที่เสิร์ฟไปแล้วก็จบไป
ข้อมูลผิด = รับวัตถุดิบเสียเข้าห้องเก็บของไปแล้ว ปนอยู่กับของดีหลายร้อยกล่อง · ต้องไล่เปิดทีละกล่อง และบางกล่องถูกเอาไปปรุงแล้วด้วย

ที่ไม่เหมือนกัน: ของเสียในร้านจริงยังดมหรือดูออก · ข้อมูลผิดใน database หน้าตาปกติทุกอย่าง — อีเมลซ้ำสองแถวก็เป็นแค่ข้อความธรรมดาสองบรรทัด ไม่มีอะไรเตือน จนวันที่ลูกค้าล็อกอินแล้วเข้าผิดบัญชี

ทั้ง Day 2 จึงพูดเรื่องเดียว: ทำยังไงให้ข้อมูลที่เข้าห้องเก็บของถูกตั้งแต่แรก และดึงออกมาใช้ได้เร็ว · บทนี้เริ่มจากรู้จัก "คนดูแลสต็อก" ของ Rails ก่อน ว่าเขาทำงานยังไง

2 · แผนที่ของวันนี้

ไม่ต้องจำ แค่ดูว่าทั้งวันจะเดินไปทางไหน · ทุกบทใช้ภาพร้านอาหารชุดเดิมจาก Day 1 แล้วต่อยอดที่ "ห้องเก็บของ":

บทเรื่องในร้านอาหาร
106Active Record คืออะไร (บทนี้)แฟ้มที่เดินเข้าลิ้นชักเองได้
107สร้าง อ่าน แก้ ลบ ใน consoleลองหยิบ-เก็บแฟ้มในครัวทดลอง
108–109แก้หน้าตาตาราง (migration) โดยไม่ทำแอปค้างใบสั่งช่างต่อเติมตู้ · ปรับปรุงร้านโดยไม่ปิดร้าน
110–111กฎตรวจข้อมูล (validation) · กฎที่ตัว databaseคนดูแลสต็อกตรวจแฟ้ม · ตู้ที่ใส่แฟ้มผิดแบบไม่ได้เลย
112ขั้นตอนอัตโนมัติ (callbacks) · โค้ดควรอยู่ไหนขั้นตอนอัตโนมัติก่อน/หลังเก็บแฟ้ม
113–114ค้นข้อมูล (query) · โค้ดที่ไปหา database บ่อยเกิน (N+1)ใบเบิกของ · เดินไปตู้ 101 รอบ
115–116บทความ ผู้เขียน คอมเมนต์ผูกกันช่อง "เป็นของบทความเลขที่ …" บนใบคอมเมนต์
117เส้นทางมาตรฐาน (REST) · กันลูกค้าแอบกรอกช่องเกินพนักงานต้อนรับรับเฉพาะช่องบนแบบฟอร์ม
118–119โค้ดชุดสำเร็จรูป (scaffold) · Lab 2ชุดเปิดแผนกสำเร็จรูป แล้วเติมกฎของร้านเรา

สไลด์หน้า 3 บอกว่าถ้าจำได้แค่ 3 อย่าง ให้จำสามประโยคนี้ (ยังไม่ต้องเข้าใจตอนนี้ บท 119 จะกลับมาถามอีกที):

  1. แก้หน้าตาตารางได้โดยไม่ต้องปิดแอป — ทำทีละขั้น: ต่อของใหม่ก่อน → ย้ายข้อมูล → ค่อยรื้อของเก่า (บท 109)
  2. กฎตรวจข้อมูลในโค้ด Rails ยังไม่ใช่การรับประกัน — ต้องมีกฎที่ตัว database คู่กันด้วย (บท 111)
  3. โค้ดที่ไปหา database บ่อยเกินจำเป็น ดูจากโค้ดไม่ออก ต้องดูจาก log (บท 114)

3 · ตู้แฟ้มของร้าน — database · ตาราง · แถว · คอลัมน์ · SQL

ก่อนไปต่อ ต้องมีคำพื้นฐานห้าคำ · database คือโปรแกรมที่เก็บข้อมูลให้อยู่ถาวร ปิดแอปแล้วข้อมูลไม่หาย · แอปของเราใช้ PostgreSQL ตัวเดียวกับที่เคยใช้กับ Go

ข้างใน database แบ่งเป็นตาราง (table) ตามชนิดของข้อมูล เช่นตาราง posts เก็บบทความ · ข้อมูลหนึ่งชิ้นคือแถว (row) หนึ่งแถว · แต่ละแถวมีช่องเหมือนกันทุกแถว เรียกว่าคอลัมน์ (column)

นี่คือหน้าตาตาราง posts หลังบันทึกบทความไปสองชิ้น (ตัวอย่างจากเครื่องตรวจ · ตัดเวลาให้สั้น):

idtitlebodycreated_atupdated_at
1ต้มยำกุ้งน้ำข้นเปรี้ยว เผ็ด หอมสมุนไพร2026-11-04 11:452026-11-04 11:45
2ส้มตำปูปลาร้า(ว่าง — NULL)2026-11-04 11:462026-11-04 11:46

เราไม่ได้เปิดตารางด้วยมือ · ทุกครั้งที่อยากได้ข้อมูล ต้องสั่งเป็นภาษาเฉพาะของ database ชื่อ SQL · เช่นคำสั่งนี้ (Rails "ยิง" คือส่งคำสั่งนี้ไปให้ database เองตอนเราพิมพ์ Post.count — เดี๋ยวเห็นของจริงในหัวข้อ 4):

SELECT COUNT(*) FROM "posts"
🗄 เทียบให้เห็นภาพ — ตู้เก็บแฟ้มในห้องเก็บของ

database = ตู้เก็บแฟ้มทั้งตู้ · ตาราง posts = ลิ้นชักที่ติดป้าย "posts" · แถว = แฟ้มหนึ่งแผ่น (บทความหนึ่งชิ้น) · คอลัมน์ = ช่องที่พิมพ์ไว้บนแบบฟอร์มของแฟ้ม (ช่องหัวข้อ · ช่องเนื้อหา) · SQL = ภาษาที่คนดูแลสต็อกใช้สั่งตู้ "หยิบแผ่นที่ …" "นับให้หน่อย"

ที่ไม่เหมือนกัน: ลิ้นชักจริงใส่กระดาษหน้าตาไหนก็ได้ · แต่ตารางบังคับให้ทุกแถวมีช่องชุดเดียวกันเป๊ะ — บทความที่ไม่มีเนื้อหาก็ยังมีช่อง body แค่ช่องว่าง (NULL) · และไม่มีใครเปิดลิ้นชักด้วยมือได้ ทุกอย่างต้องผ่านคำสั่ง SQL

🗣 ศัพท์ที่เพิ่งเรียนไป
database · table · row · column
ตู้ · ลิ้นชัก · แฟ้มหนึ่งแผ่น · ช่องบนแฟ้ม · ในทีมมักเรียก row ว่า "record" ด้วย · ถาม senior ได้ว่า "ตาราง posts ตอนนี้มีกี่ row แล้ว และมี column อะไรบ้าง" — คำถามแรกที่ควรถามเวลาเข้าโปรเจกต์ใหม่
SQL · query
SQL = ภาษาที่ใช้สั่ง database (อ่านว่า เอส-คิว-แอล หรือ ซีเควล) · query = คำสั่ง SQL หนึ่งคำสั่ง · พิมพ์บอก AI ได้ว่า "explain this SQL line by line in plain words" แล้วแปะบรรทัดจาก log ไปทั้งบรรทัด
NULL
ค่าว่างในช่องของ database · ฝั่ง Ruby เห็นเป็น nil · ถาม senior ได้ว่า "ช่องนี้ปล่อยให้ NULL ได้ไหม" — เป็นคำถามสำคัญมากในบท 111

4 · คำถามจากบท 99 — class ว่างเปล่า แต่รู้จัก title

บท 99 เราเห็นว่า app/models/post.rb มีแค่สองบรรทัด แต่ post.title ใช้ได้ · คำตอบสั้น ๆ คือ Post ไปถามตาราง posts เองว่ามีคอลัมน์อะไร แล้วสร้าง method ตามนั้น · วันนี้ดูให้ชัดขึ้นโดยวางเทียบกับ GORM ที่เคยเขียนในบท 27 (โค้ดจากสไลด์หน้า 5 ปรับให้ตรงกับตาราง posts ของเรา)

Go + GORM

// 1) ต้องประกาศทุกช่องเอง
type Post struct {
	ID        uint
	Title     string
	Body      string
	CreatedAt time.Time
	UpdatedAt time.Time
}

// 2) สร้าง 3) ยื่นให้ db บันทึก 4) เช็ค err เอง
p := Post{Title: "ต้มยำกุ้งน้ำข้น", Body: "เปรี้ยว เผ็ด หอมสมุนไพร"}
if err := db.Create(&p).Error; err != nil {
	return err
}

Ruby + Active Record

# 1) ไม่ประกาศช่องเลย
class Post < ApplicationRecord
end

# 2) สร้าง 3) สั่งตัวมันเองให้บันทึก
post = Post.new(title: "ต้มยำกุ้งน้ำข้น", body: "เปรี้ยว เผ็ด หอมสมุนไพร")
post.save!

ฝั่ง Go ตัดมาจากไฟล์ที่มี package main + import ครบ และ go build ผ่าน ผมรันกับ PostgreSQL ตัวเดียวกันจริง · สไลด์ตั้งชื่อตัวแปรว่า p · ผมใช้ post ฝั่ง Ruby เพราะ p เป็นชื่อคำสั่งพิมพ์ค่าที่เราใช้มาตั้งแต่บท 93 อ่านแล้วจะงง

เทียบทีละงาน:

งานGo + GORMRails
บอกว่าบทความมีช่องอะไรเขียนเองใน struct ทุกช่องไม่เขียน · Rails ไปถามตาราง posts ตอนรัน
สร้างบทความหนึ่งชิ้น (ยังไม่บันทึก)p := Post{Title: …}post = Post.new(title: …)
บันทึกdb.Create(&p) — ยื่นบทความให้ db เป็นคนบันทึก (&p = ส่งตัวจริงไป GORM จึงเติม ID กลับมาให้ได้)post.save! — สั่งบทความตัวเองให้บันทึก
ถ้าบันทึกไม่ได้ได้ .Error กลับมา ต้องเขียน if err != nil เองsave! โยน error ออกมาเอง (! = "ถ้าทำไม่ได้ให้ระเบิด" · แบบไม่มี ! รอบท 110)

มาดูแถวแรกด้วยตาตัวเอง (บท 97 เคยลองแบบเดียวกันมาแล้ว) · เปิด bin/rails console แล้วถามว่า Post รู้จักช่องอะไรบ้าง:

wongnok-blog(dev):001> Post.column_names
=> ["id", "title", "body", "created_at", "updated_at"]

column_names = "บอกชื่อคอลัมน์ทั้งหมดของตารางเรา" · ได้กลับมาเป็น Array ของข้อความ · ไม่มีชื่อไหนเขียนอยู่ใน post.rb เลย · ทั้งห้าชื่อมาจากตาราง posts ใน database ล้วน ๆ

📋 เทียบให้เห็นภาพ — พิมพ์แบบฟอร์มเอง กับ ลอกแบบจากลิ้นชัก

ร้าน Go (เปิดร้านเองตั้งแต่ศูนย์): เราต้องพิมพ์แบบฟอร์มแฟ้มเองว่ามีช่องหัวข้อ ช่องเนื้อหา … (นั่นคือ struct) แล้วหวังว่าตรงกับลิ้นชัก · ร้าน Rails (แฟรนไชส์): คนดูแลสต็อกของ Post เดินไปเปิดลิ้นชัก posts ดูว่าแฟ้มในนั้นมีช่องอะไร แล้วทำแฟ้มเปล่าตามแบบนั้นให้เลย · ลิ้นชักมีช่องเพิ่ม แฟ้มก็มีช่องเพิ่มเอง

ที่ไม่เหมือนกัน: คนจริงเห็นแบบฟอร์มตั้งแต่ก่อนเริ่มงาน · Rails ไปดูลิ้นชักตอนโปรแกรมรัน ไม่ใช่ตอนเราเขียนโค้ด — ถ้าพิมพ์ชื่อช่องที่ลิ้นชักไม่มี จะรู้ตัวตอนรันเท่านั้น (ลองทายในกล่องท้ายหัวข้อนี้)

ต่อด้วยแถวที่สองและสาม — สร้างบทความ แล้วสั่งบันทึก (ผลจริง · 0x… คือที่อยู่ในหน่วยความจำ ของคุณจะต่าง):

wongnok-blog(dev):002> post = Post.new(title: "ต้มยำกุ้งน้ำข้น", body: "เปรี้ยว เผ็ด หอมสมุนไพร")
=>
#<Post:0x000000012595f3c8
...
wongnok-blog(dev):003> post
=>
#<Post:0x000000012595f3c8
 id: nil,
 title: "ต้มยำกุ้งน้ำข้น",
 body: "เปรี้ยว เผ็ด หอมสมุนไพร",
 created_at: nil,
 updated_at: nil>
wongnok-blog(dev):004> post.id
=> nil
wongnok-blog(dev):005> post.save!
  TRANSACTION (0.9ms)  BEGIN /*application='WongnokBlog'*/
  Post Create (2.7ms)  INSERT INTO "posts" ("title", "body", "created_at", "updated_at") VALUES ('ต้มยำกุ้งน้ำข้น', 'เปรี้ยว เผ็ด หอมสมุนไพร', '2026-11-04 12:25:36.162255', '2026-11-04 12:25:36.162255') RETURNING "id" /*application='WongnokBlog'*/
  TRANSACTION (1.0ms)  COMMIT /*application='WongnokBlog'*/
=> true
wongnok-blog(dev):006> post.id
=> 1
wongnok-blog(dev):007> Post.count
  Post Count (3.1ms)  SELECT COUNT(*) FROM "posts" /*application='WongnokBlog'*/
=> 1

บรรทัด ... หลัง post = Post.new(…) ไม่ใช่ error · console ย่อผลของบรรทัดที่มี = ให้สั้นลงเอง · อยากเห็นครบ ให้พิมพ์ชื่อตัวแปรเปล่า ๆ (post) อย่างบรรทัด 003

อ่านทีละบรรทัด:

บรรทัดแปลเป็นภาษาคน
Post.new(…) → id: nil สร้างแฟ้มเปล่าแล้วกรอกช่องในมือ · ยังไม่ได้เข้าลิ้นชัก จึงยังไม่มีเลขแฟ้ม (id เป็น nil) · ยังไม่มี SQL สักบรรทัด
TRANSACTION … BEGIN "เริ่มงานชุดหนึ่ง" · Rails ห่องานบันทึกไว้ในชุด ถ้าพังกลางทางจะไม่มีอะไรค้างครึ่ง ๆ (เรียกว่า transaction = ทำทั้งหมดหรือไม่ทำเลย · ละเอียดในบท 111)
Post Create (2.7ms) ป้ายที่ Rails แปะหน้าบรรทัด: "คนดูแลสต็อกของ Post กำลังสร้างแถว ใช้เวลา 2.7 มิลลิวินาที" · ข้างหลังคือ SQL ที่ยิงจริง
INSERT INTO "posts" (…) VALUES (…) INSERT INTO = "ใส่แถวใหม่ลงตาราง" · วงเล็บแรก = ช่องที่จะกรอก (ชื่อช่องอยู่ใน " ") · VALUES = ค่าที่กรอกเรียงตามลำดับช่อง (ค่าที่เป็นข้อความอยู่ใน ' ') · ภาษาคน: "ใส่แฟ้มใหม่ลงลิ้นชัก posts กรอกหัวข้อ เนื้อหา เวลาสร้าง เวลาแก้ ตามนี้" · สังเกตว่า Rails เติมเวลาให้เอง
RETURNING "id" "ใส่เสร็จแล้วบอกเลขแฟ้มกลับมาด้วย" · นี่คือเหตุที่หลังบันทึก post.id กลายเป็น 1
/*application='WongnokBlog'*/ คอมเมนต์ที่ Rails แปะท้าย SQL บอกว่าแอปไหนส่งมา · database ไม่สนใจ · ข้ามได้
TRANSACTION … COMMIT → => true "ยืนยันงานชุดนี้" · ข้อมูลเข้าตู้จริงแล้ว · save! ตอบ true

เทียบกับ Go: ผมเปิดโหมดโชว์ SQL ของ GORM (db.Debug()) แล้วรันโค้ดฝั่งซ้าย · ได้ SQL หน้าตาเกือบเหมือนกันทุกตัวอักษร (ผลจริง ตัด path ด้านหน้าออก):

2026/11/04 18:48:29 …/gormdemo/main.go:30
[7.862ms] [rows:1] INSERT INTO "posts" ("title","body","created_at","updated_at") VALUES ('ต้มยำกุ้งน้ำข้น','เปรี้ยว เผ็ด หอมสมุนไพร','2026-11-04 18:48:29.883','2026-11-04 18:48:29.883') RETURNING "id"
saved id: 1

บรรทัดแรก = วันเวลา + ไฟล์และบรรทัดในโค้ด Go ที่สั่ง · [7.862ms] = ใช้เวลาเท่าไร · [rows:1] = ใส่ไป 1 แถว · saved id: 1 คือบรรทัดที่โปรแกรม Go ของผมพิมพ์เองหลังบันทึกเสร็จ

แปลว่า ทั้งสองฝั่งทำงานเดียวกันเป๊ะ: แปลงคำสั่งในภาษาโปรแกรมเป็น SQL แล้วส่งให้ PostgreSQL · ตัวแปลงแบบนี้เรียกว่า ORM (เจอคำนี้ในบท 27) · ที่ต่างกันคือใครถือคำสั่งบันทึก — หัวข้อถัดไป

# ตาราง posts ของเรามีแค่ id title body created_at updated_at
# สไลด์หน้า 5 เขียนแบบนี้ (มีช่อง user_id ด้วย)
wongnok-blog(dev):001> Post.new(title: "Hello", user_id: 1)

ตารางของเราไม่มีช่อง user_id · คิดว่าได้ Post ที่ไม่มี user_id · ได้ error · หรือ Rails สร้างช่องให้เอง?

(wongnok-blog):1:in '<main>': unknown attribute 'user_id' for Post. (ActiveModel::UnknownAttributeError)

      raise UnknownAttributeError.new(self, name)
      ^^^^^
…/attribute_methods.rb:512:in 'ActiveModel::AttributeMethods#method_missing': undefined method 'user_id=' for an instance of Post (NoMethodError)
	from …

ได้ error ตั้งแต่ Post.new ยังไม่ทันถึง save! · unknown attribute 'user_id' for Post = "Post ไม่รู้จักช่องชื่อ user_id" (attribute = ช่องของข้อมูลฝั่ง model)
บรรทัดล่างบอกเหตุผลลึกกว่านั้น: undefined method 'user_id=' — Rails สร้าง method สำหรับกรอกค่าเฉพาะช่องที่มีในตารางจริง · ไม่มีช่อง ก็ไม่มี method ให้กรอก · และ Rails ไม่สร้างช่องในตารางให้เอง (นั่นเป็นงานของ migration — บท 108)

ฝั่ง GORM ก็คล้ายกัน: ผมลองใส่ UserID uint ใน struct ทั้งที่ตารางไม่มีช่องนี้ · go build ผ่าน แต่พังตอนยิง INSERT ด้วย ERROR: column "user_id" of relation "posts" does not exist · ทั้งสองฝั่งรู้ว่าช่องไม่ตรงก็ต่อเมื่อไปถึงตาราง · ช่อง user_id จะมาในบทถัด ๆ ไปของ Day 2 ตอนมีตาราง users

🗣 ศัพท์ที่เพิ่งเรียนไป
ORM (Object-Relational Mapping)
ตัวแปลงระหว่าง object ในโค้ดกับแถวในตาราง · GORM คือ ORM ของ Go · Active Record คือ ORM ของ Rails · ถาม senior ได้ว่า "ขอดู SQL ที่ ORM ยิงจริงหน่อย" — ใน Rails ดูได้จาก log ของ console หรือ server เลย
attribute
ช่องของข้อมูลฝั่ง model (post.title) · ตรงกับคอลัมน์ฝั่งตาราง · ใน Rails มาจากตารางตอนรัน ไม่ได้ประกาศในไฟล์ · พิมพ์บอก AI ได้ว่า "I get ActiveModel::UnknownAttributeError for user_id — does the posts table have that column?"
bang method (save!)
method ที่ชื่อลงท้ายด้วย ! · ใน Active Record แปลว่า "ทำไม่ได้ให้โยน error" · save ไม่มี ! ตอบ true/false แทน (บท 110) · ถาม senior ได้ว่า "ตรงนี้ทีมใช้ save หรือ save! — อยากให้พังดัง ๆ หรือเช็คผลเอง"

5 · Active Record คือชื่อ "วิธีคิด" ไม่ใช่แค่ชื่อของใน Rails

คำว่า Active Record ที่เห็นในชื่อ ActiveRecord::Base จริง ๆ เป็นชื่อ pattern — แบบแผนการออกแบบที่มีคนตั้งชื่อไว้ให้คุยกันรู้เรื่อง · Rails Guides เล่าว่า Martin Fowler เป็นคนอธิบาย pattern นี้ไว้ในหนังสือ Patterns of Enterprise Application Architecture (สไลด์หน้า 6 อ้างว่าหนังสือออกปี 2002 ก่อน Rails 2 ปี)

แก่นของ pattern นี้มีประโยคเดียว:

object หนึ่งตัว = หนึ่งแถวในตาราง · และ object ตัวนั้นรู้วิธีอ่าน เขียน ลบ ตัวเองลง database

ดังนั้น post หนึ่งตัวไม่ได้ถือแค่ข้อมูล · มันถือของห้าอย่างไว้ในตัว (สไลด์หน้า 6) ซึ่งเราจะได้ลงมือทีละอย่างทั้งวัน:

ของที่ post แบกไว้ตัวอย่างเจอในบท
ข้อมูล — ค่าของแต่ละช่องpost.titleบทนี้ · 107
การเดินทางไป database — บันทึก ลบ โหลดใหม่post.save! · post.destroy107
กฎตรวจข้อมูล"ไม่มีหัวข้อ ไม่รับ"110
ขั้นตอนอัตโนมัติก่อน/หลังบันทึก"ติดป้ายก่อนเข้าตู้ทุกครั้ง"112
วิธีหาข้อมูลที่เกี่ยวข้องpost.comments115
📄 เทียบให้เห็นภาพ — แฟ้มที่เดินเข้าลิ้นชักเองได้

ในร้าน Rails คนดูแลสต็อกฝังอยู่ในแฟ้มทุกแผ่น · แฟ้มบทความหนึ่งแผ่นรู้ว่าตัวเองมีช่องอะไร · สั่ง post.save! แฟ้มก็เดินเข้าลิ้นชัก posts เอง · สั่ง post.destroy ก็เดินออกไปทิ้งเอง · ไม่ต้องมีใครถือไปให้

ที่ไม่เหมือนกัน: แฟ้มไม่ได้ "เดิน" ไปจริง — save! แค่แปลเป็นคำสั่ง SQL (INSERT INTO …) แล้วยื่นให้ PostgreSQL ทำ · และกระดาษจริงเป็นแฟ้มได้แม้ไม่มีตู้ · แต่ Post ต้องมีตู้ก่อนถึงจะสร้างแฟ้มเปล่าได้ เพราะต้องไปดูช่องจากลิ้นชัก (หัวข้อ 7 พิสูจน์ให้ดู)

🗣 ศัพท์ที่เพิ่งเรียนไป
pattern (design pattern)
วิธีแก้ปัญหาที่คนใช้ซ้ำกันบ่อยจนตั้งชื่อเรียกได้ · พูดชื่อเดียวแล้วทั้งทีมเห็นภาพตรงกัน · ได้ยินคำว่า "Active Record" ให้ดูบริบท: คุยเรื่องออกแบบ = ชื่อ pattern · คุยเรื่อง Rails = ส่วนของ Rails ที่คุยกับ database (เป็น gem จริงชื่อ activerecord อยู่ในใบเสร็จ Gemfile.lock ของคุณ — บท 98)
Active Record pattern
object หนึ่งตัวแทนหนึ่งแถว และมี method บันทึก/ลบตัวเอง (post.save!) · พิมพ์บอก AI ได้ว่า "explain the difference between the Active Record and Data Mapper patterns with a Rails example"

6 · อีกสายหนึ่ง: Data Mapper — แฟ้มเป็นกระดาษเปล่า มีพนักงานคลังแยก

Active Record ไม่ใช่ทางเดียว · สไลด์หน้า 7 แบ่งโลกของ ORM เป็นสองสาย ด้วยคำถามข้อเดียว: "ใครควรเป็นคนที่รู้เรื่อง database"

Active Record — แฟ้มรู้เอง

post   ← ถือข้อมูล + รู้วิธีเก็บตัวเอง
 │
 │  post.save!
 ▼
ตาราง posts

Data Mapper — มีคนกลาง

post   ← ถือแค่ข้อมูล ไม่รู้จัก database
 │
 │  ยื่นให้
 ▼
mapper ← พนักงานคลัง: repo.save(post)
 │
 ▼
ตาราง posts

ฝั่งขวา post เป็นกระดาษเปล่าธรรมดา · งานบันทึก/ค้นหาทั้งหมดอยู่ที่ "พนักงานคลัง" อีกตัว (มักเรียกว่า mapper หรือ repository) · repo.save(post) ข้างบนเป็นแค่ภาพประกอบ ไม่ใช่โค้ดของ library ไหน

เทียบกับ Go: คุณเคยทำหน้าตาแบบขวามาแล้วในบท 27 — struct User ใน model.go ถือแค่ข้อมูล · ส่วน repository.go เป็นคนเดียวที่เรียก GORM (r.db.WithContext(ctx).First(&u, id)) · struct ไม่มีวันสั่ง save ตัวเอง

🧑‍💼 เทียบให้เห็นภาพ — พนักงานคลังที่ถือแฟ้มไปเก็บให้

ร้านแบบ Data Mapper: แฟ้มบทความเป็นกระดาษเปล่าที่กรอกข้อมูลอย่างเดียว ไม่รู้ว่าตู้อยู่ไหน · มีพนักงานคลังแยกหนึ่งคน คอยรับแฟ้มไปเก็บ ไปหยิบ ไปทิ้ง · ร้านแบบ Active Record ไม่มีพนักงานคนนี้ เพราะแฟ้มทำเองได้

ที่ไม่เหมือนกัน: ในร้านจริง การมีพนักงานเพิ่มหนึ่งคนแปลว่าต้องจ้างเพิ่ม · ในโค้ด "พนักงานคลัง" คือไฟล์และโค้ดที่ต้องเขียนเพิ่ม (ข้อเสียที่สไลด์บอกว่า "ไฟล์เยอะกว่า") · แลกกับการที่กระดาษเปล่าเอาไปทดสอบหรือใช้ที่อื่นได้โดยไม่ต้องมีตู้

🗣 ศัพท์ที่เพิ่งเรียนไป
Data Mapper · repository
แยก object ข้อมูลล้วนออกจากตัวที่คุยกับ database · repository คือชื่อเรียกตัวกลางที่พบบ่อย (บท 27 ใช้คำนี้) · ถาม senior ได้ว่า "โปรเจกต์นี้ให้ model คุยกับ database ตรง ๆ หรือผ่าน repository" — ตอบคำถามนี้ได้ จะรู้ทันทีว่าโค้ดบันทึกข้อมูลอยู่ไฟล์ไหน

7 · ราคาที่ต้องจ่าย

ข้อดีของ Active Record เห็นแล้ว: เขียนสั้น เริ่มเร็ว ของทุกอย่างอยู่ที่เดียว · สไลด์หน้า 6 บอกราคาที่ต้องจ่ายไว้สามข้อ · แปลเป็นภาษาคน:

ข้อสองพิสูจน์ได้ · ผมชี้ DATABASE_URL ไปที่ database ชื่อ w106_nope ที่ไม่มีอยู่จริง แล้วสั่งแค่ Post.new (ยังไม่ save) · ผลจริง ตัดบรรทัดคำแนะนำที่ตามมาออก:

$ bin/rails runner 'p Post.new(title: "x")'
…/postgresql_adapter.rb:63:in 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.new_client': Database not found: w106_nope. Available database configurations can be found in config/database.yml. (ActiveRecord::NoDatabaseError)

bin/rails runner '…' = รันโค้ด Ruby บรรทัดเดียวโดยโหลดแอปให้ก่อน (บท 99) · ผลคือพังตั้งแต่ Post.new เพราะ Post ต้องไปถามตารางก่อนว่ามีช่องอะไร · ฝั่ง Go Post{Title: "x"} สร้างได้เสมอ ไม่ต้องมี database เลย

Rails ยอมรับข้อเสียพวกนี้โดยตั้งใจ แลกกับความเร็วในการเขียน · ไม่ใช่ว่าแบบไหนถูกแบบไหนผิด · แค่ต้องรู้ว่าจ่ายอะไรไป จะได้ไม่แปลกใจตอนเทสต์ต้องเปิด database (Day 4)

🗣 ศัพท์ที่เพิ่งเรียนไป
fat model
model ที่มีโค้ดกองอยู่มากเกินไปจนอ่านยาก · คำนี้ได้ยินบ่อยในทีม Rails · ถาม senior ได้ว่า "Post เริ่ม fat แล้ว ทีมเราแยกโค้ดออกไปไว้ที่ไหน"
coupled to the database (ผูกกับ database)
โค้ดที่ทำงานไม่ได้ถ้าไม่มี database · Active Record model เป็นแบบนี้โดยธรรมชาติ · พิมพ์บอก AI ได้ว่า "this Rails model test needs the database — is that expected or can I avoid it?"

8 · ApplicationRecord และการเดาชื่อตาราง

ทวนบท 99 สั้น ๆ: Post < ApplicationRecord < ActiveRecord::Base (เขียนย่อให้เห็นว่าใครสืบทอดจากใคร ไม่ใช่โค้ดที่พิมพ์ได้จริง) · ActiveRecord::Base คือของจาก Rails ที่คุยกับ database ได้ทุกอย่าง · ApplicationRecord คือแม่แบบกลางของแอปเรา ไว้ใส่ของที่ model ทุกตัวต้องมี (ตอนนี้ยังว่าง) · Post จึงได้ความสามารถทั้งหมดมาฟรีผ่านการสืบทอด

เหลือคำถามเดียว: Post รู้ได้ยังไงว่าต้องไปถามตาราง posts · คำตอบคือกฎตั้งชื่อจากบท 99 — Rails เอาชื่อ class มาทำเป็นตัวเล็กแล้วเติมพหูพจน์ · Post → posts · ถ้าตั้งชื่อ class ไม่ตามตาราง จะเกิดอะไร? ลองสร้าง class ชื่อ Article (บทความเหมือนกัน แต่คนละคำ) ในโปรเจกต์ที่มีแค่ตาราง posts (ผลจริงข้างล่าง ตัดบรรทัด from … ยาว ๆ ท้าย error ออก):

# app/models/article.rb
class Article < ApplicationRecord
end
wongnok-blog(dev):001> Article.table_name
=> "articles"
wongnok-blog(dev):002> Article.count
  Article Count (46.2ms)  SELECT COUNT(*) FROM "articles" /*application='WongnokBlog'*/
(wongnok-blog):2:in '<main>': PG::UndefinedTable: ERROR:  relation "articles" does not exist (ActiveRecord::StatementInvalid)
LINE 1: SELECT COUNT(*) FROM "articles" /*application='WongnokBlog'*...
                             ^

ถ้าอยากใช้ชื่อ class ที่ไม่ตามกฎจริง ๆ (เช่น ต่อกับตารางเก่าที่ตั้งชื่อไว้ก่อนแล้ว) ให้บอกชื่อตารางเองหนึ่งบรรทัด:

# app/models/article.rb
class Article < ApplicationRecord
  self.table_name = "posts"
end
wongnok-blog(dev):001> Article.table_name
=> "posts"
wongnok-blog(dev):002> Article.count
  Article Count (37.6ms)  SELECT COUNT(*) FROM "posts" /*application='WongnokBlog'*/
=> 2
wongnok-blog(dev):003> Article.first.title
  Article Load (3.4ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT 1 /*application='WongnokBlog'*/
=> "ต้มยำกุ้งน้ำข้น"

เทียบกับ Go: GORM ก็เดาชื่อตารางแบบเดียวกัน (บท 27: User → users) · และถ้าชื่อไม่ตามกฎ ให้เขียน method TableName() (ผมรันแล้ว ได้แถวจากตาราง posts จริง) · GORM จะเรียก method นี้ถามเองว่า "Article ใช้ตารางไหน":

Go + GORM

type Article struct {
	ID    uint
	Title string
}

func (Article) TableName() string { return "posts" }

Rails

class Article < ApplicationRecord
  self.table_name = "posts"
end
🏷 เทียบให้เห็นภาพ — ป้ายลิ้นชักของร้านแฟรนไชส์

ร้านแฟรนไชส์ (Convention over Configuration — บท 90) ตั้งกฎว่า แฟ้มชื่ออะไร ลิ้นชักติดป้ายชื่อนั้นเป็นพหูพจน์ · คนดูแลสต็อกของแฟ้ม Post จึงเดินไปลิ้นชัก "posts" ได้เลยโดยไม่ต้องถาม · ถ้าร้านเราซื้อตู้เก่ามาที่ติดป้ายไว้แล้ว ก็แปะโน้ตบอกคนดูแลสต็อกทีเดียว (self.table_name)

ที่ไม่เหมือนกัน: คนจริงหาลิ้นชักไม่เจอจะเดินกลับมาถาม · Rails ไม่ตรวจล่วงหน้าเลย — เปิดแอปได้ปกติ แล้วไปพังตอนมีคนใช้ Article ครั้งแรก · และ Rails ไม่ได้รู้ความหมายของคำ — Article กับ Post แปลว่าบทความเหมือนกัน แต่สำหรับ Rails เป็นแค่ตัวอักษรคนละชุด

🗣 ศัพท์ที่เพิ่งเรียนไป
table_name · naming convention
ชื่อตารางที่ model ใช้ · ปกติ Rails เดาจากชื่อ class ตามกฎตั้งชื่อ · เขียน self.table_name = "…" เมื่อชื่อไม่ตามกฎ · พิมพ์บอก AI ได้ว่า "map this Rails model to an existing legacy table named tbl_article" — legacy = ของเก่าที่มีอยู่ก่อนแล้วแก้ไม่ได้
relation "…" does not exist
PostgreSQL หาตารางชื่อนี้ไม่เจอ · สาเหตุบ่อยสุดคือยังไม่ได้ db:migrate หรือชื่อ class ไม่ตรงกับชื่อตาราง · ถาม senior ได้ว่า "ตาราง articles ควรมีอยู่แล้ว หรือผมต้องรัน migration ก่อน"

9 · ของฟรีที่มากับ model สองบรรทัด

class Post < ApplicationRecord + end ทำให้ได้ method พวกนี้มาโดยไม่ต้องเขียนอะไรเพิ่ม (สไลด์หน้า 8) · ไม่ต้องจำ · แค่รู้ว่า "มีของแบบนี้อยู่" เวลาสั่ง AI หรืออ่านโค้ดที่ AI เขียนมา จะได้ไม่งงว่ามาจากไหน · ลงมือจริงในบท 107

กลุ่มmethodภาษาคน
สร้าง / บันทึกPost.new · Post.create · post.save! · post.update · post.destroy ทำแฟ้มใหม่ (ยังไม่เก็บ) · ทำแล้วเก็บทันที · เก็บ · แก้แล้วเก็บ · ทิ้ง
ค้นหาPost.find(1) · Post.find_by(…) · Post.where(…) · Post.first / Post.last · Post.all หยิบแผ่นเลขที่ 1 · หยิบแผ่นแรกที่ตรงเงื่อนไข · หยิบทุกแผ่นที่ตรงเงื่อนไข · แผ่นแรก/สุดท้าย · ทุกแผ่น
ถามสถานะของแฟ้มpost.new_record? · post.persisted? ยังไม่เข้าตู้ใช่ไหม · เข้าตู้แล้วใช่ไหม
ถามตัวตารางPost.count · Post.column_names · Post.table_name · Post.columns_hash มีกี่แผ่น · มีช่องอะไร · ลิ้นชักชื่ออะไร · ช่องแต่ละช่องเป็นชนิดไหน

method ที่ลงท้ายด้วย ? ตอบ true/false เสมอ (กฎเดียวกับ nil? ในบท 94) · ส่วนที่ขึ้นต้นด้วย Post. คือถามทั้งลิ้นชัก · ที่ขึ้นต้นด้วย post. คือถามแฟ้มแผ่นเดียว
ยังมีอีกเยอะ (กฎตรวจข้อมูลบท 110 · วิธีค้นแบบละเอียดบท 113) ค่อย ๆ เจอทีละบท

ภารกิจ — ถาม Post ว่ามันรู้อะไรเกี่ยวกับตัวเองบ้าง

🎯 ภารกิจ (~10 นาที)

ทำในโปรเจกต์ wongnok_blog ของคุณจาก Lab 1 (บท 105) · เก็บโปรเจกต์นี้ไว้ บท 107 ใช้ต่อ

  1. เปิด terminal ในโฟลเดอร์ wongnok_blog · ตั้ง DATABASE_URL ให้ชี้ PostgreSQL ของคุณแบบเดียวกับบท 105 (export DATABASE_URL=…)
  2. ดูว่ามี model Post หรือยัง: ls -1F app/models · ถ้าเห็น post.rb แล้ว ข้ามข้อ 3
  3. สร้างตาราง posts ด้วยคำสั่งเดียวกับบท 99:
    bin/rails generate model Post title:string body:text
    bin/rails db:migrate
  4. เปิด bin/rails console แล้วพิมพ์ทีละบรรทัด:
    Post.table_name
    Post.column_names
    Post.columns_hash["body"].type
    Post.count
  5. ตอบในใจ: ช่องไหนใน column_names ที่คุณไม่ได้สั่งตอน generate · และแต่ละช่องมาจากไหน

ควรเห็น: "posts" · Array 5 ชื่อ ["id", "title", "body", "created_at", "updated_at"] · :text · และ 0 (ถ้าเพิ่งสร้างตาราง ยังไม่มีบทความ)

columns_hash คือ Hash (บท 92) ที่ใช้ชื่อช่องเป็นกุญแจ แล้วได้ข้อมูลของช่องนั้นกลับมา · .type = "ช่องนี้เก็บของชนิดไหน" · :text = ข้อความยาว ตรงกับที่สั่ง body:text ไว้

ถ้าเจอ error นี้ แปลว่า… (ข้อความจริงจากเครื่องตรวจ · ตัดบรรทัด from … ยาว ๆ ออก)

เห็นอะไรแปลว่า
PG::UndefinedTable: ERROR: relation "posts" does not exist (ActiveRecord::StatementInvalid) มีไฟล์ post.rb แล้วแต่ยังไม่มีลิ้นชักในตู้ → ลืม bin/rails db:migrate (ข้อ 3)
undefined method 'colum_names' for class Post (NoMethodError) ตามด้วย Did you mean? column_names พิมพ์ชื่อ method ผิด · Ruby เดาคำที่ถูกให้แล้วในบรรทัด Did you mean?
uninitialized constant Pots (NameError) ตามด้วย Did you mean? Post พิมพ์ชื่อ class ผิด (หรือยังไม่มีไฟล์ app/models/post.rb เลย ถ้าข้อความเป็น constant Post)
Database not found: … (ActiveRecord::NoDatabaseError) ชี้ไปถูกเครื่องแล้ว แต่ยังไม่มี database ชื่อนั้น → bin/rails db:prepare แบบบท 105
connection to server on socket "/tmp/.s.PGSQL.5432" failed terminal หน้าต่างนี้ยังไม่ได้ export DATABASE_URL=… หรือ PostgreSQL ไม่ได้เปิดอยู่ (error ตัวเดียวกับที่เจอในบท 98)

ผลจริงจากเครื่องตรวจ (เลขเวลาหน้าชื่อไฟล์ migration · เวลา s/ms ของคุณจะต่าง):

$ ls -1F app/models
application_record.rb
concerns/

$ bin/rails generate model Post title:string body:text
      invoke  active_record
      create    db/migrate/20261104114505_create_posts.rb
      create    app/models/post.rb
      invoke    test_unit
      create      test/models/post_test.rb
      create      test/fixtures/posts.yml

$ bin/rails db:migrate
== 20261104114505 CreatePosts: migrating ======================================
-- create_table(:posts)
   -> 0.0034s
== 20261104114505 CreatePosts: migrated (0.0034s) =============================

$ bin/rails console
Loading development environment (Rails 8.1.4)
wongnok-blog(dev):001> Post.table_name
=> "posts"
wongnok-blog(dev):002> Post.column_names
=> ["id", "title", "body", "created_at", "updated_at"]
wongnok-blog(dev):003> Post.columns_hash["body"].type
=> :text
wongnok-blog(dev):004> Post.count
  Post Count (3.2ms)  SELECT COUNT(*) FROM "posts" /*application='WongnokBlog'*/
=> 0
  • ข้อ 5: สั่งไปแค่ title กับ body แต่ได้มา 5 ช่อง · id — Rails เติมให้ทุกตารางเอง (เลขประจำแถว) · created_at / updated_at — มาจากบรรทัด t.timestamps ในไฟล์ migration ที่ generate สร้าง (บท 99)
  • post.rb ยังมีแค่สองบรรทัดเหมือนเดิม · ทุกคำตอบใน console มาจากตาราง posts ใน database
  • Post.count เป็นคำถามเดียวที่ยิง SQL ให้เห็นใน log (SELECT COUNT(*) FROM "posts" = "นับแฟ้มในลิ้นชัก posts") · อีกสามคำถามเป็นเรื่องหน้าตาของตาราง Rails ไปถามไว้ครั้งเดียวแล้วจำไว้ และไม่ได้โชว์คำถามนั้นใน log

ทดสอบความเข้าใจ

1. ช่างเพิ่มช่อง views ลงตาราง posts ด้วย migration (ใบสั่งช่างมาต่อเติมตู้ — บท 108) โดยไม่แตะ app/models/post.rb เลย · เปิด console ใหม่แล้วสั่ง Post.column_names จะเห็นอะไร?

ตัวเลือกที่ 2 ถูก · ผมลองจริง: เพิ่มช่องด้วย migration แล้ว Post.column_names ได้ ["id", "title", "body", "created_at", "updated_at", "views"] ทั้งที่ post.rb ยังมีแค่สองบรรทัด

ตัวเลือกที่ 1 ผิด — นั่นคือวิธีของ Go ที่ต้องเพิ่ม field ใน struct เอง · Rails ไม่มีรายชื่อช่องในไฟล์ model ให้แก้ · ตัวเลือกที่ 3 ผิด — ไม่มีอะไร "ไม่ตรงกัน" เพราะ post.rb ไม่ได้บอกช่องไว้ตั้งแต่แรก · ตัวเลือกที่ 4 ผิด — generate สร้างไฟล์ใหม่ ไม่เกี่ยวกับการอ่านช่องจากตาราง

จุดที่ต้องจำ: ใน Rails ตารางคือที่เดียวที่บอกว่า model มีช่องอะไร · อยากรู้ ให้ถาม Post.column_names ไม่ใช่เปิด post.rb

2. โค้ดแบบไหนตรงกับ Active Record pattern?

ตัวเลือกที่ 4 ถูก · แก่นของ Active Record คือ object หนึ่งตัว = หนึ่งแถว และรู้วิธีบันทึกตัวเอง (แฟ้มเดินเข้าลิ้นชักเอง)

ตัวเลือกที่ 1 ผิด — นั่นคือ Data Mapper (มีพนักงานคลังแยก) · ตัวเลือกที่ 2 ผิด — นั่นคือไม่ใช้ ORM เลย เขียน SQL ด้วยมือ · ตัวเลือกที่ 3 ผิด — ไม่มี database และไม่มี object ที่รู้วิธีเก็บตัวเอง

จุดที่ต้องจำ: แยกสองสายด้วยคำถามเดียว "ใครถือคำสั่ง save" · ตัวข้อมูลเอง = Active Record · คนกลาง = Data Mapper

3. สร้าง app/models/article.rb ที่มีแค่ class Article < ApplicationRecord กับ end · database มีแต่ตาราง posts · สั่ง Article.count จะได้อะไร?

ตัวเลือกที่ 1 ถูก · ผลจริง: SELECT COUNT(*) FROM "articles" แล้วได้ PG::UndefinedTable: ERROR: relation "articles" does not exist · แก้ด้วย self.table_name = "posts"

ตัวเลือกที่ 2 ผิด — Rails ไม่รู้ความหมายของคำ รู้แค่กฎตัดต่อตัวอักษร · ตัวเลือกที่ 3 ผิด — Rails ไม่สร้างตารางให้เอง ตารางเกิดจาก migration เท่านั้น · ตัวเลือกที่ 4 ผิด — console เปิดได้ปกติ และ Article.table_name ยังตอบ "articles" ได้ · พังตอนไปหาตารางจริงครั้งแรก

จุดที่ต้องจำ: ชื่อ class → ชื่อตาราง ตามกฎ · ชื่อไม่ตามกฎ ต้องบอกด้วย self.table_name (Go ใช้ TableName())

4. ใน Go Post{Title: "x"} สร้างได้แม้ไม่มี database · แต่ Rails Post.new(title: "x") ตอนยังไม่มี database ได้ ActiveRecord::NoDatabaseError · เพราะอะไร?

ตัวเลือกที่ 3 ถูก · ช่องของ Post อยู่ในตาราง ไม่ได้อยู่ในไฟล์ · สร้างแฟ้มเปล่ายังต้องไปดูลิ้นชักก่อน · นี่คือ "ราคา" ข้อ "ทดสอบยาก" ของ Active Record

ตัวเลือกที่ 1 ผิด — ไม่เกี่ยวกับความเร็ว · ตัวเลือกที่ 2 ผิด — ผลจริงในหัวข้อ 4 เห็นว่าหลัง Post.new ยังได้ id: nil และไม่มี SQL INSERT จนกว่าจะ save! · ตัวเลือกที่ 4 ผิด — object ทั่วไปอย่าง "abc" หรือ Hash สร้างได้ปกติ · มีแค่ model ที่ต้องรู้จักตาราง

จุดที่ต้องจำ: Active Record แลกความสั้นกับการผูกติด database · object ข้อมูลกับการเก็บข้อมูลแยกกันไม่ออก

ถามกลับได้เลย — ถ้าผลใน console ของคุณไม่ตรงกับในบท ให้ก็อปบรรทัดที่พิมพ์พร้อมผลทั้งก้อน (รวมบรรทัด SQL ถ้ามี) มาถาม · ข้อความ error บรรทัดแรกกับชื่อ error ในวงเล็บท้ายบรรทัด บอกได้เกือบทุกครั้งว่าพังที่ตู้ ที่ลิ้นชัก หรือที่การพิมพ์

ถ้าเปิดสไลด์ดูประกอบ

หน้าสไลด์เขียนของจริงต้องสนใจไหม
5Post.new(title: "Hello", user_id: 1) ตาราง posts ตอนนี้ยังไม่มีช่อง user_id → ActiveModel::UnknownAttributeError ใช่ ถ้าพิมพ์ตาม — ตัด user_id ออกก่อน
5"Post ไปถามตารางเอง … ตอน app เริ่มทำงาน" ในโหมด development ผมเช็คแล้ว Rails ไปถามตอนใช้ Post ครั้งแรก ไม่ใช่ตอนเปิดแอป · แก่นเหมือนกัน: ถามตอนรัน ไม่ใช่ตอนเขียนโค้ด ไม่ต้อง
5ตัวแปรชื่อ p ใช้ได้ แต่ชนกับชื่อคำสั่งพิมพ์ p (บท 93) · บทนี้ใช้ post ไม่ต้อง
6"Active Record ไม่ใช่ชื่อ gem" ชื่อมาจาก pattern จริง แต่ Rails ก็มี gem ชื่อ activerecord (activerecord (8.1.4) ใน Gemfile.lock) ไม่ต้อง — แค่อย่างงเวลาเห็นชื่อ gem
6Martin Fowler · หนังสือปี 2002 · "มาก่อน Rails 2 ปี" Rails Guides ยืนยันว่า pattern มาจากหนังสือของ Fowler · ปีไม่ได้ตรวจในบทนี้ ไม่ต้อง
7จัด GORM ไว้ฝั่ง Active Record ในโค้ดหน้า 5 struct ของ GORM ไม่ได้ save ตัวเอง ต้องยื่นให้ db.Create(&p) · การจัดกลุ่มแบบนี้เถียงกันได้ ไม่ต้อง — ดูที่ "ใครถือคำสั่ง save" ดีกว่าจำป้าย

อ้างอิง