บทเรียน 90 · Rails · Day 1

Rails คืออะไร — เข้าใจภาพรวมแบบร้านอาหาร ก่อนเขียนโค้ดบรรทัดแรก

~20 นาที · บทแรกของฝั่ง Rails · ยังไม่ต้องติดตั้งอะไร · ถ้าเคยผ่าน บทเรียน 38 (Gin) มาจะเห็นภาพเร็วขึ้น

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

🔁 ทวนก่อนเริ่ม — คำจากฝั่ง Go ที่บทนี้จะหยิบมาเทียบ (จำไม่ได้ก็ไม่เป็นไร บทนี้อธิบายซ้ำให้)

🧰 เวอร์ชันที่ใช้ตรวจ: Ruby 4.0.9 · Rails 8.1.4 · สไลด์ใช้ Ruby 4.0.6 · Rails 8.1.3 — เป็นรุ่นแก้บั๊กของสายเดียวกัน ใช้ตามสไลด์ได้เลย

1 · เปิดหน้าเว็บหนึ่งครั้ง เกิดอะไรขึ้น

ตอนคุณพิมพ์ที่อยู่เว็บแล้วกด Enter โปรแกรม browser (Chrome, Safari) จะส่งคำขอออกไปหาเครื่องอีกเครื่องหนึ่ง
เครื่องนั้นมีโปรแกรมเปิดรออยู่ เรียกว่า server · server อ่านคำขอ ทำหน้าเว็บเสร็จแล้วส่งคำตอบกลับมา
คำตอบส่วนใหญ่คือ HTML — ข้อความที่บอก browser ว่าหน้าเว็บมีหัวเรื่องอะไร ย่อหน้าอะไร แล้ว browser ก็วาดออกมาให้เห็น

ลูกค้า (browser) ร้าน (server) ── ① ใบสั่งอาหาร (request) ──────────────────────▶ "ขอหน้า /posts/42 หน่อย" ทำหน้าเว็บ (HTML) ◀──────────────────── ② จานอาหาร (response) ── หน้า HTML ของบทความเลขที่ 42 browser เอา HTML มาวาดเป็นหน้าเว็บให้คนเห็น
🍜 เทียบให้เห็นภาพ — เว็บแอปคือร้านอาหาร

browser คือลูกค้า · request คือใบสั่งอาหาร ที่เขียนว่าอยากได้อะไร · server คือร้านที่เปิดอยู่ · response คือจานอาหารที่ยกมาเสิร์ฟ
ลูกค้าไม่ต้องรู้ว่าในครัวทำยังไง ขอแค่ได้จานที่สั่ง · ภาพนี้จะใช้ต่อไปทั้ง 16 บท

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

ใน Go คุณเคยเป็นเจ้าของร้านแบบนี้มาแล้ว — บท 37 เปิด server ด้วย net/http แล้วบท 38 ใช้ Gin
ทุกบรรทัดอย่าง r.GET("/users/:id", …) คือการบอกว่า "ใบสั่งแบบนี้ ให้ฟังก์ชันนี้ทำ" · Rails ทำงานเดียวกันนี้ แค่แบ่งงานในครัวละเอียดกว่า (หัวข้อ 5)

🗣 ศัพท์ที่เพิ่งเรียนไป
browser / client
ฝั่งที่ขอ · ในเว็บคือโปรแกรมท่องเว็บ แต่จริง ๆ อะไรที่ส่งคำขอได้ก็นับเป็น client เช่นคำสั่ง curl (คำสั่งที่ขอหน้าเว็บจาก terminal ได้โดยไม่ต้องเปิด browser) · ถาม senior ได้ว่า "ปัญหานี้อยู่ฝั่ง client หรือฝั่ง server"
server
ฝั่งที่ตอบ · โปรแกรมที่เปิดค้างไว้รอคำขอ · ใน Rails เปิดด้วยคำสั่ง bin/rails server (บท 100)
request / response
คำขอหนึ่งครั้ง กับคำตอบของมัน · มาเป็นคู่เสมอ · พิมพ์บอก AI ได้ว่า "walk me through the request/response cycle of this page"
HTML
ภาษาบอกโครงหน้าเว็บ (หัวเรื่อง ย่อหน้า ลิงก์) · server ส่งมา browser วาดออกมา

2 · Ruby กับ Rails ไม่ใช่ของชิ้นเดียวกัน

ชื่อ "Ruby on Rails" ฟังเหมือนของชิ้นเดียว แต่จริง ๆ เป็นของสองชิ้นซ้อนกัน
Ruby คือภาษาโปรแกรม เหมือนที่ Go เป็นภาษา · ใช้เขียนอะไรก็ได้ ไม่จำเป็นต้องเป็นเว็บ
Rails คือ framework — ชุดเครื่องมือทำเว็บสำเร็จรูป ที่เขียนด้วยภาษา Ruby และรันอยู่บน Ruby

ภาษาframework ที่เขียนด้วยภาษานั้นคุณเคยเจอที่ไหน
GoGinบท 38
TypeScriptNext.jsบท 69
RubyRailsเริ่มบทนี้

แล้ว framework "ทำอะไรแทนเรา"? คำตอบสั้น ๆ คือ ทำส่วนที่ทุกเว็บต้องมีเหมือนกัน — รับคำขอ · แยกว่า URL ไหนไปหาใคร · คุยกับ database · ประกอบหน้า HTML
เราเขียนแค่ส่วนที่เป็นของร้านเราเอง เช่น "หน้าบทความต้องโชว์หัวเรื่องกับเนื้อหา"
อีกเรื่องที่ต่างจากการเรียก library: framework เป็นฝ่ายเรียกโค้ดของเรา — ใน Gin คุณเขียน handler ไว้ แล้ว Gin เรียกมันเองตอนมีคำขอเข้ามา

