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

rails new — สร้างโปรเจกต์แรก แล้วเดินดูทีละห้อง

~20 นาที (+ ภารกิจ ~10 นาที) · ควรผ่าน บทเรียน 97 (อ่านโค้ด Rails ไม่งง) มาก่อน · ถ้าลืมภาพ "แอป Rails = ร้านอาหาร" ย้อนดู บทเรียน 90

เจ็ดบทที่ผ่านมาคุณเขียน Ruby ล้วน ๆ · บทนี้คือครั้งแรกที่จะได้แอป Rails ตัวจริง
คุณจะพิมพ์คำสั่งเดียว แล้วได้โปรเจกต์ชื่อ wongnok_blog (แอปบล็อกของสไลด์) ที่เปิดดูในเบราว์เซอร์ได้ทันที
อ่านจบแล้วคุณจะ: รู้ว่า rails new ทำอะไรให้บ้าง · เดินในโปรเจกต์แล้วรู้ว่าโฟลเดอร์ไหนคือห้องอะไร · อ่าน Gemfile ออก · และเปิด server จนเห็นหน้าแรกที่ localhost:3000

🔁 ทวนก่อนเริ่ม — คำจากบทก่อนที่บทนี้ใช้

🧰 เครื่องที่ใช้ตรวจ: Ruby 4.0.9 · Rails 8.1.4 · PostgreSQL 17 — สไลด์ใช้ Ruby 4.0.6 · Rails 8.1.3 ต่างกันแค่เลขท้าย · จุดที่บทนี้เห็น 8.1.4 ของคุณอาจเห็น 8.1.3
ทุกผลรันในบทนี้ก็อปมาจากเครื่องจริง · path ยาว ๆ ของเครื่องตรวจย่อเป็น … · กล่องไหนตัดบรรทัดออกจะเขียนกำกับไว้

Go เริ่มจากห้องเปล่า — Rails ได้ร้านมาทั้งร้าน

ตอนเริ่มโปรเจกต์ Go คุณพิมพ์ go mod init แล้วได้ไฟล์มาไฟล์เดียว ที่เหลือต้องสร้างเองทั้งหมด: main.go · router ของ Gin · handler · โค้ดต่อ database

# ผลรันจริง — โฟลเดอร์เปล่า แล้วสั่ง go mod init
$ go mod init blog
go: creating new go.mod: module blog
$ ls -A
go.mod

rails new ทำกลับกัน — คำสั่งเดียวได้ไฟล์มา 109 ไฟล์ และเปิดเป็นเว็บได้ทันทีโดยยังไม่ต้องเขียนโค้ดสักบรรทัด
ข้อดีคือเริ่มได้เร็ว · ข้อเสียคือวันแรกมีไฟล์ที่ไม่รู้จักเต็มไปหมด — บทนี้จะพาดูเฉพาะห้องที่ต้องรู้

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

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

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

🗣 ศัพท์ที่เพิ่งเรียนไป
generator (rails new)
คำสั่งที่ "สร้างไฟล์ให้" ตามแบบที่กำหนดไว้ · rails new คือ generator ตัวใหญ่สุด สร้างทั้งโปรเจกต์ · ถาม senior ได้ว่า "ทีมเราใช้ rails new กับ flag อะไรบ้างตอนเริ่มโปรเจกต์นี้"
boilerplate / project skeleton
โครงโปรเจกต์สำเร็จรูปที่ยังไม่มีเนื้อหาของเรา · พิมพ์บอก AI ได้ว่า "explain which files in a fresh rails new app I can ignore on day one"

อ่านคำสั่ง rails new ทีละท่อน

คำสั่งที่สไลด์ให้พิมพ์คือบรรทัดนี้ — ยาว แต่แตกได้เป็นห้าท่อน

$ rails new wongnok_blog --database=postgresql --css=tailwind
ท่อนแปลว่าในร้านอาหาร
railsโปรแกรม rails ที่ติดตั้งไว้ในเครื่อง (ได้มาตอนติดตั้ง gem rails)บริษัทแม่แฟรนไชส์
new"สร้างโปรเจกต์ใหม่""ขอเปิดสาขาใหม่"
wongnok_blogชื่อโปรเจกต์ = ชื่อโฟลเดอร์ที่จะถูกสร้างชื่อสาขา
--database=postgresqlให้ตั้งค่าไว้สำหรับ PostgreSQL · ถ้าไม่ใส่จะได้ SQLiteขอตู้เก็บแฟ้มยี่ห้อ PostgreSQL
--css=tailwindติดตั้ง Tailwind (เครื่องมือแต่งหน้าเว็บที่เคยเห็นฝั่ง FRONT) ไว้ให้ขอชุดตกแต่งร้านแบบ Tailwind

ของที่ขึ้นต้นด้วย -- เรียกว่า flag = ตัวเลือกเสริมของคำสั่ง เหมือน -v ใน go test -v ที่เคยใช้ · ไม่ใส่ก็ได้ แต่จะได้ค่าเริ่มต้นแทน
ทำไมเลือก PostgreSQL? เพราะเป็น database ตัวเดียวกับที่ใช้ฝั่ง Go และ Day 2 จะใช้จริง · ส่วน SQLite (ค่าเริ่มต้น) คือ database ที่เป็นไฟล์เดียวอยู่ในโปรเจกต์ ไม่ต้องเปิดโปรแกรม database แยก

สไลด์หน้า 41 ยก flag อื่นมาอีกห้าตัว — วันนี้ยังไม่ใช้ ไม่ต้องจำ รู้แค่ว่ามี:

flagภาษาคน
--apiไม่เอาหน้าเว็บ ส่งแต่ข้อมูล JSON — สำหรับแอปที่มี frontend แยก (เช่น Next.js) · สไลด์บอกว่าตัวนี้เปลี่ยนใจทีหลังยากสุด
--skip-solidไม่เอาชุด Solid — ของเสริมที่ Rails 8 ใส่มาให้ ใช้เก็บงานค้างและข้อมูลชั่วคราวไว้ใน database (Day 3 จะเจอ)
--javascript=esbuildให้ไฟล์ JavaScript ต้องผ่านขั้น build ก่อน (คล้ายตอนทำ Next.js ฝั่ง FRONT) · ค่าเริ่มต้นของ Rails ไม่ต้อง build
--devcontainerสร้างไฟล์ตั้งค่าให้ VS Code เปิดโปรเจกต์ข้างใน Docker
--skip-testไม่สร้างโฟลเดอร์ test/

