418342 ภาคปลาย 2553/การบ้าน 3
ให้ไว้เมื่อวันที่ 17 กุมภาพันธ์ 2554 ส่งวันพฤหัสบดีที่ 17 มีนาคม 2554 เวลา 23.59 น. (เลื่อนจากเดิมคือวันพุธที่ 16 มีนาคม 2554)
ในการบ้านนี้เราจะทำการสร้างระบบบล็อกเบื้องต้นที่มีฟีเจอร์ดังต่อไปนี้
- มีหน้าหลักแสดงโพสต์ล่าสุด
- มีระบบให้ผู้อ่านมาพิมพ์คอมเมนต์
- ผู้เขียนสามารถตั้งค่าต่างๆ ของบล็อก เช่น ชื่อบล็อก, จำนวนโพสต์ที่แสดงในแต่ละหน้า, ฯลฯ
- ผู้เขียนสามารถแท็กบทความต่างๆ ได้
เนื้อหา
เริ่มต้น
- เนื้อหาของการบ้านนี้เขียนด้วย Rails 2.3.4 ดังนั้น ก่อนรันจะต้องลงมันโดยสั่ง
gem install rails -v 2.3.4
- ให้ดาวน์โหลดโค้ดเริ่มต้นจาก http://theory.cpe.ku.ac.th/~pramook/418342/homework-03/homework-03-starter.zip ในโค้ดเริ่มต้นนี้จะมี scaffold ซึ่งสร้างตามฐานข้อมูลที่เขียนไว้ข้างล่าง พร้อมกับข้อมูลทดสอบส่วนหนึ่งอยู่
- ให้ลงไลบรารี Maruku ซึ่งเป็นตัวแปรภาษา Makrdown ที่เราจะใช้ในการเขียนเนื้อหาของบล็อก ด้วยการสั่ง
gem install maruku
- Rails 2.x มีการสั่งรัน server ที่แตกต่างไปจาก Rails 3 กล่าวคือ เราต้องสั่งด้วยคำสั่ง
ruby script/server
แทนที่จะสั่ง rails server ตามเดิม
ฐานข้อมูล
ฐานข้อมูลของระบบบล็อกจะประกอบด้วยตารางต่อไปนี้
ตาราง posts
เก็บโพสต์ของเจ้าของบล็อก มีฟีลด์ดังต่อไปนี้
- title เป็น string
- content เป็น text ซึ่งเขียนด้วยภาษา Markdown
- published_at เป็น datetime ซึ่งจะมีค่าเป็น nil ถ้าหากผู้ใช้ยังไม่ได้ตีพิมพ์โพสต์นี้
- timestamp (created_at และ modified_at) ซึ่งสร้างด้วย t.timestamp ใน migration
ตาราง comments
เก็บคอมเมนต์ที่มีคนมาเขียน มีฟีลด์ดังต่อไปนี้
- name เป็น string เก็บชื่อคนเขียน comment
- email เป็น string เก็บ email ของคนเขียน comment
- url เป็น string เก็บ URL ของเว็บไซต์ของคนเขียน comment
- content เป็น text ซึ่งเขียนด้วยภาษา Markdown เช่นกัน
- post_id เป็น integer ซึ่งเก็บ id ของ post ที่ comment
- timestamp (created_at และ modified_at) ซึ่งสร้างด้วย t.timestamp ใน migration
ตาราง tags
เก็็บ tag ต่างๆ มีฟีลด์ดังต่อไปนี้
- name เป็น string ซึ่งเป็นชื่อของ tag
ตาราง posts_tags
ใช้ในการทำความสัมพันธ์แบบ many-to-many ระหว่าง post กับ tag มีฟีลด์ดังต่อไปนี้
- post_id
- tag_id
ตาราง settings
เก็บค่าต่างๆ ที่ผู้ใช้สามารถกำหนดให้บล็อกได้ เช่น ชื่อบล็อก ฯลฯ มีฟีลด์ดังต่อไปนี้
- name เป็น string ซึ่งเป็นชื่อของค่าที่ผู้ใช้กำหนดได้
- value เป็น string ซึ่งเก็บค่าจริง
ในตาราง settings จะมีค่าต่อไปนี้อยู่
- blog_name = ชื่อของบล็อก (จะปรากฏอยู่ที่หัวเว็บเพจ)
- blog_subtitle = คำอธิบายบล็อกสั้นๆ (จะปรากฏอยู่ที่หัวเว็บเพจเช่นกัน)
- blog_url = URL ของบล็อกซึ่งมีไว้ใช้ทำ RSS feed
- posts_per_page = จำนวนบล็อกในแต่ละหน้า
- comment_count = จำนวนคอมเมนต์เริ่มต้นที่แสดงเวลาดูโพสต์
การบ้านข้อ 1: แก้ไขโมเดล
แก้ไข Post
- ทำให้ Post มีความสัมพันธ์แบบ many-to-many กับ Tag
- ทำให้ Post มีความสัมพันธ์แบบ many-to-one กับ Comment
- เขียน validation ทำให้ Post สอดคล้องกับเงื่อนไขต่อไปนี้
- title จะต้องไม่ว่างเปล่า
- content จะต้องไม่ว่างเปล่า
- เขียนเมธอดชื่อ published? เพื่อเช็คว่าผู้ใช้ได้ตีพิมพ์โพสต์ไปแล้วหรือยัง โดย method จะเช็คว่า published_at มีค่าเป็น nil หรือไม่ ถ้าใช่แสดงว่ายังไม่ได้ตีพิมพ์
เมื่อคุณแก้โค้ดตามนี้แล้ว ให้ลองรัน
rake test:units TEST=unit/test/post_test.rb
เพื่อตรวจสอบว่าได้แก้ไขไปถูกต้องแล้วหรือไม่
แก้ไข Comment
เขียน validation ทำให้ Comment สอดคล้องกับเงื่อนไขต่อไปนี้
- name จะต้องไม่ว่างเปล่า
- email จะต้องมีรูปแบบเหมือน email (ใบ้: ใช้ validate_format_of)
- url จะต้องมีรูปแบบเหมือน URL จริงๆ (คือ http://something.someone.org/abc/def) (ใบ้: ไปหา regular expression มาจากใน net)
- content จะต้องไม่ว่างเปล่า
เมื่อคุณแก้โค้ดตามนี้แล้ว ให้ลองรัน
rake test:units TEST=unit/test/comment_test.rb
เพื่อตรวจสอบว่าได้แก้ไขไปถูกต้องแล้วหรือไม่
แก้ไข Tag
เขียน validation
ทำให้ Tag ที่มีรูปแบบถูกต้องทุกตัวสอดคล้องกับเงื่อนไขต่อไปนี้
- name จะต้องไม่ว่างเปล่า
- ใน name จะต้องไม่มีเครื่องหมายคอมมา (,) อยู่ภายใน (เนื่องจากเราใจใช้เครื่องหมายนี้ในการแยก tag ต่างๆ ออกจากกัน) กล่าวคือ "space adventure" ใช้ได้ แต่ "space,adventure" หรือ "space, adventure" ใช้ไม่ได้
ทำให้ชื่อ Tag อยู่ในรูป "ปกติ"
เราต้องการให้เวลาเจ้าของบล็อกใส่ tag ให้โพสต์ แล้วเขาสามารถพิมพ์ "life, space adventure, computer security " แล้วระบบสามารถที่จะแยกมันออกเป็น 3 tag ได้แก่ "life", "space adventure", และ "computer security" ให้โดยอัตโนมัติ
เนื่องจากเราบอกว่าชื่อแท็กจะต้องไม่มีเครื่องหมายคอมมาอยู่ข้างใน ดังนั้นเราสามารถแยก string ที่ผู้ใช้ให้มาข้างบนเป็นสามส่วนคือ "life" และ " space adventure" และ " computer security "
เวลาเราเก็บชื่อของ Tag ใน database เราต้องการกำจัดข้อมูลที่ไม่สำคัญออกจากชื่อ Tag ที่ผู้ใช้ป้อนมาไป ยกตัวอย่างเช่น ผู้ใช้ป้อน " computer security " ซึ่งมีช่องว่างอยู่ข้างหน้าชื่อ ข้างหลังชื่อ และช่องว่างระหว่างคำว่า computer กับคำว่า security มีอยู่ถึงสามตัว แต่ในเวลาเก็บข้อมูลเราต้องการให้ชื่อมีค่าเท่ากับ "computer security" เท่านั้น กล่าวคือ
- จะต้องไม่มีช่องว่างนำหน้าชื่อ Tag
- จะต้องไม่มีช่องว่างตามหลังชื่อ Tag
- ระหว่างคำสองคำที่อยู่ข้างๆ กันในชื่อ Tag จะมีช่องว่างคั่นได้เพียงช่องเดียวเท่านั้น
เราเรียกชื่อที่สอดคล้องกับกฎทั้งสามข้อข้างต้นว่าเป็นชื่อ Tag ในรูป "ปกติ"
ตรวจสอบความถูกต้อง
จงเพิ่มคลาสเมธอด normalize_name(given_name) ในคลาส Tag โดยที่เมธอดนี้ืคืนค่าของ given_name เมื่อทำให้มันอยู่ในรูปปกติแล้ว
เวลาประกาศคลาสเมธอดให้ประกาศเช่นนี้ <geshi lang="rails"> class Tag
...
def Tag.normalize_name(given_name) ... end
...
end </geshi>
เมื่อคุณแก้โค้ดตามนี้แล้ว ให้ลองรัน
rake test:units TEST=unit/test/tag_test.rb
เพื่อตรวจสอบว่าได้แก้ไขไปถูกต้องแล้วหรือไม่
แก้ไข Setting
จงเขียน validation เพื่อทำให้ Setting ทุกตัวสอดคล้องกับเงื่อนไขต่อไปนี้
- name จะต้องไม่เป็นค่าว่าง
- value จะต้องไม่เป็นค่าว่าง
- ถ้า name มีค่าเท่ากับ "posts_per_page" หรือ "comment_count" แล้ว value จะต้องเป็นสตริงที่มีค่าเป็นตัวเลข
เมื่อคุณแก้โค้ดตามนี้แล้ว ให้ลองรัน
rake test:units TEST=unit/test/setting_test.rb
เพื่อตรวจสอบว่าได้แก้ไขไปถูกต้องแล้วหรือไม่
การบ้านข้อ 2: ทำให้ Markdown ใช้ได้
แก้ไข Post
ให้สร้างเมธอดชื่อ markdowned_content ซึ่งคืนค่า content ที่ถูกแปลด้วยภาษา Markdown แล้ว ในการแปลให้ใช้ไลบรารีชื่อ Maruku ซึ่งมีวิธีใช้บอกอยู่ในเอกสารนี้ http://maruku.rubyforge.org/usage.html
เมื่อคุณแก้โค้ดตามนี้แล้ว ให้ลองรัน
rake test:units TEST=unit/test/markdown_post_test.rb
เพื่อตรวจสอบว่าได้แก้ไขไปถูกต้องแล้วหรือไม่
แก้ไข Comment
สำหรับโมเดล Comment ก็ให้สร้างเมธอดชื่อ markdowned_content ซึ่งทำงานเช่นเดียวกันกับ markdowned_content ของ Post เช่นกัน
เมื่อคุณแก้โค้ดตามนี้แล้ว ให้ลองรัน
rake test:units TEST=unit/test/markdown_comment_test.rb
เพื่อตรวจสอบว่าได้แก้ไขไปถูกต้องแล้วหรือไม่
การบ้านข้อ 3: แก้ Post Controller และ View ที่เกี่ยวข้อง
แก้ show ของ PostsController
ให้เปลี่ยน app/views/posts/show.html.erb ให้เขียนโค้ด HTML ที่มีการแสดงข้อมูลต่อไปนี้
- หัวเรื่องของโพสต์
- เนื้อหาโพสต์
- วันที่เขียน
- แท็กต่างๆ ของโพสต์
- ลิงค์ Edit และ Back ที่ได้มาจาก scaffold
- จำนวนคอมเมนต์
- คอมเมนต์เรียงจากเก่าไปใหม่
- แบบฟอร์มสำหรับให้ผู้ใช้กรอกคอมเมนต์
จงทำให้แบบฟอร์มในการกรอกคอมเมนต์นี้ทำงานได้ถูกต้อง
แก้ไข index ของ PostsController
ให้เปลี่ยน app/views/posts/index.html.erb ให้เขียนโค้ด HTML ทีมีการแสดงข้อมูลดังนี้
- แสดงโพสต์ใหม่ล่าสุดเท่ากับจำนวน posts_per_page ที่เก็บไว้ใน Setting
- สำหรับแต่ละโพสต์ ให้มีการแสดง
- วันที่เขียน
- หัวเรื่อง ซึ่งมีลิงค์ไปยังหน้า show ของโพสต์นั้น
- เนื้อหาโพสต์
- แท็กต่างๆ ของโพสต์
- จำนวนคอมเมนต์ ซึ่งมีลิงค์ไปยังหน้า show ของโพสต์นั้น แต่เมื่อตามลิงค์ไปแล้วให้ไปอยู่ตรงที่ตำแหน่งเริ่มคอมเมนต์พอดี
- ลิงค์ Edit และ Delete ที่ได้มาจาก scaffold
ในการแสดงโพสต์ใหม่ล่าสุดนี้ คุณจะต้องแสดง
- โพสต์ที่ published_at มีค่าไม่เป็นค่าว่าง
- เรียงโพสต์ตาม published_at จากใหม่สุดไปยังเก่าสุด
- มีลิงค์ให้ผู้ใช้คลิกเพื่ออ่านโพสต์เก่าขึ้นหรือไม่ขึ้นที่อยู่ในหน้าถัดไป (เหมือนบล็อกทั่วๆ ไป)
เพิ่ม action "admin" ของ PostController
ให้เขียน action ชื่อ admin โดยที่เมื่อผู้ใช้เข้าไปยัง url /posts/admin แล้วจะเจอหน้าเว็บซึ่งมีลักษณะคล้ายๆ กับหน้า index ที่ scaffold สร้างมาให้ กล่าวคือ
- มีการแสดงโพสต์เป็นตาราง แทนที่จะเป็นมีแต่ละโพสต์ไปเหมือนกับหน้า index โดยที่แต่ละแถวของตารางจะตรงกับโพสต์หนึ่งโพสต์ และการเรียงโพสต์ให้เรียงตาม modified_at
- สำหรับตารางแต่ละแถว
- ไม่ต้องมีการแสดงเนื้อหาโพสต์
- มีการแสดงจำนวน comment
- มีการแสดง tag ทั้งหมดของโพสต์ พร้อมกับลิงค์ไปยังหน้าของ tag นั้น
- มีการแสดงเวลาที่ผู้ใช้ตีพิมพ์โพสต์
- มีลิงค์ไปยังหน้า show ของโพสต์นั้น
- มีลิงค์สำหรับลบโพสต์
- มีลิงค์สำหรับให้ผู้ใช้ซ่อนบทความที่ติพิมพ์ไปแล้ว (unpublish) เมื่อคลิกลิงค์นี้ มันจะทำให้ published_at ของโพสต์นั้นมีค่าเป็นค่าว่าง
แก้ไข edit ของ PostsController
ให้ทำการ
- ลบ input สำหรับให้กรอก published_at ออก
- เพิ่ม input ชนิด text สำหรับกรอก tag ต่างๆ กล่าวคือถ้าผู้ใช้กรอก "dog, sea otter, cat" แล้วส่งแบบฟอร์มมา ให้ระบบทำการตัดคำและทำให้โพสต์มี tag สามแท็กคือ dog และ sea otter และ cat
- ทำให้ปุ่ม submit ของฟอร์มมีอยู่สามปุ่ม ได้แก่
- "Save Draft" ซึ่งทำการส่งฟอร์มธรรมดา แต่ยังไม่ได้ทำให้โพสต์ที่เขียนถูกตีพิมพ์ เมื่อส่งฟอร์มสำเร็จให้ redirect ไปหน้า admin
- "Save and Continue Editing" ซึ่งทำการส่งฟอร์ม แต่ยังไม่ได้ทำให้โพสต์ที่เขียนถูกตีพิมพ์ เมื่อส่งฟอร์มสำเร็จแล้วให้กลับมายังหน้าเดิม
- "Publish" ซึ่งทำการส่งฟอร์ม และทำให้ค่า published_at ของโพสต์มีค่าเท่ากับเวลาปัจจุนัน เมื่อส่งฟอร์มสำเร็จแล้วให้ redirect ไปหน้า index
การบ้านข้อ 4: แก้ไข CommentsController และ View ที่เกี่ยวข้อง
แก้ไขหน้า index
- ทำหน้า index ให้มีการแสดงคอมเมนต์จากใหม่ไปยังเก่า
- สำหรับคอมเมนต์แต่ละคอมเมนต์ให้แสดง
- ชื่อและลิงค์ไปยังเว็บไซต์ของผู้เขียน
- เนื้อหาที่ถูกแปลด้วยภาษา Markdown แล้ว
- โพสต์ที่คอมเมนต์นี้ผูกอยู่
ในการแสดงนี้ ตอนแรกให้แสดงคอมเมนต์เท่ากับจำนวน comment_count ที่เก็บไว้ใน Setting และที่ส่วนล่างสุดของหน้าให้มีลิงค์ "Read more comments" ซึ่งเมื่อคลิกแล้วจะมีการส่ง Ajax request ไปเพื่อดึงคอมเมนต์อีกจำนวน comment_count ตัวมาแสดงต่อจากที่มีไปแล้วโดยไม่มีการ reload หน้าใหม่
การบ้านข้อ 5: แก้ไข TagsController และ View ที่เกี่ยวข้อง
แก้ไขหน้า show
ทำให้หน้า show มีการแสดงโพสต์ทั้งหมดทีี่ีมี tag นั้น โดยการแสดงผลจะมีลักษณะคล้ายกับหน้า index ของ Post คือจะแสดงเป็นโพสต์ไปพร้อมเนื้อหา การแสดงผลจะไม่อยู่ในรูปแบบตารางเหมือนกับที่ scaffold สร้างมาให้
ในการแสดงนี้ ตอนแรกให้แสดงโพสต์เท่ากับจำนวน posts_per_page ที่เก็บไว้ใน Setting และที่ส่วนล่างสุดของหน้าให้มีลิงค์ "Read more comments" ซึ่งเมื่อคลิกแล้วจะมีการส่ง Ajax request ไปเพื่อดึงคอมเมนต์อีกจำนวน posts_per_page ตัวมาแสดงต่อจากที่มีไปแล้วโดยไม่มีการ reload หน้าใหม่
ส่งงาน
หลังจากทำการพัฒนาเสร็จแล้ว ให้ rollback migration ทั้งหมด (ให้ database ว่าง) แล้ว zip ไดเรคทอรีที่ rails สร้างทั้งหมดส่งมาที่อีเมล์ของ อ.ประมุข (pramook at gmail dot com) และ อ.ชาคริต (chakrit dot w at gmail dot com)