🏪 เทียบให้เห็นภาพ — เปิดร้านเอง กับ ร้านแฟรนไชส์

Go ที่เรียนมา = เปิดร้านเองตั้งแต่ศูนย์ — จ้างพนักงานต้อนรับเอง (Gin router) · เขียนใบงานให้เชฟเอง (handler) · ซื้อตู้เก็บของเอง (GORM + database) · ทุกชิ้นคุณเลือกและต่อเข้าหากันเอง
Rails = ร้านแฟรนไชส์ที่ได้ครัวครบชุด — ส่งของมาพร้อมใช้ทุกชิ้น ขอแค่คุณวางของตามตำแหน่งที่แฟรนไชส์กำหนด แล้วทุกอย่างทำงานต่อกันเอง

ที่ไม่เหมือนกัน: แฟรนไชส์จริงต้องจ่ายค่าสิทธิ์และห้ามเปลี่ยนเมนู · Rails ใช้ฟรี และถอดชิ้นไหนออกไปใส่ของอื่นแทนก็ได้ — แต่ยิ่งเปลี่ยนเยอะ ยิ่งเสียข้อดีของการ "มาครบชุด" และต้องดูแลเองมากขึ้น

🗣 ศัพท์ที่เพิ่งเรียนไป
programming language (ภาษาโปรแกรม)
ตัวกำหนดว่าเขียนโค้ดหน้าตาแบบไหน · Ruby รันด้วยคำสั่ง ruby เหมือน Go รันด้วย go run · เวลาถาม AI ให้บอกชั้นให้ชัด: "Is this a plain Ruby question or a Rails question?"
framework
ชุดโค้ดสำเร็จรูปที่วางโครงร้านให้ แล้วเรียกโค้ดของเราตามจังหวะของมัน · ถาม senior ได้ว่า "ส่วนนี้ Ruby ทำเอง หรือ Rails เติมให้" — จะได้รู้ว่าต้องเปิดเอกสารเล่มไหน
gem
ชื่อเรียก "ชุดโค้ดที่คนอื่นเขียนไว้ให้ลงมาใช้" ของ Ruby · ในภาพร้านอาหารคืออุปกรณ์สำเร็จรูปที่ซื้อมาใช้ในครัว · เทียบได้กับ module ที่ go get มา · Rails เองก็เป็น gem ตัวหนึ่ง (รายละเอียดบท 98)

3 · ลองดูหน้าตา Ruby ครั้งแรก

ยังไม่ต้องจำไวยากรณ์ · ดูแค่ว่า Ruby ตั้งใจให้อ่านจากซ้ายไปขวาแล้วเป็นประโยคภาษาอังกฤษ · นี่คือไฟล์ first.rb สามบรรทัด (บรรทัดแรกเป็น comment อีกสองบรรทัดเป็นคำสั่ง) จากตัวอย่างในสไลด์หน้า 4:

# first.rb
puts "abc".upcase.reverse
5.times { puts "hello" }

ผลรันจริง · บรรทัดที่ขึ้นต้นด้วย $ คือคำสั่งที่พิมพ์ใน terminal (ตัว $ ไม่ต้องพิมพ์) · บรรทัดที่เหลือคือสิ่งที่ขึ้นจอ:

$ ruby first.rb
CBA
hello
hello
hello
hello
hello

อ่านทีละบรรทัดแบบภาษาคน:

ทีนี้ลองบรรทัดที่อยู่ในสไลด์หน้าเดียวกัน — 3.days.ago อ่านว่า "สามวันที่แล้ว"

puts 3.days.ago

ไฟล์ days.rb มีแค่บรรทัดเดียวข้างบนนี้ · รันด้วยคำสั่ง ruby days.rb (Ruby เปล่า ๆ ยังไม่มี Rails) จะได้วันที่ของสามวันก่อน หรือได้อย่างอื่น?

ผลรันจริง — พัง:

$ ruby days.rb
days.rb:1:in '<main>': undefined method 'days' for an instance of Integer (NoMethodError)

puts 3.days.ago
      ^^^^^

อ่าน error ทีละท่อน:

  • days.rb:1 = ไฟล์ days.rb บรรทัดที่ 1 · in '<main>' = พังในเนื้อไฟล์หลัก ไม่ได้อยู่ในคำสั่งย่อยไหน (ตอนนี้ข้ามไปได้)
  • undefined method 'days' for an instance of Integer = "ตัวเลข (Integer) ไม่รู้จักคำสั่งชื่อ days"
  • ^^^^^ = ลูกศรชี้ว่าพังตรงคำไหนในบรรทัด

ทีนี้รันไฟล์เดิม แต่สั่งให้โหลด Active Support (ชิ้นส่วนหนึ่งของ Rails) เข้ามาก่อน · -r ย่อมาจาก require แปลว่า "โหลดสิ่งนี้เข้ามาก่อนรัน":

$ ruby -ractive_support/all days.rb
2026-10-31 20:35:55 +0700

ผ่าน · ไฟล์เดียวกันเป๊ะ ต่างกันแค่โหลด Active Support เข้ามา
แปลว่า days ไม่ใช่คำของ Ruby · Rails เป็นคนเติมให้ (วันที่ของคุณจะต่างจากนี้ เพราะรันคนละวัน)

📒 เทียบให้เห็นภาพ — สมุดศัพท์ประจำร้าน