กดสร้างจริง — แล้วดูว่ามันทำอะไรไปบ้าง

บนเครื่องตรวจใช้เวลาราว 13 วินาที (gem ทุกตัวลงไว้แล้ว) · เครื่องคุณครั้งแรกอาจนานเป็นนาที เพราะต้องโหลด gem จากเน็ต · ผลจริงยาว 202 บรรทัด นี่คือช่วงที่ควรดู:

# ผลรันจริง (ย่อ — ตัดบรรทัดกลางออก)
$ rails new wongnok_blog --database=postgresql --css=tailwind
      create
      create  README.md
      create  Rakefile
      create  .ruby-version
      create  config.ru
      create  .gitignore
      create  .gitattributes
      create  Gemfile
         run  git init -b main from "."
Initialized empty Git repository in …/wongnok_blog/.git/
      create  app
      create  app/assets/stylesheets/application.css
      create  app/controllers/application_controller.rb
…(ตัดออก)…
         run  bundle install --quiet
…(ตัดออก)…
  Ensure foreman is installed
         run    gem install foreman from "."
Successfully installed foreman-0.90.0
…(ตัดออก)…

อ่านทีละแบบ:

$ cd wongnok_blog
$ git log

เห็นบรรทัด git init ในผลข้างบนแล้ว และสไลด์หน้า 40 บอกว่าได้ "git repo ที่ commit แรกให้แล้ว" — git log จะแสดงอะไร?

# ผลรันจริง
$ git log
fatal: your current branch 'main' does not have any commits yet

ยังไม่มี commit เลย · rails new แค่สร้างที่เก็บ git เปล่า ๆ ให้ ไม่ได้ commit ให้ · fatal ตรงนี้ไม่ใช่เรื่องร้าย มันแค่บอกว่ายังว่าง
ทำ commit แรกเองทันที จะได้มี "จุดเริ่มที่สะอาด" ไว้เทียบทีหลังว่าคุณแก้อะไรไปบ้าง:

# ผลรันจริง (เลข commit ของคุณจะต่าง)
$ git add -A
$ git commit -m "rails new"
…(ผลของ git commit ยาวร้อยกว่าบรรทัด ตัดออก)…
$ git log --oneline
2b53597 rails new
$ git show --stat --format= | tail -1
 109 files changed, 3219 insertions(+)

git show --stat --format= | tail -1 = "สรุป commit ล่าสุดให้ดู แต่เอาแค่บรรทัดท้าย" — บรรทัดที่บอกว่า commit นี้มีกี่ไฟล์

เดินดูร้าน — ทีละห้อง

เข้าไปในโฟลเดอร์แล้วดูว่ามีอะไรบ้าง · ls -1F = แสดงบรรทัดละชื่อ และเติม / ท้ายชื่อที่เป็นโฟลเดอร์

# ผลรันจริง
$ ls -1F
Dockerfile
Gemfile
Gemfile.lock
Procfile.dev
README.md
Rakefile
app/
bin/
config/
config.ru
db/
lib/
log/
public/
script/
storage/
test/
tmp/
vendor/

19 ชื่อ ดูเยอะ แต่ Day 1 ใช้จริงแค่ไม่กี่ห้อง · ตารางนี้คือแผนผังร้าน:

โฟลเดอร์ / ไฟล์มีไว้ทำอะไรในร้านอาหารDay 1 แตะบ่อยไหม
app/โค้ดของเราเกือบทั้งหมดครัว✅ บ่อยที่สุด
config/การตั้งค่า · มี routes.rb ที่บอกว่า URL ไหนไปหาใครห้องผู้จัดการ · routes.rb ข้างในคือรายการในมือพนักงานต้อนรับ (ใบสั่งแบบไหนส่งแผนกไหน)✅ เฉพาะ routes.rb
bin/สคริปต์คำสั่ง เช่น bin/railsแผงสวิตช์ของร้าน✅ ใช้ทุกวัน แต่ไม่ต้องแก้
Gemfile · Gemfile.lockรายการ gem ที่ใช้ · รุ่นจริงที่ได้มารายการซื้อของ · ใบเสร็จ👀 เปิดอ่านบ้าง
log/บันทึกทุก request ที่เข้ามาสมุดจดใบสั่งของร้าน👀 เปิดดูตอนพัง
db/เรื่องของ databaseตู้เก็บแฟ้ม❌ รอ Day 2
test/โค้ดทดสอบห้องชิมอาหารก่อนเสิร์ฟ❌ รอ Day 4
public/ไฟล์ที่ส่งให้ลูกค้าตรง ๆ ไม่ผ่านครัว (เช่นหน้า 404)ใบปลิวหน้าร้านที่ลูกค้าหยิบเองได้❌
ที่เหลือ (lib/ tmp/ storage/ vendor/ script/ Dockerfile config.ru Rakefile …)ของสำหรับเครื่องมือ · deploy · ไฟล์ชั่วคราวห้องเก็บของหลังร้าน❌ ไม่ต้องสนใจ

ห้องครัว — app/

# ผลรันจริง
$ ls -1F app
assets/
controllers/
helpers/
javascript/
jobs/
mailers/
models/
views/

มี 8 ห้องย่อย แต่ Day 1 ใช้แค่สองห้อง และ Day 2 จะเพิ่มห้องที่สาม — ตรงกับสามตัวละครในร้านอาหาร:

แล้วในห้องเชฟกับห้องวัตถุดิบตอนนี้มีอะไร?

# ผลรันจริง
$ ls -1AF app/controllers app/models
app/controllers:
application_controller.rb
concerns/

app/models:
application_record.rb
concerns/

(-A = โชว์ไฟล์ที่ซ่อนอยู่ด้วย จะได้แน่ใจว่าไม่มีอะไรแอบอยู่ · concerns/ = ลิ้นชักเปล่าไว้เก็บของที่หลายแผนกใช้ร่วมกัน คล้าย module ในบท 95 · ยังไม่ใช้)
ยังไม่มี Post (แม่พิมพ์ของบทความ) และยังไม่มี PostsController (หัวหน้าเชฟแผนกบทความ) — ร้านเพิ่งเปิดใหม่ ยังไม่มีแผนกบทความ
ไฟล์ที่ขึ้นต้นด้วย application_ คือ "แม่แบบกลาง" ที่ทุกแผนกจะสืบทอดต่อ (สืบทอด = class ลูกได้ของทั้งหมดของ class แม่ บท 95) · บท 99 จะสาธิตการสร้าง Post ด้วยคำสั่ง bin/rails generate ให้ดู แล้วคุณจะเห็นว่า Post โผล่มาจากไหน · ส่วนแผนกบทความ (PostsController) คุณจะเขียนเองในบท 100

