Ruby on Rails :: Blog Frontend
Posted by PunNeng, Sat Jul 29 21:43:00 UTC 2006
หลังจากที่ลองการ validate กันง่ายๆ ไปแล้ว คราวนี้ลองจะมาทำฝั่ง front-end กันบ้าง
คร่าวๆ คือ จะทำเป็น lists ในหน้า index แล้วพอคลิกที่หัว จะทำเข้าไปดูข้างในนั้น แล้วก็จะมีที่แสดง comment ด้วย เริ่มกันเลย สร้าง controller ที่ชื่อว่า post ก่อน
simple_blog>./script/generate controller post
จากนั้นลองไปที่ http://localhost:3000/post ดูนะ มันควรจะขึ้นว่า

ที่มันขึ้น error เตือนแบบนี้ เพราะว่ามันยังหา controller และ action ไม่เจอ เราต้องไปสร้างมันก่อน ไปที่ app/controllers/post_controller.rb
1 2 3 | def index @post_pages, @posts = paginate :posts, :per_page => 5 end |
ส่วนนี้มันจะไปทำการ query ในตาราง post มาทั้งหมด โดยแยกเป็น 5 ชิ้นต่อหน้า โดยสิ่งต่างๆ ของเรื่องหน้า จะใส่ไปใน @post_pages และที่มัน query ขึ้นมาในฐานข้อมูลทั้งหมด จะเก็บไว้ใน @posts ซึ่ง Ruby สามารถทำการ return parameter ได้หลายๆ ตัว
แต่ว่า ในฝั่ง views เราเองยังไม่ได้สร้าง file ให้มันแสดงผลเลย ไปที่ app/views/post จากนั้นก็สร้าง index.rhtml ขึ้นมา โดยที่ข้างในใส่ว่า
1 2 3 4 5 6 7 8 9 10 11 12 13 | <% for post in @posts %> <h1><%= link_to post.title, :action => "show", :id => post %></h1> <p><%= truncate(post.body, 200)%></p> <% end %> <%= if @post_pages.current.previous link_to("Previous page", { :page => @post_pages.current.previous }) end %> <%= pagination_links @post_pages %> <%= if @post_pages.current.next link_to("Next page", { :page => @post_pages.current.next }) end %> |
ตรงนี้ เราเขียนไว้ว่า ให้มันสามารถ link ไปดูในแต่ละ post ได้ แล้วก็แสดงรายละเอียดตรงส่วน body อีก 200 ตัวอักษร โดยวนเก็บมาจาก @posts ทีละตัว ซึ่ง @posts ตอนนี้ เป็น array อยู่ หลังจากนั้น ลองเข้าในที่ http://localhost:3000/post ดูอีกที
คราวนี้ เรายังขาดอีกหนึ่งอันคือ ทำให้มัน click เข้าไปดูในแต่ละ post ได้ กลับไปที่ post_controller.rb แล้วเพิ่มตัวนี้เข้าไป
1 2 3 4 5 6 7 8 9 | def show unless params[:id].nil? @post = Post.find params[:id] session[:post] = @post else flash[:notice] = "There is no post for id: #{params[:id]}" redirect_to :action => 'index' end end |
ถ้า id ไม่เป็น nil มันจะทำการ query ตาม id ที่เราส่งไป ซึ่งมาจาก link_to นั่นแหละ แล้วทำการจัดเก็บไว้ใน session (สำหรับเรื่อง session มันเป็นเรื่องยาวนิดนึง ไว้คราวหลังจะมาต่อเรื่องนี้กันครับ) ซึง format ง่ายๆ ก็ประกาศอย่างที่ผมทำแหละครับ แต่ถ้าไม่มี params[:id] เราจะสั่งให้มันเก็บค่าข้อความเตือนไว้ใน flash[:notice] แล้วตอนที่เรา redirect ไปยัง index มันจะทำการแสดง flash[:notice] ซึ่งเราจะไปเพิ่ม code กันทีหลัง ซึ่งการสั่ง redirect เราจะใช้ 'redirect_to' แล้วตามด้วย action ถ้าจะเปลี่ยน controller ก็เพิ่มเข้าไปด้วย สำหรับ String ใน Ruby เราจะใช้ " " และถ้าต้องการใส่ตัแปร จะเพิ่ม #{your_variable} เข้าไป อ้อ เกือบลืม สำหรับ unless ทำหน้าที่เหมือน if not หมายความง่ายๆ ว่า 'ถ้าไม่' แล้วใน Ruby จะใช้ nil แทน null ลองสังเกตหลัง nil ดูจะมี'.?' ซึ่งหมายความว่า 'หรือไม่' การสร้าง method ใน Ruby เราสามารถใช้สัญลักษณ์พวกนี้ได้ครับ อีกหน่อยคงจะมีสัญลักษณ์แปลกๆ มาให้เห็นอีกครับ ส่วนตัว 'params[:id]' ก็เป็นตัวแปรที่เราส่งมาจากหน้า html แหละครับ
อ่อ อีกนิด ทุกๆ อย่างที่มีการกระทำ เช่น operator ตัว Ruby จะทำทุกอย่างให้เป็น method เช่น '+' ในความเป็นจริงแล้วมันคือ method อันหนึ่ง ไว้ว่างๆ จะมาเล่าถึงเรื่องนี้กันต่อครับ
ทีนี้ มาใส่เพิ่มที่ฝั่ง views บ้าง เพราะยังไม่ได้ให้มันแสดงอะไรเลย ให้สร้าง app/views/post/show.rhtml แล้วใส่เพิ่มว่า
1 2 | <h1><%= @post.title %> <p><%= @post.body %></p> |
จะได้ดังนี้