Ruby เปล่า ๆ เหมือนพนักงานใหม่ที่พูดไทยคล่อง แต่ยังไม่รู้ศัพท์ในครัวของร้านนี้ — ได้ยินคำว่า "สามวันก่อน" แบบย่อ ๆ ของร้านแล้วงง
Active Support คือสมุดศัพท์ที่แฟรนไชส์แจกมา พอเปิดร้าน Rails พนักงานทุกคนก็ได้สมุดเล่มนี้ทันที เลยเข้าใจ 3.days.ago (สามวันก่อน) · 1.megabyte (หนึ่งเมกะไบต์) ได้หมด

ที่ไม่เหมือนกัน: สมุดศัพท์ไม่ได้เปลี่ยนภาษาไทย แต่ Active Support เติมคำสั่งใหม่ลงไปในของของ Ruby โดยตรง — ตัวเลขทุกตัวในโปรแกรมรู้จัก days ขึ้นมาเอง · นี่คือเหตุผลที่มือใหม่แยกไม่ออกว่าบรรทัดไหนเป็น Ruby บรรทัดไหนเป็น Rails · วิธีพิสูจน์คือรันด้วย ruby เปล่า ๆ ถ้าพังแปลว่าเป็นของ Rails

🗣 ศัพท์ที่เพิ่งเรียนไป
method
คำสั่งที่ของชิ้นหนึ่งทำได้ เช่น upcase ของข้อความ · times ของตัวเลข · เรียกด้วยจุด (ละเอียดบท 91 และ 93)
NoMethodError
error ที่แปลว่า "ของชิ้นนี้ไม่มีคำสั่งชื่อนี้" · เจอบ่อยที่สุดในชีวิตคน Ruby · เอาทั้งบรรทัดไปถาม AI ได้ว่า "Why do I get NoMethodError here — is this method from Rails?"
Active Support
ชิ้นส่วนของ Rails ที่เติมคำสั่งสะดวก ๆ ให้ Ruby (days · megabyte · และอีกเยอะ) · ในแอป Rails ถูกโหลดให้อัตโนมัติ ไม่ต้องสั่ง -r เอง

4 · แอปตัวอย่างของคอร์ส: บล็อก wongnok_blog

ตลอดคอร์ส Rails สไลด์จะสร้างแอปเดียวต่อเนื่องกันไป ชื่อ wongnok_blog — เป็นเว็บบล็อก
บล็อกคือเว็บที่มีบทความหลายชิ้น คนเข้ามาอ่านได้ เจ้าของเขียนใหม่ แก้ หรือลบได้
คำว่า "บทความ" ภาษาอังกฤษคือ post · และคำนี้จะโผล่มาสี่หน้าตา:

เขียนว่าหมายถึงใน Go ที่เคยเขียน เทียบได้กับ
Post (ตัวใหญ่ · เอกพจน์)แม่พิมพ์ของบทความ — บอกว่าบทความหนึ่งชิ้นมีอะไรบ้าง (หัวเรื่อง เนื้อหา)type Post struct { … }
posts (ตัวเล็ก · พหูพจน์)ตารางใน database ที่เก็บบทความทุกชิ้นตาราง posts ที่ GORM เดาให้จาก struct Post
/postsURL ของหน้ารวมบทความทั้งหมดr.GET("/posts", …)
/posts/42URL ของบทความเลขที่ 42 ชิ้นเดียวr.GET("/posts/:id", …)

กฎจำง่าย: ตัวใหญ่เอกพจน์ = แม่พิมพ์ (ชนิดของข้อมูล) · ตัวเล็กพหูพจน์ = ที่เก็บหลายชิ้น หรือ URL

🗂 เทียบให้เห็นภาพ — คนดูแลสต็อก กับลิ้นชักแฟ้ม

ในห้องเก็บของของร้าน มีตู้เก็บแฟ้ม (database) · ในตู้มีลิ้นชักบทความ (ตาราง posts) · ในลิ้นชักมีแฟ้มหลายแผ่น แผ่นละหนึ่งบทความ (แถว · row)
Post คือคนดูแลสต็อกประจำลิ้นชักบทความ — รู้ว่าแฟ้มหนึ่งแผ่นมีช่องอะไร (หัวเรื่อง เนื้อหา) และใครอยากได้บทความ ต้องขอผ่านเขา

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

❓ "ใน Go ต้องประกาศ struct ชัด ๆ แล้ว Rails เอา Post มาจากไหน?"

Post ไม่ได้โผล่มาเฉย ๆ — มีไฟล์ประกาศอยู่จริงชื่อ app/models/post.rb (Rails มีคำสั่งเขียนไฟล์นี้ให้ · บท 99)
ที่ดูเหมือน "มาเฉย ๆ" เพราะไฟล์นั้นสั้นมาก · เทียบกันตรง ๆ:

// Go: ท่อนหนึ่งในโปรเจกต์ — ต้องเขียนทุกช่องเอง
type Post struct {
    ID    uint
    Title string
    Body  string
}
# Rails: ไฟล์ app/models/post.rb ทั้งไฟล์ — ที่ Rails สร้างให้จริง
class Post < ApplicationRecord
end

พิสูจน์ได้ (แค่ดู ยังไม่ต้องทำตาม): ผมสร้างตาราง posts ที่มีช่อง title กับ body ไว้ก่อน (วิธีสร้างเรียน Day 2) แล้วถาม Post ว่ารู้จักช่องอะไรบ้าง:

$ bin/rails runner 'p Post.column_names'
["id", "title", "body", "created_at", "updated_at"]

สรุป: ใน Go คุณเขียน struct ก่อน แล้ว GORM ทำตามให้ (AutoMigrate บท 27) · ใน Rails กลับกัน — ทำตารางก่อน แล้ว model อ่านตามตาราง