ห้องผู้จัดการ — config/

config/ มีของ 20 อย่าง (ไฟล์ + โฟลเดอร์ย่อย) แต่ Day 1 เปิดแค่ config/routes.rb — รายการในมือพนักงานต้อนรับ ที่บอกว่า URL ไหนไปหาแผนกไหน · ดูของจริงที่ได้มา:

# ผลรันจริง — config/routes.rb (ไฟล์เต็ม)
Rails.application.routes.draw do
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
  # Can be used by load balancers and uptime monitors to verify that the app is live.
  get "up" => "rails/health#show", as: :rails_health_check

  # Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb)
  # get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
  # get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker

  # Defines the root path route ("/")
  # root "posts#index"
end

ไวยากรณ์ของ routes ทั้งหมดรอไว้บท 101 · ตอนนี้แค่รู้ว่าอยากรู้ว่าแอปมี URL อะไรบ้าง ให้เปิดไฟล์นี้ไฟล์เดียว

ตู้เก็บแฟ้ม — db/

# ผลรันจริง
$ ls -1F db
cable_schema.rb
cache_schema.rb
queue_schema.rb
seeds.rb

สามไฟล์แรกเป็นของชุด Solid (Day 3) · seeds.rb คือที่ใส่ข้อมูลตัวอย่าง (Day 2) · สไลด์หน้า 42 วาด db/migrate/ กับ db/schema.rb ไว้ด้วย แต่แอปใหม่ยังไม่มีสองตัวนี้ — มันจะเกิดตอนคุณสร้างตาราง posts ครั้งแรกใน Day 2 · เปิดหาไม่เจอไม่ใช่คุณทำผิด

เทียบกับโปรเจกต์ Go ที่เคยทำ

วิธีจำผังที่เร็วที่สุดคือถามว่า "ของที่เคยเขียนเองใน Go ย้ายไปอยู่ไหน":

หน้าที่Go (ที่คุณเขียนเอง)Rails (ได้มาแล้ว)
จุดเริ่มโปรแกรม ต่อสายทุกอย่างmain.goไม่มีให้เขียน — Rails ต่อสายเองจากชื่อไฟล์ (บท 99–100)
รายการ URLr.GET(…) กระจายใน main.goconfig/routes.rb ไฟล์เดียว
handlerฟังก์ชัน func(c *gin.Context)app/controllers/
หน้าตาข้อมูล + คุยกับ DBstruct + GORMapp/models/
หน้าตาที่ส่งให้ลูกค้าฝั่ง Go ที่เรียนส่ง JSON ด้วย c.JSON(…) (ยังไม่เคยทำหน้า HTML)app/views/ (ไฟล์ HTML)
รายการ dependencygo.modGemfile
รุ่นที่ได้มาจริง + รหัสเช็คว่าไฟล์ไม่ถูกแก้go.sumGemfile.lock
🚶 เทียบให้เห็นภาพ — วันแรกที่เข้าทำงานร้านแฟรนไชส์

พนักงานใหม่ไม่ต้องรู้ทุกห้อง วันแรกรู้แค่ ครัวอยู่ไหน (app/) · รายการของพนักงานต้อนรับ (ผังส่งใบสั่ง) เก็บอยู่ห้องผู้จัดการ (config/routes.rb) · สวิตช์เปิดร้านอยู่ตรงไหน (bin/rails) · และสมุดจดใบสั่งวางไว้ไหน (log/) — ห้องเก็บของหลังร้านค่อยเรียนทีหลัง
และเพราะทุกสาขาผังเหมือนกัน ไปเปิดโปรเจกต์ Rails ของบริษัทไหนก็เจอ controller ที่ app/controllers/ เสมอ

ที่ไม่เหมือนกัน: ในร้านจริง ถ้าเอาหม้อไปวางผิดห้อง คนยังเดินหาจนเจอได้ · แต่ใน Rails ถ้าวางไฟล์ผิดโฟลเดอร์หรือตั้งชื่อไม่ตรงกฎ Rails หาไม่เจอเลย และ error มักไม่บอกตรง ๆ ว่าวางผิด — กฎตั้งชื่อนี้คือเรื่องทั้งบทของบท 99

🗣 ศัพท์ที่เพิ่งเรียนไป
directory structure
ผังโฟลเดอร์มาตรฐานของ Rails ที่ทุกโปรเจกต์เหมือนกัน · ถาม senior ได้ว่า "โปรเจกต์นี้มีโฟลเดอร์ไหนที่ไม่ใช่ของมาตรฐาน Rails บ้าง" — ที่ไม่ใช่มาตรฐานคือที่ที่คนใหม่หลงทาง
config/routes.rb
ไฟล์รวม URL ทั้งแอป · พิมพ์บอก AI ได้ว่า "read config/routes.rb and list every URL this app serves" จะได้ภาพรวมแอปเร็วกว่าไล่อ่าน controller ทีละไฟล์
app/ (models · views · controllers)
สามห้องหลักของ MVC (Model · View · Controller — ภาพร้านอาหารในบท 90) · ถาม AI ได้ว่า "in a Rails app, which folder should this code go in: models, controllers or views?"

Gemfile — รายการซื้อของของร้าน

gem คือของสำเร็จรูปที่คนอื่นเขียนไว้ เช่นตัวคุยกับ PostgreSQL หรือ web server · Gemfile คือรายการว่าแอปนี้ต้องใช้ gem อะไร รุ่นไหน · Bundler คือโปรแกรมที่อ่านรายการแล้วไปติดตั้งให้ ด้วยคำสั่ง bundle install (rails new สั่งให้แล้วรอบหนึ่ง)

gem ตรงกับอะไรใน Go — module ไม่ใช่ package

ใน Go package คือโฟลเดอร์โค้ดหนึ่งโฟลเดอร์ที่ import ได้ (เช่น net/http) · module คือชุดของ package ที่ปล่อยออกมาพร้อมกัน มีชื่อและเลขรุ่นเดียวกัน = หนึ่งบรรทัดใน go.mod
gem มีชื่อ มีเลขรุ่น และติดตั้งทั้งก้อน → ตรงกับ module · ข้างใน gem หนึ่งตัวมีไฟล์ย่อยหลายไฟล์ เหมือน module หนึ่งตัวมีหลาย package