แต่มันยังขาด comment อยู่นี่นะ งั้นคงต้องสร้างเพิ่ม ไปใส่ในฐานข้อมูลเลย
1 2 3 4 5 6 7 | CREATE TABLE `comments` ( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , `comment` TEXT NOT NULL , `created_by` VARCHAR( 64 ) NOT NULL , `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , `post_id` INT NOT NULL ) |
แล้วก็มาสร้าง model ต่อ
simple_blog>./script/generate model comment
มานั่งออกแบบกันนิดนึง ผมจะให้ 1 post ของผม มีหลาย comment นะ ความสัมพันธ์จะเป็นแบบ one to many ผมจะไปเพิ่ม code ใน app/models/post.rb ว่า
1 2 3 4 5 | class Post < ActiveRecord::Base has_many :comments validates_presence_of :title, :body validates_uniqueness_of :title end |
แล้วเปิดแก้ไขเลยที่ app/models/comment.rb
1 2 3 4 | class Comment < ActiveRecord::Base belongs_to :post validates_presence_of :name, :comment end |
สังเกตได้ง่ายๆ เพราะมันตั้งชื่อได้สื่อที่สุด ในเรื่องความสัมพันธ์ ถ้าเป็นแบบอื่น เช่น 1 to 1 ตัวที่จำเป็นจะต้องมีเพียง 1 จะใส่ code ว่า 'has_one :model' แต่คราวนี้มันมีเพียง 1 เลยเป็นแบบ singular หรือเอกพจน์นั่นเอง แต่ของเราเป็น has_many มันเลยต้องทำให้เป็นพหูพจน์ ในกรณีที่เป็น many to many ก็จะใช้คำว่า has_and_belongs_to_many ทั้งสอง model และชื่อ model ต้องเป็นพหูพจน์ด้วย แต่จุดต่างคือ ต้องเพิ่มตารางเป็นตัวเก็บ index ไว้ด้วยอีกอันนึง โดยตั้งชื่อ table ว่า models1_models ให้ตั้งตามลำดับตัวอักษร เช่น เรามี post กับ comment ถ้ามันมีความสัมพันธ์กันเป็นแบบ many to many เราจะต้องสร้างตารางเพิ่มขึ้นชื่อว่า comments_posts ให้ตั้งเป็นพหูพจน์ด้วย และเรียงตามลำดับตัวอักษร ตัว c มาก่อนตัว p ตารางนี้ก็เก็บ id ของทั้งสองตารางเอาไว้ คือ post_id และ comment_id แต่เราโชคดีไปที่คราวนี้เป็นแบบ one to many ซึ่งตัวนี้จะมี foriegn key เป็น post_id1 จริงๆ ล้วมันยังมีรายละเอียดอีกเยอะ สัมหรับเรื่องความสัมพันธ์ แต่เอาไว้ก่อนละกัน เดี๋ยวจะไม่จบ อ่อ ยังมี validation ด้วย ตัวนี้ เคยเห็นกันไปแล้วเมื่อครั้งที่แล้ว ต่อไป เราก็จะมาสร้างส่วนที่จัดการสำหรับ comment กัน ผมจะสร้างใน post controller ละกัน เพิ่ม method แบบนี้ใน post_controller.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def new_comment if request.post? post = session[:post] session[:post] = nil comment = Comment.new(params[:comment]) post.comments << comment if post.save flash[:notice] = "Your comment has been saved." else flash[:notice] = "Failed" end redirect_to :action => 'show', :id => post else redirect_to :action => 'index' end end |
หลังจากที่มันเข้ามาแล้วมันจะทำการตรวจสอบว่า method เป็น post หรือเปล่า ซึ่งเราจะไปกำหนดให้เป็น post ในหน้า form อีกที ถ้าไม่เป็น มันจะถูกสั่งให้ไปหน้า index แต่ถ้าเป็น ก็ทำการเอาค่าจาก session ออกมา แล้วสร้าง comment ตามที่เราส่งมา ซึ่งจริงๆ ตัว params จะเป็น hash ตรงนี้ขอเก็บไว้ก่อน จะมาอธิบายอีกทีหลังจากที่แสดงฝั่ง views แล้ว เพราะมันมีอะไรสัมพันธ์กันอยู่ จากนั้นก็ทำการยัด comment ที่เพิ่งสร้างมา เข้าไปใน post.comments ตรงนี้ละครับ เป็นตัวจัดการความสัมพันธ์ให้ โดยมันจะเชื่อมของมันเอง ในกรณีที่เป็น 1 to many ตัวนี้จะเป็น post.comment ไม่มี s นะครับ แล้วทำการ save ก็เป็นอันเสร็จสิ้น แต่ถ้ามัน save ไม่ได้ มันก็จะไม่เอาอะไรลงในฐานข้อมูล แล้วขึ้นเตือนว่า Failed จากนั้นก็จะ redirect ไปยังหน้าเดิม คือหน้า show
มาดูบ้างว่าฝั่ง views เราจะแสดงอะไรบ้าง ให้ย้อนกลับไปทีหน้า show.rhtml ใหม่อีกที แล้วแก้ไข code ตามนี้
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <%= error_messages_for 'comment' %> <h1><%= @post.title %></h1> <p><%= @post.body %></p> <div id="comment"> <h2>Comments:</h2> <% if @post.comments.size != 0 %> <% for comment in @post.comments %> <strong><%= comment.created_by %> sent at <%= comment.created_at.strftime("%a %b, %d %Y %H:%M:%Y") %></strong> <p><%= comment.comment %></p> <% end %> <% end %> <%= start_form_tag :action => "new_comment" %> <p><label for="comment_name">Name:</label></p><br /> <%= text_field 'comment', 'created_by' %></p> <p><label for="comment_comment">Comment:</label></p><br /> <%= text_area 'comment', 'comment' %> <p><%= submit_tag 'Send' %></p> <% end_form_tag %> </div> |
มาดูที่บรรทัดแรกก่อน จะเป็นตัวแสดง error messages ที่จะถูกส่งมาตอนที่เรา validation มัน ยังไม่ใช่ flash[:notice] นะ จากนั้นก็แสดงผล ตามด้วยเงื่อนไขว่า ถ้าเกิด size เป็น 0 ก็ไม่แสดงอะไร ถ้าหากไม่เป็น 0 มันก็จะทำการ loop เอา comment แต่ละตัวมาแสดง จากนั้นก็สร้าง form การส่ง comment ลองสังเกตดูตรง text_field และ text_area นะ parameters สองตัวแรกนี่แหละ จะถูกเก็บเป็น hash เอาไว้ซึ่งในฝั่ง controller จะเห็น hash ในลักษณะ :comment => {:created_by => 'value in text field', :comment => 'value in text_area'} ตามนี้ ตอนทีเราไป new มัน มันจะทำการจับคู่ให้อัติโนมัติตเลย น่าจะได้ดังนี้

ฮึ่ยยย ยาวแฮะ คราวหน้ามาต่อกันเรื่อง layout กันหน่อยครับ
1ไม่รู้ว่าโพสเก่าๆ ผมเคยบอกไปหรือยัง แต่ไม่เป็นไร บอกซ้ำอีกทีก็ได้ สำหรับเรื่องความสัมพันธ์บน MyISAM ของ MySQL มันยังคงมีปัญหา ผมตัดปัญหาเรื่องนี้ด้วยการใช้ความสัมพันธ์ที่สร้างขึ้นจาก ActiveRecord แทน ง่ายกว่าเยอะ
ปล. ตัวอย่างนี้จะใช้งานไม่ได้ใน rails 2.0 ไว้จะมาเขียนอีกทีครับ ว่าทำไม ??
แก้ไขล่าสุด วันที่ 9 กรกฏาคม 2550 เวลา 23.39 น.