บท 91–97 ยังไม่มี Rails · ตัวอย่างที่เป็นบทความในบทพวกนั้นจะจำลองขึ้นด้วย Ruby ธรรมดา และจะบอกทุกครั้งว่ากำลังจำลอง

🗣 ศัพท์ที่เพิ่งเรียนไป
model
ส่วนของแอปที่ดูแลข้อมูล · Post คือ model ของบทความ · ถาม senior ได้ว่า "model นี้ผูกกับตารางไหน มีคอลัมน์อะไรบ้าง"
table / row (ตาราง / แถว)
ตารางคือลิ้นชัก · แถวคือแฟ้มหนึ่งแผ่น = บทความหนึ่งชิ้น · ถ้าพูดว่า "post เลข 42" = แถวที่ id เป็น 42
singular / plural (เอกพจน์ / พหูพจน์)
Rails ใช้สองรูปนี้ต่างหน้าที่กันเสมอ (Post กับ posts) · พิมพ์บอก AI ได้ว่า "follow Rails naming conventions for singular and plural"

5 · MVC — เดินตามใบสั่ง "ขอดูบทความที่ 42" ทั้งเส้น

ในร้าน Rails งานไม่ได้กองอยู่ที่คนคนเดียว · ใบสั่งหนึ่งใบจะผ่านมือ 4 ฝ่าย ตามลำดับ
สามฝ่ายหลักมีชื่อย่อรวมกันว่า MVC = Model · View · Controller · อีกฝ่ายคือ routes ที่ยืนรับอยู่หน้าร้าน
บทนี้ดูแค่เส้นทาง ยังไม่ต้องดูโค้ด:

ลูกค้าพิมพ์ localhost:3000/posts/42 แล้วกด Enter │ ใบสั่ง: GET /posts/42 (GET = "ขอดู") ▼ ① พนักงานต้อนรับ = routes (ไฟล์ config/routes.rb) "/posts/42 เหรอ → ส่งแผนกบทความ ทำเมนู show" │ ▼ ② หัวหน้าเชฟแผนกบทความ = controller ชื่อ PostsController เมนูที่ทำ = action ชื่อ show อ่านรายละเอียดในใบสั่ง (params): "เลข 42" │ "ขอแฟ้มบทความเลข 42 หน่อย" ▼ ③ ห้องเก็บวัตถุดิบ = model ชื่อ Post ไปหยิบจากตู้เก็บแฟ้ม (database) ลิ้นชัก posts แฟ้มเลข 42 │ ได้บทความกลับมา: หัวเรื่อง + เนื้อหา ▼ ④ จัดจาน = view (ไฟล์ในโฟลเดอร์ app/views/posts/) เอาหัวเรื่องกับเนื้อหาใส่ลงหน้า HTML │ ▼ ⑤ ยกจานเสิร์ฟ = response → ลูกค้าเห็นหน้าบทความที่ 42

localhost:3000 คือที่อยู่ของร้านที่เปิดอยู่ในเครื่องคุณเอง — localhost = "เครื่องนี้" · 3000 = เลขประตูร้าน (port) ที่ Rails ใช้เป็นค่าเริ่มต้น (บท 100 จะเปิดจริง) · show คือชื่อที่ Rails ตกลงกันไว้สำหรับเมนู "ดูหนึ่งชิ้น"
เทียบกับ Gin: handler ฟังก์ชันเดียวของคุณทำทุกขั้นเอง — อ่าน id จาก URL · เรียก GORM · ประกอบคำตอบ · Rails แยกขั้นพวกนี้ไปไว้คนละไฟล์ คนละหน้าที่

👨‍🍳 เทียบให้เห็นภาพ — ครัวที่แบ่งหน้าที่ชัด

พนักงานต้อนรับไม่ทำอาหาร · หัวหน้าเชฟไม่ไปค้นตู้เอง · คนดูแลสต็อกไม่จัดจาน · ทุกคนทำแค่ส่วนของตัวเองแล้วส่งต่อ
ข้อดีคือพอหน้าเว็บผิด คุณรู้ว่าต้องไปหาใคร: URL ผิดทาง → routes · ข้อมูลผิด → model · หน้าตาผิด → view

ที่ไม่เหมือนกัน: ในร้านจริงมีคนสี่คนเดินไปมา · ใน Rails ทั้งหมดคือโค้ดที่ถูกเรียกต่อกันเป็นทอด ๆ ในโปรแกรมเดียว เสร็จในเสี้ยววินาที · และไม่มีใครห้ามให้ controller ทำ HTML เอง — แค่ "ข้อตกลง" บอกว่าไม่ควร

🗣 ศัพท์ที่เพิ่งเรียนไป
MVC (Model–View–Controller)
วิธีแบ่งโค้ดเป็นสามฝ่าย: ข้อมูล · หน้าตา · ตัวกลางที่คุมจังหวะ · ถาม senior ได้ว่า "logic นี้ควรอยู่ใน model หรือ controller"
routes
ตารางจับคู่ URL กับ controller + action · เทียบกับ r.GET(…) ของ Gin (บท 101)
controller / action
controller คือแผนก (เช่น PostsController) · action คือเมนูแต่ละอย่างของแผนก (เช่น show) · เวลาเขียนรวมกันจะเห็นเป็น PostsController#show — # ตรงนี้แค่คั่นชื่อแผนกกับชื่อเมนู ไม่ใช่ comment (บท 102)
view
ไฟล์แม่แบบหน้า HTML ที่เติมข้อมูลลงไป · พิมพ์บอก AI ได้ว่า "which view renders this page?" (บท 103)

6 · Convention over Configuration — ทุกสาขาวางของที่เดิม