นี่คือ Gemfile ที่ได้มาจริง ตัดเหลือบรรทัดที่ควรอ่านเป็น (ไฟล์เต็มยาว 68 บรรทัด ส่วนใหญ่เป็น comment):

# ผลรันจริง — Gemfile (ย่อ)
source "https://rubygems.org"

gem "rails", "~> 8.1.4"
gem "pg", "~> 1.1"
gem "puma", ">= 5.0"
gem "tailwindcss-rails"
…(ตัดออก)…
group :development, :test do
…(ตัดออก)…
  gem "brakeman", require: false
…(ตัดออก)…
end

Gemfile เป็นไฟล์ Ruby จริง ๆ · ทุกบรรทัดคือการเรียก method แบบละวงเล็บ (บท 97) — อ่านทีละบรรทัด:

Gemfile.lock — ใบเสร็จที่บอกรุ่นจริง

Gemfile บอก "อยากได้อะไร" · Gemfile.lock บอก "ได้อะไรมาจริง" — ชื่อ เลขรุ่นจริง และลายนิ้วมือของทุก gem · ไฟล์นี้ Bundler เขียนให้เอง ไม่ต้องแก้ด้วยมือ · ลองสั่ง bundle install อีกรอบดูสรุป:

# ผลรันจริง (ในโฟลเดอร์ wongnok_blog)
$ bundle install
Bundle complete! 24 Gemfile dependencies, 124 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
1 installed gem you directly depend on is looking for funding.
  Run `bundle fund` for details

(สองบรรทัดท้ายเรื่อง funding = ชวนบริจาคให้คนทำ gem · ข้ามได้)
เขียนในรายการ 24 ตัว ได้มาจริง 124 ตัว — เพราะ gem หนึ่งตัวลาก gem อื่นที่มันต้องใช้มาด้วย · ตัวอย่างชัดที่สุดคือ rails เอง ใน Gemfile.lock เขียนไว้แบบนี้:

# ผลรันจริง — ท่อนหนึ่งของ Gemfile.lock
    rails (8.1.4)
      actioncable (= 8.1.4)
      actionmailbox (= 8.1.4)
      actionmailer (= 8.1.4)
      actionpack (= 8.1.4)
      actiontext (= 8.1.4)
      actionview (= 8.1.4)
      activejob (= 8.1.4)
      activemodel (= 8.1.4)
      activerecord (= 8.1.4)
      activestorage (= 8.1.4)
      activesupport (= 8.1.4)
      bundler (>= 1.15.0)
      railties (= 8.1.4)
…(ตัดออก)…
CHECKSUMS
…(ตัดออก)…
  rails (8.1.4) sha256=5f3ecdf9a2a3b79b2e31b8fee3b64a00f7284d76e5631f87d0e58e665911a33b

Gemfile.lock ต้อง commit เข้า git เสมอ (คู่มือ Bundler บอกตรง ๆ ว่าสำหรับแอปให้ commit ทุกครั้ง) · ไม่งั้นเพื่อนที่ clone ไปจะได้ gem คนละรุ่นกับคุณ แล้ว "เครื่องฉันรันได้ เครื่องเธอพัง"

🛒 เทียบให้เห็นภาพ — รายการซื้อของ กับ ใบเสร็จ

Gemfile คือรายการซื้อของที่คุณเขียนเอง: "น้ำปลา 1 ขวด ยี่ห้อเดิม ล็อตไหนก็ได้" · "ข้าวสาร 5 กิโล ยี่ห้ออะไรก็ได้"
Bundler คือคนที่ถือรายการไปซื้อของ · Gemfile.lock คือใบเสร็จที่ได้กลับมา — ระบุยี่ห้อ รุ่น และบาร์โค้ดของทุกชิ้นจริง ๆ รวมของแถมที่ติดมาด้วยโดยคุณไม่ได้สั่ง (24 อย่างในรายการ ได้ของกลับมา 124 ชิ้น)
อยากให้สาขาอื่นได้ของชุดเดียวกันเป๊ะ ต้องส่งใบเสร็จให้ ไม่ใช่ส่งแค่รายการ

ที่ไม่เหมือนกัน: ใบเสร็จจริงเป็นแค่หลักฐานหลังจ่ายเงิน ไม่มีใครเอาไปยื่นให้ร้านจัดของตาม · แต่ bundle install อ่าน Gemfile.lock ก่อนเสมอ แล้วติดตั้งตามรุ่นในนั้นเป๊ะ ๆ — มันจึงเป็นทั้งใบเสร็จและใบสั่งซื้อซ้ำในไฟล์เดียว

🗣 ศัพท์ที่เพิ่งเรียนไป
gem · Gemfile
gem = แพ็กเกจของ Ruby หนึ่งตัว (เทียบ module ของ Go) · Gemfile = รายการ gem ที่แอปต้องการ · ถาม senior ได้ว่า "gem ตัวนี้ทำอะไร ทำไมโปรเจกต์ถึงต้องใช้" ทุกครั้งที่เห็นชื่อแปลกใน Gemfile
Bundler · bundle install
ตัวจัดการ gem ของโปรเจกต์ (เทียบ go mod download ของ Go) · พิมพ์บอก AI ได้ว่า "I just cloned a Rails app, what do I run before bin/rails server?" คำตอบแรกเกือบทุกครั้งคือ bundle install
Gemfile.lock (lockfile)
รุ่นจริงที่เลือกแล้ว + checksum ของทุก gem · commit เสมอ ห้ามแก้ด้วยมือ · ถาม AI ได้ว่า "diff Gemfile.lock between these two commits and list the version changes" เวลาอยากรู้ว่าใครแอบอัปเกรดอะไร
group
ป้ายบอกว่า gem ใช้ใน environment ไหน (development · test · production) · ถาม senior ได้ว่า "gem นี้ควรอยู่ group ไหน ใช้ตอน production ด้วยหรือเปล่า"

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

~> อ่านว่าอะไร

~> แปลว่า "ขยับรุ่นได้ แต่ไม่ไกล" · วิธีอ่าน: ตัวเลขตัวท้ายสุดที่เขียนไว้ ขยับขึ้นได้ ส่วนตัวเลขข้างหน้ามันล็อก

