🔁 ทวนก่อนเริ่ม — คำจากบทก่อน ๆ ที่บทนี้ใช้
- แอปบล็อก
wongnok_blog— เว็บที่คนเขียนบทความลงไปให้คนอื่นอ่าน · บทความภาษาอังกฤษคือ post (บท 90) - class — แม่พิมพ์ของข้อมูล คล้าย
type ... structใน Go ·class A < Bแปลว่า A สืบทอดความสามารถจาก B (บท 95) - เรียก method ด้วยจุด —
"abc".upcaseคือสั่งให้ string"abc"ทำงานupcase(บท 91) bin/rails— ปุ่มสั่งงานโปรเจกต์ Rails ทุกอย่าง ต้องรันในโฟลเดอร์โปรเจกต์ (บท 98)app/models·app/controllers·app/views— สามห้องหลักในโปรเจกต์ (บท 98)
🧰 เครื่องที่ใช้ตรวจ: Ruby 4.0.9 · Rails 8.1.4 · PostgreSQL 17
(สไลด์ใช้ Ruby 4.0.6 · Rails 8.1.3 — กฎตั้งชื่อในบทนี้เหมือนกันทุกตัว) ·
ผลรันทุกกล่องก็อปมาจากเครื่องจริง ย่อเฉพาะ path ยาว ๆ เป็น …
posts คืออะไร — คำว่า post ในแอปบล็อก
แอปในสไลด์คือบล็อก — เว็บที่มีบทความให้อ่าน · ของหลักของบล็อกจึงเป็นบทความ
ซึ่งภาษาอังกฤษเรียกว่า post
Rails ใช้คำนี้สองแบบ และสองแบบนี้ไม่ใช่ของชิ้นเดียวกัน:
Post(ตัวใหญ่ · ไม่มี s) = ชนิดของข้อมูล — บอกว่า “บทความหนึ่งชิ้นมีหน้าตายังไง” เช่นมีหัวข้อ มีเนื้อหาposts(ตัวเล็ก · มี s) = บทความหลายชิ้นรวมกัน — ใช้เป็นชื่อตารางใน database และชื่อ URL/posts
ข่าวดีคือคุณเคยเขียนแบบนี้มาแล้วใน Go · ตอน GORM ใน บทเรียน 27
มี type User struct (หนึ่งคน) กับ var users []User (หลายคน) และ GORM ก็เดาชื่อตาราง users ให้เอง:
// Go — ท่อนหนึ่งในไฟล์ (เทียบให้ดูเฉย ๆ)
type Post struct { // แม่พิมพ์ของบทความ "หนึ่งชิ้น" → ตัวใหญ่ ไม่มี s
Title string
Body string
}
var posts []Post // บทความ "หลายชิ้น" → ตัวเล็ก มี s
Rails ใช้หลักเดียวกันเป๊ะ แค่เข้มกว่า: ไม่ใช่แค่ชื่อตัวแปร แต่ชื่อไฟล์ ชื่อตาราง ชื่อ URL ทั้งหมดต้องตามกฎนี้ · หัวข้อถัดไปจะไล่ให้ดูทีละช่อง
นึกถึงตู้เก็บแฟ้มหลังร้าน · Post คือแบบฟอร์มเปล่าหนึ่งแผ่น
ที่พิมพ์หัวไว้ว่า “หัวข้อ: ____ · เนื้อหา: ____” · ส่วน posts
คือลิ้นชักที่ติดป้ายว่า “บทความ” ข้างในมีแบบฟอร์มที่กรอกแล้วหลายสิบแผ่น (หนึ่งแผ่น = บทความหนึ่งชิ้น)
พูดถึง “แบบ” ใช้เอกพจน์ · พูดถึง “กอง” ใช้พหูพจน์ — แค่นั้นเลย ·
(ในภาพร้านอาหารของคอร์ส Post ยังเป็นคนดูแลสต็อกที่ถือแบบฟอร์มนี้และรู้ว่าลิ้นชักอยู่ไหนด้วย — เห็นชัดในหัวข้อ “สร้าง Post ของจริง”)
ที่ไม่เหมือนกัน: ในออฟฟิศจริง คุณจะติดป้ายลิ้นชักว่าอะไรก็ได้ เช่น “งานเขียน” ·
แต่ Rails ไม่ให้เลือก — ป้ายลิ้นชักต้องเป็นชื่อแบบฟอร์มแปลงเป็นพหูพจน์เท่านั้น
(Post → posts) ถ้าตั้งเป็นอย่างอื่น Rails จะหาไม่เจอ
- model
- class ที่เป็นตัวแทนของข้อมูลหนึ่งชนิดในแอป เช่น
Postแทนบทความ · ชื่อ model เป็นเอกพจน์ ตัวใหญ่เสมอ · ถาม senior ได้ว่า “แอปนี้มี model อะไรบ้าง ตัวไหนสำคัญที่สุด” — เป็นคำถามแรกที่ดีเวลาเปิดโปรเจกต์ใหม่ - table · row (record)
- table คือลิ้นชักใน database ที่เก็บของชนิดเดียวกัน (
posts) · row หรือ record คือของหนึ่งชิ้นในนั้น (บทความหนึ่งบทความ) · พิมพ์บอก AI ได้ว่า “thepoststable has one row per Post” - singular / plural
- เอกพจน์ / พหูพจน์ — คำที่ senior ใช้เวลาพูดถึงกฎตั้งชื่อของ Rails เช่น “model is singular, table is plural”
กฎตั้งชื่อ — ชื่อเดียว แตกไปอยู่ได้ 6 ที่
พอตัดสินใจว่าของหลักชื่อ “post” แล้ว ชื่ออื่นทุกตัวในโปรเจกต์ถูกกำหนดตามมาเอง ไม่ต้องคิดเพิ่ม · ตารางนี้คือหัวใจของบท อ่านคอลัมน์ขวาสุดให้ดี:
| ของ | ชื่อ | ชิ้นเดียว หรือ หลายชิ้น | เหตุผลภาษาคน |
|---|---|---|---|
| model (class) | Post | ชิ้นเดียว | บอกหน้าตาของบทความหนึ่งชิ้น เหมือนแบบฟอร์มหนึ่งแผ่น |
| ไฟล์ model | app/models/post.rb | ชิ้นเดียว | ไฟล์นี้เก็บ class Post · ชื่อไฟล์ = ชื่อ class แบบตัวเล็ก |
| ตารางใน database | posts | หลายชิ้น | ลิ้นชักที่เก็บบทความทุกชิ้น |
| URL | /posts | หลายชิ้น | หน้ารวมบทความ · ส่วน /posts/42 อ่านว่า “บทความเบอร์ 42 ในกลุ่มบทความ” |
| controller (class) | PostsController | หลายชิ้น | ดูแลงานของบทความทั้งกลุ่ม ทั้งโชว์รายการ และเปิดดูทีละชิ้น |
| ไฟล์ controller | app/controllers/posts_controller.rb | หลายชิ้น | ชื่อไฟล์ = ชื่อ class แบบตัวเล็กคั่นด้วย _ |
| โฟลเดอร์ view | app/views/posts/ | หลายชิ้น | เก็บหน้าตาทุกหน้าของแผนกบทความ |
สังเกตรูปแบบการเขียนสองแบบ · ชื่อ class เขียนตัวใหญ่ต้นคำแล้วติดกัน (PostsController) ·
ชื่อไฟล์และตาราง เขียนตัวเล็กหมด คั่นคำด้วย _ (posts_controller.rb)
กฎย่อสั้นที่สุด: model เอกพจน์ · ที่เหลือเกือบทั้งหมดพหูพจน์
ทีนี้เทียบกับ Go · ตอนเขียน Gin คุณบอกเองทุกอย่าง — URL ไหนไปฟังก์ชันไหน
(r.GET("/posts", listPosts)) ตั้งชื่อฟังก์ชันว่าอะไรก็ได้ ·
Rails ไม่มีบรรทัดแบบนั้นให้เขียน เพราะมันเดาจากชื่อแทน
Go เหมือนเปิดร้านเองตั้งแต่ศูนย์ · คุณจะวางครัวตรงไหน ติดป้ายแผนกว่าอะไรก็ได้
แต่ต้องเขียนผังร้านแปะไว้ให้พนักงานใหม่อ่านเอง
Rails เหมือนร้านแฟรนไชส์ · ทุกสาขาวางของตำแหน่งเดียวกัน
ป้ายแผนกตั้งตามกฎกลาง — แผนกบทความต้องชื่อ “Posts” ห้องเก็บของต้องชื่อ “posts” ·
พนักงานที่ย้ายมาจากสาขาอื่นเดินเข้ามาก็หาของเจอทันทีโดยไม่ต้องดูผัง
ที่ไม่เหมือนกัน: พนักงานจริงเห็นป้าย “Post” แทน “Posts” ก็ยังเดาออกว่าคือแผนกเดียวกัน · แต่ Rails ไม่เดาใกล้เคียง — ขาด s ตัวเดียวก็หาไม่เจอแล้ว (เดี๋ยวจะทำให้พังจริงให้ดูในหัวข้อ “ตั้งชื่อผิดตัวเดียว”)
- Convention over Configuration
- หลักของ Rails: “ตั้งชื่อตามกฎ แทนการเขียนตั้งค่าเอง” · convention = ข้อตกลงที่ทุกคนทำเหมือนกัน · configuration = การเขียนบอกเองว่าอะไรอยู่ไหน · ถาม senior ได้ว่า “โปรเจกต์นี้มีจุดไหนที่ไม่ตั้งชื่อตาม convention บ้าง” เพราะจุดพวกนั้นคือที่ที่คนใหม่หลงบ่อยที่สุด
- CamelCase · snake_case
- CamelCase = ตัวใหญ่ต้นคำแล้วเขียนติดกัน (
PostsController) ใช้กับชื่อ class · snake_case = ตัวเล็กคั่นด้วย_(posts_controller) ใช้กับชื่อไฟล์และตาราง · บอก AI ได้ว่า “rename this file to snake_case to match the class name” - controller
- class ที่รับคำขอจาก browser แล้วตัดสินใจว่าจะตอบอะไร — ในภาพร้านอาหารคือหัวหน้าเชฟของแผนก ·
ตอนนี้รู้แค่ว่าชื่อต้องพหูพจน์ต่อท้ายด้วย
Controllerก็พอ · ละเอียดใน บทเรียน 102
Rails แปลงชื่อเอง — ลองสั่งดูด้วยมือ
Rails ไม่ได้มีสมุดจดไว้ว่า Post คู่กับ posts ·
มันคำนวณชื่อใหม่ทุกครั้งด้วย method ของ string ที่ Active Support (ของเสริมที่ Rails เติมให้ Ruby · บท 90) เพิ่มเข้ามา
เราเรียก method พวกนั้นเองได้ · ที่ที่สั่งง่ายที่สุดคือ bin/rails console
(ช่องพิมพ์ Ruby ที่รู้จักทุกอย่างในแอป — เหมือนครัวทดลองหลังร้าน ลองของได้โดยไม่ต้องเปิดร้าน · ละเอียดใน บทเรียน 104)
ในโฟลเดอร์โปรเจกต์ สั่ง bin/rails console แล้วพิมพ์ทีละบรรทัด (ผลจริง):
$ bin/rails console
Loading development environment (Rails 8.1.4)
wongnok-blog(dev):001> "Post".tableize
=> "posts"
wongnok-blog(dev):002> "post".pluralize
=> "posts"
wongnok-blog(dev):003> "person".pluralize
=> "people"
wongnok-blog(dev):004> "people".singularize
=> "person"
wongnok-blog(dev):005> "posts".classify
=> "Post"
อ่านทีละส่วน:
wongnok-blog(dev):001>คือ prompt ของ console — ชื่อแอป ·dev= โหมดพัฒนา ·001= บรรทัดที่เท่าไหร่ · คุณพิมพ์ต่อจากเครื่องหมาย>=>บรรทัดถัดไปคือค่าที่ได้กลับมา console พิมพ์ให้เองโดยไม่ต้องputspluralize= ทำเป็นพหูพจน์ ·singularize= ทำเป็นเอกพจน์tableize= แปลงชื่อ model เป็นชื่อตาราง (ตัวเล็ก + พหูพจน์) ·classify= ทางกลับ แปลงชื่อตารางเป็นชื่อ class- สังเกต
person→people· Rails รู้จักคำพิเศษแบบนี้ ไม่ได้แค่เติม s
พิมพ์ exit เพื่อออกจาก console
"Category".tableize # หมวดหมู่
"Person".tableize # คน
"Mouse".tableize # หนู
"Sheep".tableize # แกะ
สี่บรรทัดนี้จะได้ชื่อตารางอะไรบ้าง? คิดก่อนกดเฉลย (ใบ้: ภาษาอังกฤษมีคำที่พหูพจน์ไม่ได้แค่เติม s)
wongnok-blog(dev):001> "Category".tableize
=> "categories"
wongnok-blog(dev):002> "Person".tableize
=> "people"
wongnok-blog(dev):003> "Mouse".tableize
=> "mice"
wongnok-blog(dev):004> "Sheep".tableize
=> "sheep"
ถูกตามภาษาอังกฤษทุกตัว · Category เปลี่ยน y เป็น ies ·
Mouse เป็น mice · Sheep พหูพจน์หน้าตาเหมือนเดิม
⚠️ Rails ไม่ได้รู้ภาษาอังกฤษจริง มันมีแค่รายการกฎตัดท้ายคำ ·
ผลจริง: "human".pluralize ได้ "humen" (ที่ถูกคือ humans) เพราะไปโดนกฎ man → men ·
ก่อนสร้าง model ด้วยคำที่ไม่คุ้น สั่ง "ชื่อ".tableize ใน console ดูก่อนเสมอ ใช้เวลาสองวินาที
ไม่ต้อง import — Rails หาไฟล์จากชื่อให้
นี่คือคำตอบของ “Post มาเฉย ๆ ได้ไง” ครึ่งแรก
ใน Go ถ้าจะใช้ Post ที่อยู่อีก package คุณต้อง import บอกที่อยู่ก่อนเสมอ ไม่งั้น build ไม่ผ่าน:
// Go — ต้องบอกว่า Post อยู่ package ไหน
import "blog/models"
p := models.Post{Title: "สวัสดี"}
ใน Rails คุณพิมพ์ Post ได้เลยในไฟล์ไหนก็ได้ของแอป · พอ Ruby เจอชื่อที่ยังไม่รู้จัก
Rails จะแปลงชื่อเป็นชื่อไฟล์ (Post → post.rb)
แล้วไปหาไฟล์นั้นในโฟลเดอร์ app/models, app/controllers ฯลฯ ให้เอง
ถ้ายังไม่มีไฟล์นั้น ก็หาไม่เจอ · โปรเจกต์ที่เพิ่ง rails new ยังไม่มี model เลย ·
ลองเปิด bin/rails console ใหม่ แล้วพิมพ์ Post เฉย ๆ (ผลจริง):
$ bin/rails console
Loading development environment (Rails 8.1.4)
wongnok-blog(dev):001> Post
(wongnok-blog):1:in '<main>': uninitialized constant Post (NameError)
uninitialized constant Post แปลตรง ๆ ว่า “ยังไม่รู้จักชื่อ Post” ·
constant คือชื่อที่ขึ้นต้นด้วยตัวใหญ่ ซึ่งชื่อ class ทุกตัวเป็นแบบนั้น ·
ตอนนี้ไม่มี app/models/post.rb Rails เลยหาให้ไม่ได้
Go เหมือนห้องสมุดที่คุณต้องบอกเลขชั้นเองทุกครั้งก่อนหยิบ (import "blog/models") ·
Rails เหมือนมีบรรณารักษ์ · คุณแค่ตะโกนชื่อเล่ม “Post!” บรรณารักษ์เดินไปหาเล่มที่สันเขียนว่า post.rb แล้วยื่นให้
ที่ไม่เหมือนกัน: บรรณารักษ์จริงหาจากเนื้อเรื่องหรือชื่อผู้แต่งได้ด้วย · แต่ Rails อ่านแค่สันหนังสือ (ชื่อไฟล์) อย่างเดียว — ถ้าข้างในเล่มเป็นเรื่อง Post แต่สันเขียนชื่ออื่น มันจะหาไม่เจอ (หัวข้อ “ตั้งชื่อผิดตัวเดียว” ข้างล่างพิสูจน์ให้ดู)
- autoload (โหลดอัตโนมัติ)
- การที่ Rails ไปเปิดไฟล์ให้เองตอนโค้ดเรียกชื่อ class ครั้งแรก ทำให้ไม่ต้องเขียน
requireหรือimport· ถาม senior ได้ว่า “ไฟล์ในโฟลเดอร์lib/ของโปรเจกต์นี้ autoload ไหม” เพราะบางโฟลเดอร์ไม่ได้เปิดไว้ - Zeitwerk
- ชื่อของตัวโหลดอัตโนมัติที่ Rails ใช้ (อ่านว่า “ไซท์-แวร์ก”) · กฎหลักของมันคือชื่อไฟล์ต้องตรงกับชื่อ class · เจอคำนี้ใน error ให้เช็คชื่อไฟล์กับชื่อ class ก่อนอย่างอื่น
- constant · NameError
- constant = ชื่อที่ขึ้นต้นตัวใหญ่ เช่นชื่อ class · NameError พร้อมคำว่า
uninitialized constant X= “หาชื่อ X ไม่เจอ” · พิมพ์บอก AI ได้ว่า “I get uninitialized constant Post — which file should define it?”
สร้าง Post ของจริงด้วยคำสั่งเดียว
👀 หัวข้อนี้กับหัวข้อถัดไป อ่านผลตามไปก่อน ยังไม่ต้องพิมพ์ตาม ·
ได้ลงมือเองในภารกิจท้ายบท (สร้าง Category แล้วเก็บกวาดให้สะอาด) ·
เหตุผล: บทเรียน 100 จะเริ่มสร้างหน้า /posts จากโปรเจกต์ที่ยังไม่มี Post
ไม่ต้องสร้างไฟล์เองทีละไฟล์ · Rails มีคำสั่ง bin/rails generate (ย่อ g)
ที่สร้างไฟล์ทุกไฟล์ให้ถูกชื่อ ถูกที่ ตามกฎในตารางข้างบน
คำสั่งนี้แปลว่า “สร้าง model ชื่อ Post ที่มีช่อง title เป็นข้อความสั้น และช่อง body เป็นข้อความยาว”:
$ bin/rails generate model Post title:string body:text
invoke active_record
create db/migrate/20261103141245_create_posts.rb
create app/models/post.rb
invoke test_unit
create test/models/post_test.rb
create test/fixtures/posts.yml
title:string— ชื่อช่อง:ชนิดของช่อง ·string= ข้อความสั้น (หัวข้อ) ·text= ข้อความยาว (เนื้อหา)invoke= “เรียกผู้ช่วยมาทำงาน” ·create= “สร้างไฟล์นี้แล้ว”db/migrate/…_create_posts.rb— ไฟล์สั่งสร้างตารางposts· ตัวเลขข้างหน้าคือวันเวลาที่สั่ง ของคุณจะเป็นเลขอื่นapp/models/post.rb— ไฟล์ของ classPost(เอกพจน์ · ตรงตาราง)- สองไฟล์ใน
test/— ที่สำหรับเขียนเทส · ยังไม่ต้องสนใจ เจอกันใน Day 4
เปิดไฟล์ model ที่ได้มา · นี่คือทั้งไฟล์จริง ไม่ได้ตัดอะไรออก:
# app/models/post.rb
class Post < ApplicationRecord
end
แค่สองบรรทัด · บรรทัดแรกแปลว่า “สร้าง class ชื่อ Post ที่สืบทอดจาก ApplicationRecord” (< = สืบทอด จาก บท 95) ·
บรรทัดสองปิด class
แล้ว title กับ body ไปอยู่ไหน? ไม่อยู่ในไฟล์นี้เลย · มันอยู่ในไฟล์ migration:
# db/migrate/20261103141245_create_posts.rb
class CreatePosts < ActiveRecord::Migration[8.1]
def change
create_table :posts do |t| # สร้างตารางชื่อ posts (พหูพจน์!)
t.string :title # ช่อง title เก็บข้อความสั้น
t.text :body # ช่อง body เก็บข้อความยาว
t.timestamps # เติมช่องเวลาสร้าง/เวลาแก้ให้เอง
end
end
end
ไม่ต้องจำวิธีเขียน แค่อ่านออกพอ:
class CreatePosts < ActiveRecord::Migration[8.1]— class ชื่อ “สร้างตาราง posts” ที่สืบทอดความสามารถด้านแก้ตารางมาจาก Rails ·[8.1]= เขียนตามกติกาของ Rails รุ่น 8.1def change— method ที่บอกว่า “จะเปลี่ยนอะไรใน database”create_table :posts do |t|— สร้างตารางชื่อposts(:postsคือ symbol จาก บท 92) ·do |t| … endคือ block (บท 93) ที่ได้tมาเป็น “ตัวแทนของตารางที่กำลังสร้าง”t.string :title— สั่งtว่า “เพิ่มช่องชื่อ title ชนิดข้อความสั้น” · บรรทัดt.text :bodyก็แบบเดียวกันt.timestamps— เพิ่มช่องcreated_at(สร้างเมื่อไหร่) กับupdated_at(แก้ล่าสุดเมื่อไหร่) ให้เอง
ไฟล์นี้คือใบสั่งช่างมาต่อเติมตู้เก็บแฟ้ม — ยังไม่มีอะไรเกิดขึ้นจนกว่าจะสั่งช่างลงมือด้วย
bin/rails db:migrate · วิธีเขียนไฟล์นี้ละเอียดเป็นเรื่องของ Day 2 ·
วันนี้รู้แค่ว่า “ช่องของข้อมูลถูกจดไว้ที่นี่ แล้วไปเกิดเป็นตารางใน database” ก็พอ
$ bin/rails db:migrate
== 20261103141245 CreatePosts: migrating ======================================
-- create_table(:posts)
-> 0.0045s
== 20261103141245 CreatePosts: migrated (0.0046s) =============================
ตอนนี้ลิ้นชัก posts มีอยู่จริงแล้ว · ลองสร้างบทความหนึ่งชิ้น
เขียนไฟล์ Ruby เล็ก ๆ ไว้ที่โฟลเดอร์บนสุดของโปรเจกต์ แล้วรันด้วย bin/rails runner
(รันไฟล์ Ruby โดยโหลดแอปทั้งตัวให้ก่อน — เหมือน ruby ไฟล์.rb แต่รู้จัก Post):
# try_post.rb — ลองสร้างบทความหนึ่งชิ้น (ยังไม่บันทึกลง database)
post = Post.new(title: "สวัสดี Rails", body: "บทความแรกของบล็อก")
p post
puts post.title
puts post.body
$ bin/rails runner try_post.rb
#<Post id: nil, title: "สวัสดี Rails", body: "บทความแรกของบล็อก", created_at: nil, updated_at: nil>
สวัสดี Rails
บทความแรกของบล็อก
Post.new(title: …, body: …)— สร้างบทความหนึ่งชิ้นจากแม่พิมพ์Postแล้วกรอกช่องตามชื่อ- บรรทัด
#<Post …>มาจากp— โชว์ทุกช่องของบทความ · มีidcreated_atupdated_atที่เราไม่ได้สั่ง (มาจากt.timestampsและ Rails เติมidให้เอง) · ค่าเป็นnilเพราะยังไม่ได้บันทึก post.titleใช้ได้ ทั้งที่post.rbไม่มีคำว่า title สักตัว
นี่คือคำตอบครึ่งหลังของ “Post มาเฉย ๆ ได้ไง” · ตอนรัน Rails ไปเปิดตาราง posts ดูว่ามีช่องอะไร
แล้วสร้าง method title body ให้ตามนั้น (method ที่ Rails สร้างให้ตอนรัน จาก บท 97) ·
เทียบสองฝั่งให้เห็นชัด:
Go — เขียนทุกช่องเองใน struct
type Post struct {
ID uint
Title string
Body string
}
compiler รู้ตั้งแต่ตอน build ว่า Post มีช่องอะไร · พิมพ์ p.Titel ผิด build ไม่ผ่านทันที (ผลจริง: p.Titel undefined (type Post has no field or method Titel))
Rails — class ว่าง ช่องมาจากตาราง
class Post < ApplicationRecord
end
ช่องจดไว้ที่เดียวคือตารางใน database · Rails ไปอ่านตอนรัน · พิมพ์ชื่อช่องผิดจะรู้ตอนรันถึงบรรทัดนั้น
# try_author.rb
post = Post.new(title: "สวัสดี Rails")
puts post.author
ตาราง posts ไม่มีช่อง author · สั่ง bin/rails runner try_author.rb แล้วจะได้อะไร?
พังตอนรันถึงบรรทัดที่ 3 ของไฟล์ (บรรทัด 1 คือ comment) · ผลจริง ตัด path ยาวด้านหน้าออก:
…: undefined method 'author' for an instance of Post (NoMethodError)
from …
from …/try_author.rb:3:in '<main>'
undefined method 'author' = “บทความไม่มีคำสั่งชื่อ author” ·
Rails สร้าง method ให้เฉพาะช่องที่มีในตารางจริง ไม่ได้มั่วให้ทุกชื่อ ·
ต่างจาก Go ตรงที่ Go ฟ้องตั้งแต่ build ส่วน Ruby ฟ้องตอนรันถึงบรรทัดนั้น
model Post คือคนดูแลสต็อกของลิ้นชักบทความ · ใน Go คนดูแลสต็อกถือสมุดที่จดไว้ว่า
“บทความมีช่อง Title, Body” (struct) · ใน Rails คนดูแลสต็อกไม่ถือสมุดเลย
ทุกครั้งที่เริ่มงาน เขาเปิดลิ้นชักดูป้ายหัวคอลัมน์ แล้วรู้เองว่ามีช่องอะไรบ้าง ·
ช่างมาต่อเติมลิ้นชักเพิ่มช่องใหม่เมื่อไหร่ คนดูแลสต็อกก็รู้เองโดยไม่ต้องแก้สมุด
ที่ไม่เหมือนกัน: คนจริงเปิดลิ้นชักแล้วเจอว่าลิ้นชักยังไม่ได้สร้าง จะบอกคุณดี ๆ ·
Rails จะโยน error ยาวทันที (relation "posts" does not exist ถ้าลืม db:migrate — ลองเจอเองในภารกิจท้ายบท)
- generator
- คำสั่ง
bin/rails generate …ที่สร้างไฟล์ให้ถูกชื่อถูกที่ตามกฎ · ทางกลับคือbin/rails destroy …(ลบไฟล์ชุดเดียวกันออก) · ถาม senior ได้ว่า “ทีมนี้ใช้ generator หรือสร้างไฟล์เอง” — บางทีมปิดการสร้างไฟล์เทสอัตโนมัติไว้ - migration
- ไฟล์ใน
db/migrate/ที่สั่งสร้างหรือแก้ตาราง — ใบสั่งช่างมาต่อเติมตู้ · สั่งลงมือด้วยbin/rails db:migrate· เรื่องเต็มอยู่ Day 2 · บอก AI ได้ว่า “add a migration that adds apublished_atcolumn to posts” - attribute (column)
- ช่องของข้อมูล เช่น
titlebody· ใน database เรียก column · ในฝั่ง model เรียก attribute · ถาม senior ได้ว่า “attribute ของ Post มาจาก schema ตอนรันใช่ไหม ถ้าอยากรู้ว่ามีช่องอะไรต้องดูไฟล์ไหน”
ตั้งชื่อผิดตัวเดียว แล้วเจออะไร
มาดูสองแบบที่มือใหม่เจอบ่อยที่สุด · แบบแรก ชื่อไฟล์ model ไม่ตรงชื่อ class · แบบที่สอง ชื่อ controller ขาด s
แบบที่ 1 · ชื่อไฟล์ model ไม่ตรง
$ mv app/models/post.rb app/models/article.rb # mv = เปลี่ยนชื่อไฟล์ · ข้างในยังเป็น class Post เหมือนเดิม
$ bin/rails console
wongnok-blog(dev):001> Post
ตอนนี้มี Post แล้ว (จากหัวข้อที่แล้ว) · ถ้าเปลี่ยนชื่อไฟล์ post.rb เป็น article.rb แต่ข้างในยังเขียน class Post อยู่ · พิมพ์ Post ใน console จะเจออะไร?
wongnok-blog(dev):001> Post
(wongnok-blog):1:in '<main>': uninitialized constant Post (NameError)
หาไม่เจอ ทั้งที่ class Post ยังอยู่ในโปรเจกต์ครบทุกตัวอักษร ·
เพราะ Rails แปลง Post เป็น post.rb แล้วไปหาไฟล์ชื่อนั้น — ไม่ได้เปิดทุกไฟล์มาค้นว่าข้างในมีอะไร ·
เปลี่ยนชื่อไฟล์กลับเป็น post.rb ก็ใช้ได้เหมือนเดิม (ลองแล้ว)
ใน Go ไม่มีปัญหานี้ เพราะชื่อไฟล์ .go ตั้งอะไรก็ได้ ขอแค่อยู่ใน package เดียวกัน
แบบที่ 2 · ชื่อ controller ขาด s
สมมติเราอยากมีหน้า /posts โชว์รายการบทความ
เลยเติมบรรทัดนี้ใน config/routes.rb (ไฟล์ของพนักงานต้อนรับ ที่ดูว่า URL ไหนไปแผนกไหน · ละเอียดใน บทเรียน 101):
get "posts", to: "posts#index" # URL /posts → แผนก posts · เมนู index
get— method ที่แปลว่า “ถ้ามีคนขอดู (GET) URL นี้…” · เรียกแบบละวงเล็บ (บท 97)to: "posts#index"— “…ส่งไปแผนกpostsเมนูindex” ·to:คือ hash ท้าย argument ที่ไม่ใส่ปีกกา (บท 97) ·#ตรงนี้เป็นแค่ตัวคั่นในข้อความ ไม่ใช่ commentindex— ชื่อที่ Rails นิยมใช้กับหน้ารวมรายการ (เหมือน “หน้าสารบัญ”)
Rails จะแปลงคำว่า posts เป็นชื่อ class PostsController เอง ·
แต่เราเผลอสร้างไฟล์ controller เป็นเอกพจน์:
# app/controllers/post_controller.rb ← ขาด s
class PostController < ApplicationController # ← ขาด s
def index
end
end
และสร้างไฟล์หน้าตาของหน้านี้ไว้หนึ่งบรรทัด ที่ app/views/posts/index.html.erb
(ข้างในมีแค่ <h1>บทความทั้งหมด</h1> · .html.erb = ไฟล์ HTML ที่แทรกโค้ด Ruby ได้ เรียกว่า ERB ละเอียดใน บทเรียน 103) · จากนั้นเปิด server (bin/rails server) แล้วขอหน้า /posts จากอีกหน้าต่าง terminal (ผลจริง):
$ curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:3000/posts # curl = ขอหน้าเว็บจาก terminal · -w พิมพ์แค่เลขสถานะ
HTTP 500
500 คือเลขสถานะที่แปลว่า “ฝั่ง server พัง” (ส่วน 200 = สำเร็จ · เคยเจอใน Go ฝั่ง BACK แล้ว)
ใน browser จะเห็นหน้า error สีแดง หัวข้อใหญ่สองบรรทัดนี้:
ActionDispatch::MissingController in PostsController#index
uninitialized constant PostsController
อ่านแบบมือใหม่: Rails ตามหา PostsController (มี s) แต่ไม่เจอ ·
ในร้านอาหารคือพนักงานต้อนรับตะโกนเรียกแผนก “Posts” แต่ป้ายหน้าครัวเขียนว่า “Post” — ไม่มีใครขานรับ ลูกค้าเลยได้จาน error (HTTP 500)
วิธีแก้: ทำให้ชื่อตรงกฎทั้งชื่อไฟล์และชื่อ class:
# app/controllers/posts_controller.rb ← เปลี่ยนชื่อไฟล์
class PostsController < ApplicationController # ← เติม s
def index
end
end
$ curl -s -w '\nHTTP %{http_code}\n' http://localhost:3000/posts # ตัดมาเฉพาะท้าย ๆ
…
<!-- BEGIN app/views/posts/index.html.erb
--><h1>บทความทั้งหมด</h1>
<!-- END app/views/posts/index.html.erb -->
…
HTTP 200
บรรทัด <!-- BEGIN … --> กับ <!-- END … --> คือ comment ของ HTML ที่ Rails แปะให้เองในโหมดพัฒนา
บอกว่า HTML ท่อนนี้มาจากไฟล์ไหน (browser ไม่โชว์ให้เห็นบนหน้าจอ)
ได้ 200 แล้ว · และสังเกตของแถม: เราไม่ได้บอก controller เลยว่าจะใช้หน้าตาไฟล์ไหน
แต่ Rails ไปหยิบ app/views/posts/index.html.erb มาเอง
—
ชื่อแผนก + ชื่อเมนู = ที่อยู่ของไฟล์หน้าตา · กฎตั้งชื่อชุดเดียวกันอีกแล้ว (เรื่อง view ละเอียดใน บทเรียน 103)
แล้วถ้าเป็น Go? Go ส่งตัวฟังก์ชันให้ router ตรง ๆ ไม่ใช่ส่งข้อความ
compiler เลยจับชื่อผิดได้ตั้งแต่ยังไม่รัน · ไฟล์นี้ประกาศ postIndex แต่ผูก route ด้วย postsIndex
(ใช้ net/http ตัวมาตรฐานแทน Gin ให้ไฟล์สั้น หลักเดียวกัน · ผลจริง go1.26.5):
package main
import (
"fmt"
"net/http"
)
func postIndex(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "บทความทั้งหมด")
}
func main() {
http.HandleFunc("GET /posts", postsIndex)
http.ListenAndServe(":3000", nil)
}
$ go build ./...
# blog
./main.go:13:32: undefined: postsIndex
สรุปความต่าง: Go ฟ้องตอน build · Rails ฟ้องตอนมีคนเปิดหน้านั้นจริง
เพราะ Rails เก็บแค่ข้อความ "posts#index" แล้วค่อยแปลงเป็นชื่อ class ตอนมี request ·
แลกกันระหว่างความสะดวก (ไม่ต้องเขียนผูกเอง) กับการที่ต้องตั้งชื่อให้ตรงกฎเอง
⚠️ เห็น uninitialized constant XxxController ให้เช็คสามอย่างให้ตรงกัน:
คำใน route (posts#index) · ชื่อไฟล์ (posts_controller.rb) · ชื่อ class (PostsController) ·
ผิดตัวเดียวก็พัง
ApplicationRecord กับ ApplicationController — แม่แบบกลางที่ทุกตัวสืบทอด
สังเกตไหมว่า Post < ApplicationRecord และ PostsController < ApplicationController ·
สองตัวนี้ rails new สร้างไว้ให้ตั้งแต่แรก เป็นแม่แบบกลางของแอปเรา
ที่ model ทุกตัวและ controller ทุกตัวสืบทอดไป · นี่คือไฟล์จริงทั้งไฟล์:
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser versions: :modern
# Changes to the importmap will invalidate the etag for HTML responses
stale_when_importmap_changes
end
ActiveRecord::Base— ของจริงจาก Rails ที่มีความสามารถคุยกับ database ทั้งหมด (::แปลว่า “ที่อยู่ข้างใน” —Baseที่อยู่ในActiveRecord)primary_abstract_class— บอกว่า “class นี้เป็นแม่แบบเฉย ๆ ไม่มีลิ้นชักของตัวเอง” · อย่าลบทิ้งallow_browser versions: :modern— “ไม่รับ browser รุ่นเก่า” · เพราะอยู่ในแม่แบบกลาง controller ทุกตัวในแอปจึงได้กฎนี้ไปโดยไม่ต้องเขียนซ้ำstale_when_importmap_changes— เรื่องการเก็บหน้าเว็บไว้ใช้ซ้ำ ยังไม่ต้องสนใจ
ถามใน console ได้ว่าใครเป็นลูกของใคร (superclass = “พ่อของฉันคือใคร” · ผลจริง):
wongnok-blog(dev):001> Post.superclass
=> ApplicationRecord(abstract)
wongnok-blog(dev):002> ApplicationRecord.superclass
=> ActiveRecord::Base
wongnok-blog(dev):003> PostsController.superclass
=> ApplicationController
wongnok-blog(dev):004> ApplicationController.superclass
=> ActionController::Base
wongnok-blog(dev):005> ApplicationRecord.table_name
=> nil
wongnok-blog(dev):006> Post.table_name
=> "posts"
(abstract) ข้างหลัง ApplicationRecord คือผลของบรรทัด primary_abstract_class ·
และ table_name เป็น nil = ไม่มีลิ้นชักของตัวเอง ตรงตามที่บอก
ทำไมไม่ให้ Post สืบทอดจาก ActiveRecord::Base ตรง ๆ?
เพราะอยากมีที่เดียวไว้ใส่ของที่ทุก model ในแอปเราต้องมี · ใส่ตรงนี้ทีเดียว ทุกตัวได้หมด
Go ไม่มีการสืบทอด · ที่ใกล้ที่สุดคือ r.Use(...) ใน Gin ที่ใส่ middleware
(ด่านที่ request ทุกตัวต้องผ่าน · ฝั่ง Rails ละเอียดใน บทเรียน 100) ทีเดียวแล้วมีผลกับทุก route ·
ApplicationController ทำหน้าที่คล้ายกัน แต่ทำผ่านการสืบทอด class แทน
ActiveRecord::Base / ActionController::Base คือคู่มือจากบริษัทแม่ ที่ทุกร้านในโลกได้เหมือนกัน ·
ApplicationRecord / ApplicationController คือคู่มือของสาขาเรา ที่เติมกฎเฉพาะร้านลงไป
เช่น “ไม่รับลูกค้าที่ใช้ browser รุ่นเก่า” ·
แต่ละแผนก (PostsController) ถือคู่มือของสาขาอีกที จึงได้ทั้งกฎบริษัทแม่และกฎสาขาโดยอัตโนมัติ
ที่ไม่เหมือนกัน: คู่มือสาขาจริงมักหนาเป็นเล่ม · ของ Rails ตอนเริ่มเกือบว่าง และ
ApplicationRecord เป็นแค่ “คู่มือ” — ห้ามเอาไปใช้เป็นแผนกเอง ·
สั่ง ApplicationRecord.new แล้ว Rails ไม่ยอม (ผลจริง: ApplicationRecord is an abstract class and cannot be instantiated. (NotImplementedError)
= “อันนี้เป็นแม่แบบ สร้างของจากมันตรง ๆ ไม่ได้”)
- superclass · inherit
- superclass = class พ่อ · inherit = สืบทอดความสามารถมาจากพ่อ (
<ใน Ruby) · ถาม senior ได้ว่า “มีอะไรใส่ไว้ใน ApplicationController บ้าง ที่ทุกหน้าโดนหมด” - base class (แม่แบบกลาง)
- class ที่มีไว้ให้คนอื่นสืบทอด เช่น
ApplicationRecord· ของที่ทุกตัวต้องมีให้ใส่ที่นี่ · บอก AI ได้ว่า “put this shared method in ApplicationRecord so every model gets it” - abstract class
- class ที่ใช้สร้างของตรง ๆ ไม่ได้ มีไว้เป็นพ่ออย่างเดียว · ใน Rails คือผลของ
primary_abstract_class· เห็นคำว่า(abstract)ใน console ให้อ่านว่า “อันนี้เป็นแม่แบบ ไม่มีตาราง”
สำรวจโปรเจกต์ — สามคำสั่งที่ใช้จริงตั้งแต่วันแรก
เปิดโปรเจกต์ Rails ที่ไม่เคยเห็น (ของเพื่อน ของทีม หรือของสไลด์) · สามคำสั่งนี้บอกว่าข้างในมีอะไรโดยไม่ต้องเปิดไฟล์ทีละไฟล์
1 · bin/rails about — แอปนี้ใช้อะไรรุ่นไหน
$ bin/rails about # ตัดบรรทัด Middleware กับ Application root ที่ยาวมากออก
About your application's environment
Rails version 8.1.4
Ruby version ruby 4.0.9 (2026-10-23 revision 3e93b23afc) +PRISM [arm64-darwin25]
RubyGems version 4.0.20
Rack version 3.2.8
Environment development
Database adapter postgresql
Database schema version 20261103141245
ที่ควรดู: Rails version กับ Ruby version (ถาม AI เรื่องโค้ดต้องบอกเลขนี้ด้วย) ·
Database adapter = ใช้ database ยี่ห้ออะไร ·
Database schema version = เลขของ migration ล่าสุดที่รันแล้ว (ตรงกับชื่อไฟล์ …_create_posts.rb ข้างบน) ·
ของคุณจะได้เลขเวอร์ชันและเลข schema คนละชุด
2 · bin/rails routes — URL ไหนไปแผนกไหน
$ bin/rails routes -c posts # -c posts = ดูเฉพาะของ PostsController
Prefix Verb URI Pattern Controller#Action
posts GET /posts(.:format) posts#index
อ่านทีละคอลัมน์: Prefix = ชื่อเล่นของ route นี้ (เอาไปใช้ตอนทำลิงก์ · บท 101) ·
Verb = ชนิดคำขอ (GET = ขอดู) · URI Pattern = URL
((.:format) แปลว่าจะต่อท้าย .json ก็ได้ ไม่ต่อก็ได้) ·
Controller#Action = แผนกกับเมนู — posts#index คือ PostsController เมธอด index ·
ถ้าไม่ใส่ -c posts จะได้ยาวเกือบ 30 บรรทัด เพราะ Rails มี route ของตัวเองติดมาด้วย
3 · bin/rails --help — มีคำสั่งอะไรให้ใช้บ้าง
$ bin/rails --help # 120 บรรทัด — ตัดมาแค่ส่วนบน
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")
test:system Run system tests
dbconsole Start a console for the database specified in config/database.yml
(short-cut alias: "db")
plugin new Create a new Rails railtie or engine
All commands can be run with -h (or --help) for more information.
…
สามตัวบนสุดใช้บ่อยที่สุด และมีชื่อย่อ: bin/rails g = generate · bin/rails c = console · bin/rails s = server ·
คำสั่งไหนไม่รู้วิธีใช้ ให้ต่อท้ายด้วย --help เช่น bin/rails generate model --help
ภารกิจ — สร้าง model หมวดหมู่ (Category) เอง
🎯 ภารกิจ: บล็อกอยากแบ่งบทความเป็นหมวด ๆ เช่น “อาหารเหนือ”
ทำในโปรเจกต์ wongnok_blog ที่สร้างไว้จาก บทเรียน 98
ใน terminal ที่ตั้ง DATABASE_URL ไว้แล้ว (บท 98) · ใช้เวลาราว 10 นาที
- ทายก่อน: model ชื่อ
Categoryจะได้ตารางชื่ออะไร · เช็คด้วยbin/rails consoleแล้วพิมพ์"Category".tableize(ออกด้วยexit) - สร้าง model:
bin/rails generate model Category name:string· ดูว่าได้ไฟล์อะไรบ้าง แล้วเปิดapp/models/category.rbดู - สร้างไฟล์
try_category.rbที่โฟลเดอร์บนสุดของโปรเจกต์:
แล้วรันcategory = Category.new(name: "อาหารเหนือ") puts category.namebin/rails runner try_category.rbก่อน migrate · จด error ที่เห็น - รัน
bin/rails db:migrateแล้วรันbin/rails runner try_category.rbอีกรอบ - เก็บกวาด (บทความจริงของ Day 2 จะได้ไม่ปน):
bin/rails db:rollback→bin/rails destroy model Category→ ลบtry_category.rb
ข้อ 1 — ได้ => "categories" (y เปลี่ยนเป็น ies)
ข้อ 2 — ผลจริง (เลขวันเวลาของคุณจะต่าง):
$ bin/rails generate model Category name:string
invoke active_record
create db/migrate/20261103141437_create_categories.rb
create app/models/category.rb
invoke test_unit
create test/models/category_test.rb
create test/fixtures/categories.yml
$ cat app/models/category.rb
class Category < ApplicationRecord
end
ไฟล์ model เอกพจน์ (category.rb) · migration กับ fixture เป็นพหูพจน์ (categories) ตรงกฎทุกตัว · ข้างใน class ว่างเหมือน Post
ข้อ 3 — ก่อน migrate ได้ error นี้ (ตัด path ยาวข้างหน้าออก):
…: PG::UndefinedTable: ERROR: relation "categories" does not exist (ActiveRecord::StatementInvalid)
อ่านแค่ท่อนกลางพอ (PG::UndefinedTable กับ ActiveRecord::StatementInvalid เป็นแค่ชื่อชนิดของ error) ·
relation "categories" does not exist = “ลิ้นชัก categories ยังไม่มีในตู้” · มีใบสั่งช่าง (migration) แล้ว แต่ยังไม่ได้สั่งช่างลงมือ
ข้อ 4 — ผลจริง:
$ bin/rails db:migrate
== 20261103141437 CreateCategories: migrating =================================
-- create_table(:categories)
-> 0.0029s
== 20261103141437 CreateCategories: migrated (0.0029s) ========================
$ bin/rails runner try_category.rb
อาหารเหนือ
ข้อ 5 — ผลจริง:
$ bin/rails db:rollback
== 20261103141437 CreateCategories: reverting =================================
-- drop_table(:categories)
-> 0.0038s
== 20261103141437 CreateCategories: reverted (0.0063s) ========================
$ bin/rails destroy model Category
invoke active_record
remove db/migrate/20261103141437_create_categories.rb
remove app/models/category.rb
invoke test_unit
remove test/models/category_test.rb
remove test/fixtures/categories.yml
db:rollback = ย้อน migration ล่าสุดหนึ่งตัว (รื้อลิ้นชักที่เพิ่งต่อ) · destroy = ลบไฟล์ชุดเดียวกับที่ generate สร้าง · ต้อง rollback ก่อน destroy ไม่งั้นตารางค้างอยู่ใน database แต่ไฟล์ใบสั่งหายไปแล้ว
ถ้าเจอ error นี้ แปลว่า…
relation "categories" does not exist→ ยังไม่ได้bin/rails db:migrate(ข้อ 3 ตั้งใจให้เจอ)ActiveRecord::DatabaseConnectionError: There is an issue connecting with your hostname: 127.0.0.1.→ Rails ต่อ database ไม่ได้ · Postgres ยังไม่เปิด ·DATABASE_URLชี้ผิดพอร์ต · หรือเปิด terminal หน้าต่างใหม่แล้วลืมexport DATABASE_URL=…อีกรอบ · ย้อนดูวิธีตั้งใน บทเรียน 98uninitialized constant Category→ Rails หาไฟล์app/models/category.rbไม่เจอ · generate ไม่สำเร็จ หรือพิมพ์ชื่อผิด (เช่นCatagory)
ลองตอบดู
model ชื่อ Category — Rails จะไปหาตารางใน database ชื่ออะไร?
ตัวเลือกที่ 3 ถูก · ผลจริงของ "Category".tableize คือ "categories"
ตัวเลือกที่ 1 ผิด เพราะตารางเก็บของหลายชิ้นจึงเป็นพหูพจน์เสมอ ·
ตัวเลือกที่ 2 ผิด เพราะ Rails มีกฎ y → ies ไม่ได้เติม s อย่างเดียว ·
ตัวเลือกที่ 4 ผิด เพราะชื่อตารางเป็นตัวเล็กทั้งหมด ตัวใหญ่ใช้กับชื่อ class เท่านั้น
จุดที่ต้องจำ: ไม่แน่ใจเมื่อไหร่ สั่ง "ชื่อ".tableize ใน console ดู ไม่ต้องเดา
app/models/post.rb มีแค่ class Post < ApplicationRecord กับ end แต่ post.title ใช้ได้ เพราะอะไร?
ตัวเลือกที่ 2 ถูก · ช่อง title มาจาก migration ที่สร้างตาราง posts แล้ว Rails อ่านตารางตอนรัน
ตัวเลือกที่ 1 ผิด เพราะ Rails ไม่เดาช่อง — post.author ได้ NoMethodError ทันทีเพราะตารางไม่มีช่องนั้น ·
ตัวเลือกที่ 3 ผิด เพราะไฟล์ที่ generator สร้างมีแค่ 4 ไฟล์ที่เห็นในผลรัน ไม่มีไฟล์ซ่อน ·
ตัวเลือกที่ 4 ผิด เพราะ ApplicationRecord ไม่มีตารางของตัวเองเลย (table_name เป็น nil) จึงไม่รู้จักช่อง title
จุดที่ต้องจำ: อยากรู้ว่า model มีช่องอะไร อย่าเปิดไฟล์ model — ดูที่ migration หรือตารางใน database
ใน controller เขียน Post.new ได้เลย ไม่ต้อง import อะไร เพราะอะไร?
ตัวเลือกที่ 4 ถูก · นี่คือ autoload · กล่องทายผลเรื่องเปลี่ยนชื่อไฟล์เป็น article.rb พิสูจน์แล้วว่า Rails หาจากชื่อไฟล์
ตัวเลือกที่ 1 ผิด เพราะ Rails หาเฉพาะในโฟลเดอร์ของแอป ไม่ใช่ทั้งเครื่อง ·
ตัวเลือกที่ 2 ผิด เพราะโค้ดแยกไฟล์จริง ๆ แค่ Rails เป็นคนเปิดไฟล์ให้ ·
ตัวเลือกที่ 3 ผิด เพราะ post.rb ที่ generator สร้างมีแค่สองบรรทัด ไม่มี import หรือ require เลย
จุดที่ต้องจำ: ชื่อ class กับชื่อไฟล์ต้องตรงกัน (Post ↔ post.rb) autoload ถึงจะทำงาน
route เขียน to: "posts#index" · เปิด /posts แล้วได้ uninitialized constant PostsController · ในโปรเจกต์มี post_controller.rb ที่ประกาศ class PostController · แก้ยังไง?
ตัวเลือกที่ 1 ถูก · ต้องแก้ทั้งชื่อไฟล์และชื่อ class ให้เป็นพหูพจน์ตามที่ route ขอ · ในบทนี้แก้แบบนี้แล้วได้ HTTP 200
ตัวเลือกที่ 2 ผิด เพราะต่อให้โหลดไฟล์เข้ามา ข้างในก็ยังมีแต่ PostController ไม่มี PostsController อยู่ดี ·
ตัวเลือกที่ 3 ผิด เพราะ db:migrate ยุ่งแค่กับตารางใน database ไม่สร้าง controller ·
ตัวเลือกที่ 4 ผิด เพราะคำสั่งนั้นสร้าง model (ไม่ใช่ controller) และปัญหาไม่ได้อยู่ที่ model เลย
จุดที่ต้องจำ: route · ชื่อไฟล์ · ชื่อ class ต้องตรงกันทั้งสามที่
✅ ถ้าตอนนี้คุณตอบได้ว่า “posts คือชื่อตาราง/URL ของบทความหลายชิ้น ส่วน Post คือแม่พิมพ์ของบทความหนึ่งชิ้น
และ Rails หาไฟล์ post.rb เองจากชื่อ แล้วอ่านช่องจากตาราง” — บทนี้ผ่านแล้ว
อยากรู้ว่าชื่อที่คุณจะใช้ในแอปตัวเองจะถูกแปลงเป็นอะไร เปิด bin/rails console แล้วพิมพ์
"ชื่อ".tableize ดูได้เลย ไม่ต้องเดา
ถ้าเปิดสไลด์ดูประกอบ
สไลด์หน้า 46–48 สอนเรื่องเดียวกับบทนี้ · จุดที่ของจริงไม่ตรงกับสไลด์:
| หน้า | สไลด์เขียน | ของจริง (Rails 8.1.4) | ต้องสนใจไหม |
|---|---|---|---|
| 46 | ตาราง 6 แถว Post → posts … Sheep → sheep และกล่อง “ลองเองใน console” | ตรงกับผลรันจริงทุกช่อง | ไม่ต้อง — เชื่อได้ |
| 47 | ApplicationController ว่างเปล่า | มีสองบรรทัด allow_browser … และ stale_when_importmap_changes | นิดเดียว — เห็นในไฟล์จริงแล้วไม่ต้องลบ |
| 48 | กล่อง bin/rails -T มี routes middleware stats notes | bin/rails -T ไม่มีสี่ตัวนี้ (มีแต่ task อย่าง db:migrate) · สี่ตัวนี้อยู่ใน bin/rails --help · แต่สั่ง bin/rails routes ตรง ๆ ได้ปกติ | นิดเดียว — หาใน -T ไม่เจอไม่ต้องตกใจ |
| 48 | bin/rails about: Rails 8.1.3 · Ruby 4.0.6 · RubyGems 3.6.2 · Rack 3.1.8 | เครื่องที่ตรวจ: 8.1.4 · 4.0.9 · 4.0.20 · 3.2.8 | ไม่ต้อง — เลขขึ้นกับเครื่องและโปรเจกต์ |
| 48 | Database schema version 20260215093000 | แอปที่เพิ่ง rails new ได้ 0 (ยังไม่มี migration) · พอสร้าง Post แล้วได้เลขตามชื่อไฟล์ migration | ไม่ต้อง |
อ้างอิง
- Rails Guides — Active Record Basics · Naming Conventions —
อ่านตารางคู่ชื่อ model ↔ ตาราง (มี
Person→peopleเหมือนสไลด์) · ถ้าเจอฐานข้อมูลเก่าที่ตั้งชื่อตารางไม่ตามกฎ ให้อ่านหัวข้อ Overriding the Naming Conventions ถัดไป - Rails API — ActiveSupport::Inflector —
ดูหัวข้อ
pluralize·singularize·tableize·classifyแต่ละอันมีตัวอย่างสั้น ๆ ที่ลองพิมพ์ตามใน console ได้ทันที - Rails Guides — Autoloading and Reloading Constants · Project Structure —
อ่านแค่หัวข้อ Project Structure ที่อธิบายกฎ “ชื่อไฟล์ ↔ ชื่อ class” (
posts_controller.rb↔PostsController) · หัวข้ออื่นในหน้านี้ลึกเกินสำหรับตอนนี้ - Rails Guides — The Rails Command Line · Generating Models —
อ่านหัวข้อ Generating Models แล้วเลื่อนไปที่ Undoing Code Generation with bin/rails destroy
ซึ่งคือข้อ 5 ของภารกิจ · ในหน้าเดียวกันมีหัวข้อ
bin/rails aboutและbin/rails routesด้วย - Go — How to Write Go Code · Importing packages from your module —
ฝั่ง Go ให้ทวนว่าการ
importpackage ในโปรเจกต์ตัวเองหน้าตาเป็นยังไง เทียบกับ autoload ของ Rails ที่ไม่ต้องเขียนเลย