ในแผนภาพข้างบน ไม่มีใครบอก Rails เลยว่า "แผนกบทความชื่อ PostsController" หรือ "view อยู่โฟลเดอร์ไหน"
Rails รู้เองเพราะทุกอย่างตั้งชื่อตามกฎเดียวกัน · รู้คำว่า posts คำเดียว ก็เดาได้ทั้งสาย:

คำเดียว: posts │ ├─▶ Post แม่พิมพ์ของบทความ (model) → app/models/post.rb ├─▶ ตาราง posts ลิ้นชักในตู้เก็บแฟ้ม (database) ├─▶ PostsController หัวหน้าเชฟแผนกบทความ → app/controllers/posts_controller.rb └─▶ app/views/posts/ โต๊ะจัดจานของแผนกบทความ (view)

ใน Go คุณต้องบอกทุกอย่างเอง: route นี้ไปฟังก์ชันไหน · template อยู่ไฟล์ไหน
จริง ๆ คุณเคยเห็นความคิดนี้มาแล้วนิดหนึ่ง — GORM เดาว่า struct User อยู่ตาราง users (บท 27) · Rails แค่เอาความคิดเดียวกันนี้ไปใช้กับทุกห้องในร้าน
สังเกตว่าชื่อไฟล์เขียนตัวเล็กคั่นด้วย _ (posts_controller.rb) ส่วนชื่อในโค้ดเขียนติดกันตัวใหญ่ (PostsController) · กฎทั้งชุดเรียนบท 99

🏬 เทียบให้เห็นภาพ — ร้านแฟรนไชส์ทุกสาขาหน้าตาเหมือนกัน

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

ที่ไม่เหมือนกัน: ในร้านจริง ถ้าวางของผิดที่ เพื่อนจะทักทันที · Rails ไม่ทักตอนคุณพิมพ์ชื่อผิด — มันแค่หาไม่เจอ แล้วพังตอนมีคนเปิดหน้านั้น · ต่างจาก Go ที่ go build ฟ้องก่อนได้รัน

เพราะชื่อคือตัวบอกทาง สะกดผิดตัวเดียวก็หาไม่เจอ — เช่นตั้งชื่อ PostController (ขาด s) แทน PostsController · เวลาพิมพ์ตามสไลด์ให้ดูตัวใหญ่ตัวเล็กและตัว s ท้ายคำให้ดี

🗣 ศัพท์ที่เพิ่งเรียนไป
Convention over Configuration
"ทำตามข้อตกลง แทนการเขียนตั้งค่า" · ตั้งชื่อถูกกฎแล้ว framework เดาส่วนที่เหลือให้ · พิมพ์บอก AI ได้ว่า "follow Rails conventions, don't add extra configuration"
configuration (config)
การเขียนบอกโปรแกรมตรง ๆ ว่าอะไรอยู่ไหน เชื่อมกับอะไร · Go ใช้วิธีนี้เป็นหลัก
the Rails way
วิธีที่ Rails ปูทางไว้ให้ · ถาม AI ว่า "what is the Rails way to do this?" มักได้คำตอบที่สั้นกว่าถามว่า "how to do this in Rails"

7 · ความเชื่อ 3 ข้อของ Rails และราคาที่ต้องจ่าย

สไลด์หน้า 11 บอกว่า Rails ยืนอยู่บนความเชื่อสามข้อ · ข้อแรกเพิ่งเรียนไป อีกสองข้อสั้นมาก:

ความเชื่อแปลเป็นภาษาคนในภาพร้านอาหาร
Convention over Configurationตั้งชื่อตามกฎ แล้วไม่ต้องเขียนตั้งค่าทุกสาขาวางของที่เดิม
OmakaseRails เลือกเครื่องมือให้ครบชุดแล้ว ไม่ต้องเลือกเองคำญี่ปุ่น แปลว่า "แล้วแต่เชฟ" — สั่งเป็นชุด เชฟคิดมาให้ว่าอะไรเข้ากับอะไร
The One Person Frameworkคนคนเดียวควรทำเว็บได้ครบ ตั้งแต่ database ถึงหน้าเว็บเจ้าของคนเดียวเปิดร้านได้ทั้งร้าน เพราะครัวมาครบ

ในคอร์สนี้คุณทำ backend Go หนึ่งโปรเจกต์ + frontend Next.js อีกโปรเจกต์ + Keycloak + Redis — แต่ละชิ้นเรียนกันเป็นวัน ๆ · ความเชื่อข้อ 3 บอกว่าใน Rails ทั้งหมดนี้ควรอยู่ในโปรเจกต์เดียวได้
แต่สไลด์ก็เตือนตรง ๆ ว่าทั้งสามข้อมีราคา (หน้า 12):

สิ่งที่ได้

  • เขียนโค้ดน้อยลงมาก
  • ของครบในกล่องเดียว ไม่ต้องประกอบเอง
  • ทุกโปรเจกต์ Rails โครงเหมือนกัน เปิดของคนอื่นแล้วอ่านออก
  • มี "ครัวทดลองหลังร้าน" ลองโค้ดกับแอปจริงได้สด ๆ (บท 104)

สิ่งที่เสีย

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

ข้อแรกของฝั่ง "เสีย" คือข้อที่คนมาจาก Go รู้สึกที่สุด — Go ตรวจชื่อทุกตัวก่อนเปิดร้าน · Ruby ไปรู้เอาตอนครัววิ่งไปถึงบรรทัดนั้น (เห็นแล้วใน days.rb หัวข้อ 3) · คน Rails จึงจริงจังกับการเขียนเทสมาก (Day 4)