ทำไมต้องล็อก? เพราะเลขหลักหน้าที่เปลี่ยนมักแปลว่า "มีของเปลี่ยนจนโค้ดเก่าอาจพัง" — ~> ยอมรับแค่การแก้บั๊กเล็ก ๆ

🧴 เทียบให้เห็นภาพ — "สูตรเดิม ล็อตใหม่ได้"

~> 1.2 เหมือนเขียนในรายการซื้อของว่า "น้ำปลาสูตร 1 ล็อต 2 ขึ้นไป ล็อตใหม่แค่ไหนก็ได้ แต่ห้ามเอาสูตร 2 มา" · คนซื้อ (Bundler) จะหยิบล็อตใหม่สุดที่ยังเป็นสูตร 1 แล้วจดลงใบเสร็จ (Gemfile.lock)

ที่ไม่เหมือนกัน: คนซื้อของจริงดูฉลากแล้วใช้วิจารณญาณได้ · Bundler ไม่รู้ว่าข้างใน gem เปลี่ยนไปแค่ไหน มันแค่เทียบตัวเลขทีละช่องตามกฎเป๊ะ ๆ — ถ้าคนทำ gem ตั้งเลขรุ่นไม่ตรงกับที่เปลี่ยนจริง ของที่ทำโค้ดพังก็หลุดเข้ามาได้

ไม่ต้องเชื่อคำอธิบาย ให้ RubyGems (ตัวที่ Bundler ใช้ตัดสินจริง) ตอบเอง:

# tilde.rb — ถาม RubyGems ว่า "~> 1.2" ยอมรับรุ่นไหนบ้าง
want = Gem::Requirement.new("~> 1.2")

["1.1.0", "1.2.0", "1.14.0", "1.99.9", "2.0.0"].each do |v|
  puts "#{v} => #{want.satisfied_by?(Gem::Version.new(v))}"
end

อ่านโค้ดก่อน: Gem::Requirement.new("~> 1.2") = สร้างเงื่อนไขรุ่นแบบเดียวกับที่เขียนใน Gemfile (Gem::Requirement = class ชื่อ Requirement ที่อยู่ข้างใน module Gem — :: อ่านว่า "ข้างใน" · Ruby โหลด Gem ไว้ให้เองไม่ต้อง install อะไร) · [ … ] = array ของเลขรุ่น (บท 92) · .each do |v| … end = วนทีละรุ่น (บท 93) · satisfied_by? = "รุ่นนี้ผ่านเงื่อนไขไหม" ตอบ true/false (ชื่อลงท้าย ? บท 93)

ห้ารุ่นนี้ รุ่นไหนได้ true บ้าง? ระวัง 1.14.0 — เลข 14 กับเลข 2 อะไรใหญ่กว่า?

# ผลรันจริง
$ ruby tilde.rb
1.1.0 => false
1.2.0 => true
1.14.0 => true
1.99.9 => true
2.0.0 => false

1.1.0 ต่ำกว่าขั้นต่ำ · 2.0.0 ข้ามเลขหน้าที่ล็อกไว้ · 1.14.0 ผ่าน เพราะเลขรุ่นเทียบเป็นตัวเลขทีละช่อง (14 มากกว่า 2) ไม่ได้เทียบแบบตัวอักษร
ลองแบบสามหลักบ้าง — เงื่อนไขของ rails ใน Gemfile:

# ผลรันจริง — tilde3.rb ใช้ Gem::Requirement.new("~> 8.1.4") กับอีกสี่รุ่น
$ ruby tilde3.rb
8.1.3 => false
8.1.4 => true
8.1.9 => true
8.2.0 => false

สามหลักล็อกแน่นกว่าสองหลัก: ~> 8.1.4 ยอมแค่ 8.1.x · ~> 1.2 ยอมถึง 1.99

เทียบกับ Go: ใน go.mod คุณเขียน require github.com/gin-gonic/gin v1.12.0 แปลว่า "อย่างน้อยรุ่นนี้" ไม่มีเพดานให้เขียน · Go ใช้รุ่นนี้ไปเรื่อย ๆ จนกว่าคุณจะสั่งอัปเกรดเอง · ส่วน Ruby ให้เขียนเพดานด้วย ~> แล้ว Bundler เลือกรุ่นใหม่สุดที่ยังอยู่ใต้เพดาน

🗣 ศัพท์ที่เพิ่งเรียนไป
pessimistic version constraint (~>)
ชื่อทางการของ ~> ("มองโลกแง่ร้าย" = กลัวรุ่นใหม่ทำของพัง) · ถาม senior ได้ว่า "gem นี้ควรล็อก ~> สองหลักหรือสามหลัก"

bin/rails — คำสั่ง rails ประจำโปรเจกต์

ตอน rails new คุณพิมพ์ rails เฉย ๆ เพราะยังไม่มีโปรเจกต์ · แต่หลังจากนี้ ในโฟลเดอร์โปรเจกต์ให้พิมพ์ bin/rails เสมอ — มันคือไฟล์ชื่อ rails ในโฟลเดอร์ bin/ ที่ใช้ gem ตามรุ่นใน Gemfile.lock ของโปรเจกต์นี้ ไม่ใช่รุ่นไหนก็ได้ที่ลงไว้ในเครื่อง

# ผลรันจริง — ไฟล์ bin/rails ทั้งไฟล์
$ cat bin/rails
#!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"
# ผลรันจริง (ย่อ — ตัดรายการคำสั่งย่อยยาว ๆ ท้ายออก)
$ bin/rails --help
Usage:
  bin/rails COMMAND [options]

You must specify a command. The most common commands are:

  generate     Generate new code (short-cut alias: "g")
  console      Start the Rails console (short-cut alias: "c")
  server       Start the Rails server (short-cut alias: "s")
  test         Run tests except system tests (short-cut alias: "t")
…(ตัดออก)…

Day 1 ใช้สามตัว: server (บทนี้) · generate (บท 99) · console (บท 104) · เทียบ Go: เหมือน go run . ที่อ่าน go.mod ในโฟลเดอร์นั้นเสมอ

🔑 เทียบให้เห็นภาพ — บริษัทแม่ กับ ผู้จัดการประจำสาขา