🗣 ศัพท์ที่เพิ่งเรียนไป
opinionated (framework ที่มีความเห็นของตัวเอง)
framework ที่ตัดสินใจแทนเราไว้เยอะ ว่าควรตั้งชื่อยังไง วางไฟล์ไว้ไหน ใช้เครื่องมืออะไร · Rails เรียกตัวเองแบบนี้ · ถาม senior ได้ว่า "เรื่องนี้ Rails มีทางที่ตั้งใจไว้แล้วไหม หรือต้องเลือกเอง"
Omakase
ชื่อเล่นของแนวคิด "Rails เลือกเครื่องมือมาให้เป็นชุด" · ถาม AI ได้ว่า "what does a new Rails app include by default?" จะได้รายชื่อของในชุดนี้

8 · ของในกล่องที่ Rails ให้มา

สไลด์หน้า 13 ไล่ของ 12 อย่างที่ "เคยต้องเลือกเอง แต่ Rails เลือกมาให้แล้ว" · ไม่ต้องจำชื่อฝั่ง Rails ดูแค่ว่ามีครบ:

ของในสไลด์มันคืออะไรใน Go ที่เรียนมาใน Rails (เรียนเมื่อไหร่)
routerจับคู่ URL กับคนรับGin (บท 38)config/routes.rb (บท 101)
ORMตัวแปลงโค้ดเป็นคำสั่ง databaseGORM (บท 27)Active Record (Day 2)
migration toolใบสั่งช่างมาต่อเติมตารางgoose (บท 31)migration ในโฟลเดอร์ db/migrate/ (Day 2)
validatorตรวจว่าข้อมูลที่ส่งมาครบ ถูกแบบtag binding ของ Gin (บท 39)validations (Day 2)
job queueคิวงานที่ทำทีหลังได้ เช่นส่งอีเมลคอร์สไม่ได้สอนActive Job + Solid Queue (Day 3)
cache clientจำคำตอบที่ทำแล้ว จะได้ไม่ต้องทำซ้ำgo-redis (บท 32)Solid Cache (Day 3)
WebSocketสายที่เปิดค้างไว้ ให้ server ส่งของมาหาเองได้คอร์สไม่ได้สอนAction Cable
mailerส่งอีเมลคอร์สไม่ได้สอนAction Mailer
file storageเก็บไฟล์ที่ผู้ใช้อัปโหลดคอร์สไม่ได้สอนActive Storage (Day 3)
rich text editorช่องพิมพ์ที่ใส่ตัวหนา รูป ลิงก์ได้ไม่ใช่งานของ backendAction Text
frontendส่วนที่ทำให้หน้าเว็บกดแล้วตอบสนองไวแยกอีกโปรเจกต์: Next.js (บท 69)Hotwire / Turbo (Day 3)
e2e testเทสที่เปิดเว็บแล้วกดเหมือนคนจริงhttptest (บท 40) ทดสอบแค่ระดับ handlersystem test (Day 4)

ประโยคปิดของสไลด์คือ ทั้ง 12 อย่างทำงานด้วยกันได้ตั้งแต่วันแรก และอัปเกรดไปพร้อมกัน
ฝั่ง Go ไม่มีใครรับประกันว่า Gin รุ่นใหม่จะไปด้วยกันกับ GORM รุ่นใหม่ — คุณต้องลองเอง

9 · ประวัติสั้น ๆ

ข้อคิดของสไลด์หน้า 10 ที่ควรจำ: ระบบใหญ่แทบไม่มีระบบไหนใช้ภาษาเดียวทั้งหมด · คำถามที่ถูกคือ "ส่วนไหนของระบบเหมาะกับ Rails" ไม่ใช่ "Rails ดีหรือไม่ดี"

10 · แผนที่ 16 บทของ Day 1

Day 1 แบ่งเป็นสองช่วง · ช่วงแรกเรียนภาษา Ruby ให้พออ่านโค้ด Rails ออก · ช่วงหลังเปิดร้าน Rails จริง แล้วเดินดูทีละห้อง

บทเรื่องในภาพร้านอาหาร
90Rails คืออะไร (บทนี้)ดูแผนผังร้านทั้งร้าน
ช่วงที่ 1 · ภาษา Ruby
91ติดตั้ง Ruby · ตัวแปร · object · stringหัดพูดภาษาของครัว
92Array · Hash · Symbol · map / selectจัดของหลายชิ้นลงกล่อง
93Method และ Blockเขียนสูตรอาหารของตัวเอง
94อะไรนับว่า "จริง" · nilเช็คว่าของในตู้มีจริงไหม
95Class และ Moduleทำแม่พิมพ์ของเอง
96เมื่อโค้ดพัง · rescueของหกในครัว รับมือยังไง
975 ไวยากรณ์ที่ Rails ใช้บ่อยศัพท์ในใบสั่งของแฟรนไชส์
ช่วงที่ 2 · Rails
98rails new · โฟลเดอร์ในโปรเจกต์รับมอบร้านแฟรนไชส์ เดินดูทีละห้อง
99กฎตั้งชื่อ Post ↔ postsป้ายชื่อที่ทุกสาขาใช้เหมือนกัน
100เปิด server · request เดินทางยังไงเปิดร้าน แล้วตามใบสั่งใบแรก
101Routingพนักงานต้อนรับ
102Controller · paramsหัวหน้าเชฟ + รายละเอียดในใบสั่ง
103View · ERB · layoutจัดจาน + จานรองแบบเดียวกันทุกจาน
104debugger · consoleหยุดเวลาในครัว · ครัวทดลองหลังร้าน
105Lab: สร้างแอปเอง + หน้า /aboutเปิดร้านเองตั้งแต่ต้นจนเสิร์ฟจานแรก