rails ที่ลงไว้ในเครื่องคือบริษัทแม่ — มีหน้าที่หลักคือมาเปิดสาขาใหม่ให้ (rails new)
bin/rails คือผู้จัดการประจำสาขา — สั่งเปิดร้าน เปิดครัวทดลอง สร้างแผนกใหม่ · และใช้อุปกรณ์ตามใบเสร็จของสาขานี้ (Gemfile.lock) เท่านั้น สาขาอื่นจะใช้รุ่นไหนไม่เกี่ยว

ที่ไม่เหมือนกัน: ผู้จัดการจริงคิดเองได้ · bin/rails เป็นแค่ไฟล์ 4 บรรทัดที่ส่งต่อคำสั่ง · และมันอยู่ในโฟลเดอร์โปรเจกต์ — ถ้าคุณยืนอยู่นอกโฟลเดอร์ wongnok_blog แล้วพิมพ์ bin/rails terminal จะหาไฟล์ไม่เจอ

🗣 ศัพท์ที่เพิ่งเรียนไป
binstub (bin/rails)
สคริปต์เล็ก ๆ ในโฟลเดอร์ bin/ ที่รันคำสั่งด้วย gem รุ่นของโปรเจกต์ · พิมพ์บอก AI ได้ว่า "use bin/rails instead of the global rails command" ถ้ามันแนะนำให้พิมพ์ rails เฉย ๆ ในโปรเจกต์
environment (development · test · production)
โหมดที่แอปรันอยู่ · bin/rails server บนเครื่องคุณคือ development เสมอ (เห็นในผลรันข้างล่าง) · ถาม senior ได้ว่า "ตอนนี้ server ตัวนี้รันใน environment ไหน" ทุกครั้งที่ของบนเครื่องกับบน server ทำงานต่างกัน

เปิดร้านครั้งแรก — bin/rails server

bin/rails server = เปิดร้าน — แอปรอรับใบสั่ง (request) ที่พอร์ต 3000 จนกว่าคุณจะปิด · ใน Go คุณต้องเขียน r.Run() เองใน main.go · ที่นี่ไม่ต้องเขียนอะไรเลย

⚠️ พิมพ์ตามสไลด์ทันที จะเจอหน้า error

ถ้ายังไม่ได้บอก Rails ว่า PostgreSQL อยู่ไหน server ขึ้นได้ปกติ แต่เปิดหน้าแรกแล้วได้ error 500 · ดูผลจริงด้านล่าง แล้วทำตามวิธีแก้ต่อจากนั้น

# ผลรันจริง — หน้าต่างที่ 1: เปิดร้าน
$ bin/rails server
=> Booting Puma
=> Rails 8.1.4 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 8.0.2 ("Into the Arena")
* Ruby version: ruby 4.0.9 (2026-10-23 revision 3e93b23afc) +PRISM [arm64-darwin25]
*  Min threads: 3
*  Max threads: 3
*  Environment: development
*          PID: 4989
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop

อ่านเฉพาะที่สำคัญ: Puma คือ web server จาก Gemfile · development = โหมดพัฒนา · Listening on http://127.0.0.1:3000 = ร้านเปิดแล้ว รอที่พอร์ต 3000 · 127.0.0.1 กับ [::1] = ที่อยู่ของ "เครื่องตัวเอง" สองแบบ (คนอื่นในเน็ตเข้าไม่ได้) · Use Ctrl-C to stop = ปิดร้านด้วย Ctrl-C · (PID = เลขประจำตัวของโปรแกรมที่กำลังรัน ของคุณจะได้เลขอื่น · threads = จำนวนใบสั่งที่รับพร้อมกันได้ ข้ามได้)

# ผลรันจริง — หน้าต่างที่ 2: ลองเป็นลูกค้า
$ curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:3000/
HTTP 500

# ส่วนหนึ่งของสิ่งที่หน้าต่างที่ 1 พิมพ์ออกมา (ย่อ)
Started GET "/" for ::1 at 2026-11-03 20:53:57 +0700

ActiveRecord::ConnectionNotEstablished (connection to server on socket "/tmp/.s.PGSQL.5432" failed: No such file or directory
	Is the server running locally and accepting connections on that socket?
)
…(ตัดออก)…

แปลกตรงที่ร้านเปิดได้ แต่ลูกค้าคนแรกเข้ามาแล้วพัง — เพราะ Rails ไม่ต่อ database ตอนเปิดร้าน มันไปต่อตอนมีใบสั่งเข้ามา (โหมด development จะเช็ค database ก่อนเสิร์ฟหน้าแรก) · ต่างจาก Go ที่บท 47 สอนให้เรียก db.Ping() ตอน start เพื่อให้พังตั้งแต่เปิดร้าน

🗄 เทียบให้เห็นภาพ — ร้านเปิดแล้ว แต่ไม่มีใครบอกว่าตู้แฟ้มอยู่ตึกไหน

ประตูร้านเปิดได้ (server ขึ้น) แต่ตู้เก็บแฟ้ม (PostgreSQL) ตั้งอยู่อีกตึก · Rails เดาว่าตู้อยู่ในห้องเดียวกัน (ช่องทาง /tmp/.s.PGSQL.5432 ในเครื่อง) — พอลูกค้าคนแรกสั่ง คนดูแลสต็อกเดินไปหาไม่เจอ จานเลยออกมาเป็น error 500
DATABASE_URL คือกระดาษจดที่อยู่ตู้แฟ้มที่คุณยื่นให้ร้านก่อนเปิด

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

วิธีแก้ — บอกที่อยู่ database ด้วย DATABASE_URL

ปิด server (Ctrl-C) แล้วตั้งตัวแปร DATABASE_URL — หน้าตาเดียวกับ DSN (สตริงบอกที่อยู่ database) ที่เคยใช้กับ goose ในบท 31 (postgres://postgres:secret@localhost:55433/shopdb…) · แล้วสั่ง bin/rails db:prepare ให้สร้าง database (รายละเอียดคำสั่งนี้รอ Day 2):

# ผลรันจริง — ค่าใน URL เป็นของเครื่องตรวจ
$ export DATABASE_URL=postgres://postgres:sandbox@127.0.0.1:55432/a98
$ bin/rails db:prepare
Created database 'a98'

แปล URL ทีละท่อน: postgres:// ชนิด database · postgres:sandbox ชื่อผู้ใช้:รหัสผ่าน · 127.0.0.1:55432 เครื่องและพอร์ต · a98 ชื่อ database
ของคุณใช้ค่าตาม Postgres ที่คุณเปิดไว้ เช่น Postgres ใน Docker พอร์ตปกติก็จะเป็น postgres://postgres:รหัสของคุณ@127.0.0.1:5432/wongnok_blog_development · และ export มีผลแค่หน้าต่าง terminal นั้น — ต้องรัน server ในหน้าต่างเดียวกัน
ยังไม่มี Postgres เปิดอยู่เลย? เปิด container แบบเดียวกับบท 31 ได้ แล้วใส่ชื่อผู้ใช้ รหัส และพอร์ตของ container นั้นลงใน URL

🗣 ศัพท์ที่เพิ่งเรียนไป
DATABASE_URL (connection string)
ที่อยู่ database แบบบรรทัดเดียว ส่งให้แอปผ่านตัวแปรของ terminal · ถ้าตั้งไว้ Rails จะใช้ค่านี้ก่อนค่าในไฟล์ตั้งค่า database (Day 2) · ถาม senior ได้ว่า "เครื่องเราต้องตั้ง DATABASE_URL เองไหม หรือทีมใส่ไว้ใน config แล้ว"
HTTP 500 · Internal Server Error
เลข status ที่แปลว่า "ฝั่ง server พังเอง" ไม่ใช่ลูกค้าขอผิด (เลข status เคยเจอในบท 34) · เจอ 500 ให้ไปอ่าน log ฝั่ง server ไม่ใช่ไปแก้ URL · พิมพ์บอก AI ได้ว่า "my Rails app returns 500, here is the server log: …" แล้วแปะบรรทัด error มาทั้งบรรทัด
# ผลรันจริง — เปิดร้านใหม่ในหน้าต่างเดิม แล้วลองเป็นลูกค้าอีกรอบ
$ bin/rails server
…(บรรทัดตอนเปิดเหมือนเดิม)…
* Listening on http://127.0.0.1:3000

# หน้าต่างที่ 2
$ curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:3000/
HTTP 200

# สิ่งที่หน้าต่าง server พิมพ์ (ย่อ path ยาว)
Started GET "/" for ::1 at 2026-11-03 20:53:59 +0700
…(ตัดบรรทัด SQL ที่ Rails ใช้เช็ค database ออก)…
Processing by Rails::WelcomeController#index as */*
  Rendering …/railties-8.1.4/lib/rails/templates/rails/welcome/index.html.erb
  Rendered …/railties-8.1.4/lib/rails/templates/rails/welcome/index.html.erb (Duration: 0.2ms | GC: 0.0ms)
Completed 200 OK in 4ms (Views: 1.1ms | ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 0.2ms)

อ่าน log เป็นภาษาร้านอาหาร:

เปิด http://localhost:3000 ในเบราว์เซอร์ จะเห็นโลโก้ Rails กับเลขรุ่น · ตัวหนังสือทั้งหน้ามีแค่นี้ (ผลจริง ตัดแท็ก HTML ออก · Rack = ตัวกลางมาตรฐานที่ให้ Puma คุยกับ Rails ได้ ตอนนี้ไม่ต้องสนใจ):

Ruby on Rails 8.1.4 Rails version: 8.1.4 Rack version: 3.2.8 Ruby version: ruby 4.0.9 (2026-10-23 revision 3e93b23afc) +PRISM [arm64-darwin25]

เพื่อนบางคนใช้ bin/dev แทน — มันเปิด server พร้อมตัว build Tailwind ไปด้วยกัน (ดูได้ในไฟล์ Procfile.dev) · Day 1 ใช้ bin/rails server ก็พอ

🎯 ภารกิจ: เปิดสาขาของคุณเอง (~10 นาที)

  1. เปิด terminal ไปที่โฟลเดอร์เก็บงาน (อย่าซ้อนในโปรเจกต์ Rails อื่น) แล้วพิมพ์ rails new wongnok_blog --database=postgresql --css=tailwind · รอจนกลับมาที่ prompt
  2. cd wongnok_blog · พิมพ์ git log — ควรเห็น fatal: your current branch 'main' does not have any commits yet
  3. ทำ commit แรก: git add -A แล้ว git commit -m "rails new" — ควรเห็นบรรทัด … files changed (เครื่องตรวจได้ 109 · ถ้า Rails ของคุณคนละรุ่น ตัวเลขอาจต่างนิดหน่อย)
  4. พิมพ์ ls -1AF app/controllers app/models แล้วตอบ: ตอนนี้มีไฟล์ของ Post หรือยัง?
  5. ตั้ง DATABASE_URL ให้ชี้ไป Postgres ของคุณ (ยังไม่มี → เปิด container แบบบท 31) → bin/rails db:prepare ควรเห็น Created database '…' → bin/rails server
  6. เปิดหน้าต่าง terminal ที่สอง: curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:3000/ — ควรได้ HTTP 200 · เปิดเบราว์เซอร์ไปที่ http://localhost:3000 ควรเห็นโลโก้ Rails
  7. กลับไปหน้าต่างแรก ดูบรรทัด Processing by … แล้วตอบ: แผนกไหนรับใบสั่งนี้ และทำไมไม่ใช่ของเรา? · ปิดร้านด้วย Ctrl-C

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

ข้อ 4: ยังไม่มี · ผลจริงคือแค่นี้:

$ ls -1AF app/controllers app/models
app/controllers:
application_controller.rb
concerns/

app/models:
application_record.rb
concerns/

Post ยังไม่มีเพราะยังไม่มีใครสร้าง — Rails ไม่ได้รู้ล่วงหน้าว่าคุณจะทำบล็อก · บท 99 จะสาธิต bin/rails generate สร้าง Post ให้ดู (ไฟล์โผล่ใน app/models/ ตามกฎตั้งชื่อ) · ส่วนแผนกบทความใน app/controllers/ คุณเขียนเองในบท 100

ข้อ 7: Rails::WelcomeController — แผนก Welcome ที่มากับ Rails (หัวหน้าเชฟที่ทำได้แค่หน้ายินดีต้อนรับ) · มันรับหน้า / เพราะ config/routes.rb ยังไม่มี root ของเราเอง (บรรทัด # root "posts#index" ยังเป็น comment) · บท 101 จะเขียน route เอง แล้วหน้าต้อนรับจะหายไป

ถ้าทำครบเจ็ดข้อ แปลว่าคุณมีโปรเจกต์ที่พร้อมใช้ต่อทั้ง Day 1 แล้ว · เก็บไว้อย่าลบ บท 99–105 ใช้ต่อ

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

ข้อ 1 · หลัง rails new เสร็จ คุณได้อะไรที่ go mod init ไม่ให้?

ตัวเลือกที่ 2 ถูก · ได้ 109 ไฟล์ เปิด server แล้วได้ 200 แต่หน้าที่เห็นคือหน้าต้อนรับของ Rails เอง (Rails::WelcomeController)

ตัวเลือกที่ 1 ผิด เพราะนั่นคือภาพของ Go · ตัวเลือกที่ 3 ผิด เพราะ app/controllers กับ app/models ยังไม่มีอะไรเกี่ยวกับ Post เลย · ตัวเลือกที่ 4 ผิด เพราะ git log ได้ fatal: … does not have any commits yet — commit แรกต้องทำเอง

จุดที่ต้องจำ: rails new ให้ร้านเปล่าที่ครบชุด ไม่ใช่แอปที่เสร็จแล้ว · และไม่ได้ commit ให้

ข้อ 2 · เพื่อนจะ clone โปรเจกต์ของคุณ แล้วอยากได้ gem รุ่นเดียวกับคุณเป๊ะ ต้องมีไฟล์ไหนอยู่ใน git?

ตัวเลือกที่ 4 ถูก · Gemfile บอกว่าอยากได้อะไร (รายการซื้อของ) · Gemfile.lock บอกว่าได้รุ่นไหนมาจริง (ใบเสร็จ) — Bundler ใช้ทั้งสองไฟล์

ตัวเลือกที่ 1 ผิด เพราะถ้าไม่มี lock Bundler จะเลือกรุ่นใหม่สุดที่ผ่านเงื่อนไข ณ วันที่เพื่อนติดตั้ง ซึ่งอาจไม่ใช่รุ่นของคุณ · ตัวเลือกที่ 2 ผิด เพราะ Bundler ต้องอ่าน Gemfile ด้วยว่าแอปต้องการ gem อะไร (lock เป็นผลลัพธ์ของ Gemfile) · ตัวเลือกที่ 3 ผิด เพราะเพื่อนไม่ได้รัน rails new — เขา clone โปรเจกต์ที่มีอยู่แล้ว

จุดที่ต้องจำ: commit ทั้ง Gemfile และ Gemfile.lock เสมอ · lock คือสิ่งที่ทำให้ทุกเครื่องได้รุ่นเดียวกัน

ข้อ 3 · Gemfile เขียน gem "image_processing", "~> 1.2" · ถ้ามีรุ่น 1.14.0 และ 2.2.0 ออกมาแล้ว Bundler ยอมรับรุ่นไหน?

ตัวเลือกที่ 1 ถูก · ~> 1.2 = ตั้งแต่ 1.2 แต่ต่ำกว่า 2.0 · ตัวเลขท้ายสุดที่เขียน (2) ขยับขึ้นได้ ตัวหน้า (1) ล็อก

ตัวเลือกที่ 2 ผิด เพราะนั่นคือความหมายของการเขียนสามหลัก ~> 1.2.0 · ตัวเลือกที่ 3 ผิด เพราะ ~> มีเพดานเสมอ ถ้าอยากได้ "ใหม่กว่าอย่างเดียว" ต้องเขียน >= แบบ puma · ตัวเลือกที่ 4 ผิด เพราะเลขรุ่นเทียบเป็นตัวเลขทีละช่อง (14 มากกว่า 2) — ผลรัน tilde.rb ได้ 1.14.0 => true

จุดที่ต้องจำ: ~> ล็อกทุกหลักยกเว้นหลักสุดท้ายที่เขียน · สามหลักแน่นกว่าสองหลัก

ข้อ 4 · คุณได้โปรเจกต์ Rails ของคนอื่นมา อยากรู้เร็ว ๆ ว่าแอปนี้มี URL อะไรบ้าง ควรเปิดไฟล์ไหนก่อน?

ตัวเลือกที่ 3 ถูก · URL ทั้งแอปอยู่ใน config/routes.rb ไฟล์เดียว — ห้องผู้จัดการที่แปะผังส่งใบสั่งไว้

ตัวเลือกที่ 1 ผิด เพราะนั่นคือแม่แบบกลางของ controller ไม่ได้บอกว่ามี URL อะไร · ตัวเลือกที่ 2 ผิด เพราะ Gemfile บอกเครื่องมือที่ใช้ ไม่ใช่หน้าที่แอปมี · ตัวเลือกที่ 4 ผิด เพราะ log จดเฉพาะ URL ที่มีคนเคยเข้ามาแล้ว (และขอ URL ที่ไม่มีอยู่จริงก็ถูกจดด้วย)

จุดที่ต้องจำ: อยากรู้ว่าแอปทำอะไรได้ เปิด config/routes.rb ก่อนเสมอ

ติดข้อไหน หรือเครื่องคุณได้ผลไม่ตรงกับบทนี้ แปะ error บรรทัดแรกมาทั้งบรรทัดแล้วถามได้เลย

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

จุดที่ของจริงไม่ตรงกับสไลด์ (เช็คบน Rails 8.1.4):

หน้าสไลด์เขียนของจริงต้องสนใจไหม
40"git repo ที่ commit แรกให้แล้ว"มีแค่ git init · git log ได้ fatal: … does not have any commits yet✅ ต้อง commit แรกเอง
40"สร้างไฟล์ให้ประมาณ 80 ไฟล์"109 ไฟล์ (นับหลัง commit แรก)ไม่ต้อง
40พิมพ์ bin/rails server ต่อได้เลยserver ขึ้น แต่หน้าแรกได้ 500 ถ้ายังไม่ได้บอกที่อยู่ Postgres✅ ตั้ง DATABASE_URL + db:prepare
39Rails มี 10 ชิ้นgem rails ลากมา 12 ตัว (เกินมา actionmailbox กับ railties — ตัวหลังคือเจ้าของคำสั่ง rails) + bundlerไม่ต้อง
42มี db/migrate/ และ db/schema.rbแอปใหม่ยังไม่มี · จะเกิดตอนสร้างตารางแรก (Day 2)ไม่ต้องตกใจ
44 · 45Gemfile มี ruby "4.0.6" · bundle info บอก "what it needs"Gemfile จริงไม่มีบรรทัด ruby (รุ่นอยู่ในไฟล์ .ruby-version) · bundle info บอกที่อยู่และใครพึ่ง gem นั้น ไม่ได้บอกว่ามันพึ่งอะไรไม่ต้อง

อ้างอิง