ส่วนของ database จริง (เก็บบทความลงตาราง · เพิ่ม/แก้/ลบ) รออยู่ Day 2 · ระหว่าง Day 1 ถ้าเจอคำว่า Active Record ให้รู้แค่ว่า "คือห้องเก็บวัตถุดิบ เดี๋ยวได้เรียน"

ภารกิจ — เล่าเส้นทางของใบสั่งด้วยคำของตัวเอง

🎯 ภารกิจ (ประมาณ 10 นาที)

ส่วน A — ทุกคนทำได้ ไม่ต้องติดตั้งอะไร

  1. เปิดกระดาษหรือโน้ตเปล่า
  2. เขียนเส้นทางของใบสั่ง "ขอหน้า /posts" (หน้ารวมบทความทั้งหมด) เป็นขั้น ๆ 5 ขั้น แบบหัวข้อ 5 · แต่ละขั้นเขียนทั้งชื่อในร้านอาหารและชื่อใน Rails
  3. ตอบเพิ่มหนึ่งข้อ: ขั้นที่ไปหยิบของจากตู้ หยิบแฟ้มกี่แผ่น — ต่างจากใบสั่ง /posts/42 ยังไง

ส่วน B — ถ้าเครื่องคุณมี Ruby ของคอร์สแล้ว (ยังไม่มีก็ข้ามได้ บท 91 พาติดตั้ง)

  1. เปิด terminal (โปรแกรมที่ให้พิมพ์คำสั่ง — บท 91 อธิบาย) พิมพ์ ruby -v · ควรเห็นเลขขึ้นต้นด้วย ruby 4.0.
  2. สร้างไฟล์ first.rb ด้วยโปรแกรมแก้ข้อความที่ใช้อยู่ (เช่น VS Code) พิมพ์ให้เหมือนกล่องโค้ดในหัวข้อ 3 ครบทั้งสามบรรทัด (รวมบรรทัด # first.rb) เซฟ แล้วรัน ruby first.rb ในโฟลเดอร์เดียวกับไฟล์ · ควรเห็น CBA ตามด้วย hello 5 บรรทัด
  3. เพิ่มบรรทัดที่สี่ puts 3.days.ago ต่อท้าย แล้วรันอีกที · ควรเห็น CBA กับ hello ก่อน แล้วค่อยพังด้วย NoMethodError ที่บรรทัด 4 (ถ้าคุณไม่ได้ใส่บรรทัด comment เลขจะเป็น 3 แทน — ไม่ผิด)

ถ้าเจอ error นี้ แปลว่า…

ส่วน A — ใช้คำของคุณเองได้ ขอแค่ลำดับถูก:

  1. ลูกค้าส่งใบสั่ง = browser ส่ง request GET /posts
  2. พนักงานต้อนรับ = routes ดูแล้วชี้ว่า "แผนกบทความ เมนูดูทั้งหมด" (ใน Rails เมนูนี้ชื่อ index — ได้เรียนบท 101)
  3. หัวหน้าเชฟแผนกบทความ = PostsController รับงาน แล้วขอบทความทั้งหมดจากห้องเก็บของ
  4. ห้องเก็บวัตถุดิบ = model Post หยิบแฟ้มทุกแผ่นในลิ้นชัก posts
  5. จัดจานแล้วเสิร์ฟ = view ใน app/views/posts/ ทำหน้า HTML ที่มีรายการบทความ แล้วส่งกลับเป็น response

ข้อ 3: /posts หยิบทุกแผ่น · /posts/42 หยิบแผ่นเดียว · สังเกตว่าใบสั่ง /posts ไม่มีเลขบทความ จึงไม่มี "เลข 42" ในรายละเอียดใบสั่ง (params)

ส่วน B — ผลรันจริงของไฟล์สี่บรรทัด (รันจาก terminal):

$ ruby first.rb
CBA
hello
hello
hello
hello
hello
first.rb:4:in '<main>': undefined method 'days' for an instance of Integer (NoMethodError)

puts 3.days.ago
      ^^^^^

สองบรรทัดคำสั่งแรกทำงานไปแล้วก่อนจะพังที่บรรทัด 4 — Ruby อ่านไปรันไปทีละบรรทัด · ถ้าเป็น Go ทั้งไฟล์จะไม่ได้รันเลยสักบรรทัด เพราะ go build ฟ้องก่อน · นี่คือ "พิมพ์ผิด รู้ตอนวิ่งไปถึง" ในหัวข้อ 7 เวอร์ชันที่เห็นกับตา

ลองตอบดู

ข้อไหนอธิบายความสัมพันธ์ของ Ruby กับ Rails ได้ถูกที่สุด?

ตัวเลือกที่ 2 ถูก · Ruby เป็นภาษาแบบที่ Go เป็นภาษา · Rails เป็น framework แบบที่ Gin เป็น framework (แต่ให้มาครบกว่ามาก)

ตัวเลือกที่ 1 ผิด เพราะ Rails ไม่ได้มีไวยากรณ์ใหม่ — มันคือโค้ด Ruby ธรรมดาชุดหนึ่ง · ตัวเลือกที่ 3 ผิด เพราะ Ruby เขียนโปรแกรมที่ไม่ใช่เว็บได้โดยไม่มี Rails เลย (เช่น first.rb ในหัวข้อ 3) · ตัวเลือกที่ 4 ผิด เพราะคนรันไฟล์ .rb คือคำสั่ง ruby เสมอ ทั้งในและนอกแอป Rails

จุดที่ต้องจำ: Ruby = ภาษา (เหมือน Go) · Rails = framework ที่เขียนด้วย Ruby (เหมือน Gin แต่ครบชุด)

ไฟล์ที่มีบรรทัดเดียว puts 3.days.ago รันด้วย ruby เปล่า ๆ แล้วได้ NoMethodError เพราะอะไร?

ตัวเลือกที่ 4 ถูก · ผลรันจริงคือ undefined method 'days' for an instance of Integer · พอสั่ง ruby -ractive_support/all days.rb (โหลด Active Support มาก่อน) ไฟล์เดิมก็ได้วันที่ออกมา

ตัวเลือกที่ 1 ผิด เพราะ 5.times ในหัวข้อ 3 ใช้จุดกับตัวเลขได้ปกติ · ตัวเลือกที่ 2 ผิด เพราะ 3.days.ago เขียนถูกแล้ว ใช้ได้ทันทีเมื่อมี Active Support · ตัวเลือกที่ 3 ผิด เพราะลองรันจริงแล้วพังทั้ง Ruby 4.0.9 ที่คอร์สใช้ และ Ruby 2.6 ที่ติดมากับ macOS — ปัญหาไม่ได้อยู่ที่รุ่น แต่อยู่ที่ยังไม่ได้โหลด Active Support

จุดที่ต้องจำ: โค้ดที่อ่านเป็นภาษาอังกฤษสวย ๆ ไม่ได้แปลว่าเป็นของ Ruby เสมอ · สงสัยเมื่อไหร่ให้รันด้วย ruby เปล่า ๆ

ใบสั่ง "ขอดูบทความที่ 42" เดินผ่านฝ่ายต่าง ๆ ในร้าน Rails ตามลำดับไหน?

ตัวเลือกที่ 1 ถูก · พนักงานต้อนรับ (routes) ต้องดูใบสั่งก่อนว่าแผนกไหนรับ → หัวหน้าเชฟ (controller) สั่งงาน → ห้องเก็บของ (model) หยิบแฟ้มเลข 42 → จัดจาน (view) → เสิร์ฟ

ตัวเลือกที่ 2 ผิด เพราะ controller ไม่รู้ว่าใบสั่งเป็นของตัวเองจนกว่า routes จะส่งมาให้ · ตัวเลือกที่ 3 ผิด เพราะ model ไม่รู้ว่าต้องหยิบแฟ้มไหน ถ้ายังไม่มีใครอ่านใบสั่ง · ตัวเลือกที่ 4 ผิด เพราะจัดจานก่อนมีของไม่ได้ — view ต้องรอข้อมูลบทความก่อน

จุดที่ต้องจำ: เส้นทางมาตรฐานคือ routes → controller → model → view · หน้าเว็บผิดตรงไหน ให้ไล่หาตามเส้นนี้

ในแอปบล็อก คำไหนหมายถึง "แม่พิมพ์ของบทความ" — ตัวบอกว่าบทความหนึ่งชิ้นมีช่องอะไรบ้าง?

ตัวเลือกที่ 3 ถูก · Post ตัวใหญ่เอกพจน์คือ model — เทียบได้กับ type Post struct ใน Go

ตัวเลือกที่ 1 ผิด เพราะ posts คือลิ้นชัก (ตาราง) ที่เก็บแฟ้มหลายแผ่น ไม่ใช่แม่พิมพ์ · ตัวเลือกที่ 2 ผิด เพราะ /posts เป็นแค่ที่อยู่ที่ลูกค้าพิมพ์ · ตัวเลือกที่ 4 ผิด เพราะ controller คือหัวหน้าเชฟที่สั่งงาน ไม่ได้เป็นคนกำหนดว่าบทความมีช่องอะไร

จุดที่ต้องจำ: ตัวใหญ่เอกพจน์ (Post) = แม่พิมพ์ · ตัวเล็กพหูพจน์ (posts) = ที่เก็บหลายชิ้น หรือ URL

💬 ติดตรงไหน ถามกลับได้เลย — บทนี้เป็นแผนที่ของทั้ง 16 บท ถ้าแผนที่ยังไม่ชัด บทต่อ ๆ ไปจะงงตาม · ลองถามแบบ "ในร้านอาหาร X คือใคร" มาได้ทุกคำ

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

หน้าสไลด์เขียนของจริงต้องสนใจไหม
3Post.where(published: true).order(:title) เป็นตัวอย่างโค้ด Railsพิมพ์ลงไฟล์แล้วรันด้วย ruby เปล่า ๆ จะพัง uninitialized constant Post (NameError) · ใช้ได้เฉพาะในแอป Rails ที่มี model Post แล้ว (Day 2)ไม่ต้องพิมพ์ตาม แค่ดูหน้าตา
4วาง 3.days.ago · 1.megabyte ปนกับตัวอย่าง Ruby ล้วนสองบรรทัดนี้เป็นของ Active Support (Rails) · Ruby เปล่าพัง NoMethodErrorสนใจ — คือหัวข้อ 3 ของบทนี้
4if user.admin? … grant_access … endเป็นท่อนที่ตัดมาให้ดู ไม่มีการประกาศ user · รันเฉย ๆ พัง undefined local variable or method 'user'ไม่ต้องพิมพ์ตาม
7Ruby อายุ 30 ปี · Rails อายุ 21 ปีนับถึงปี 2026 ได้ 31 กับ 22 ปีไม่ต้องสนใจ
9ตัวเลขของ Shopify ช่วง Black Friday · GitHub มีโค้ด Rails เกือบ 2 ล้านบรรทัดยังยืนยันกับแหล่งต้นทางไม่ได้ ถือเป็น "สไลด์อ้าง"ไม่ต้องจำ
1Ruby 4.0.6 · Rails 8.1.3บทเรียนตรวจด้วย Ruby 4.0.9 · Rails 8.1.4 (สายเดียวกัน)ไม่ต้องสนใจ

อ้างอิง