เขียน blog บน Merb

posted on 28 Dec 2008 16:20 by wonam in softdev

ผมได้ทดลองใช้ merb เมื่อสองสามวันก่อน อย่างไรก็ตามหลังจากได้ทดลองหาตัวอย่างที่จะได้หัดตามจากอินเทอร์เน็ต กลับพบว่าตัวอย่างพวกนั้นเก่าเกินไปแล้ว หรือไม่ก็ไม่ได้ใช้ Datamapper เป็น ORM เหมือนที่ผมต้องการทดลองเล่น

แน่นอนว่าผมทราบข่าว Merb 2 = Rails 3 แล้ว และนี่ก็เป็นสาเหตุที่เอา Merb มาหัด เพราะว่าอยากเล่น Merb สักหน่อยก่อน จะได้ทราบว่าอะไรที่มันน่าจะน่าสนใจบ้าง

บทความนี้เอาขั้นตอนหลัก ๆ มาจากบทความ "merb + datamapper + noob: quick start" แต่ตัดบางอย่างออก และปรับการใช้งานให้ตรงกับ Merb เวอร์ชั่นที่ผมใช้อยู่ (1.0.6) นอกจากนี้ยังคาดว่าผู้อ่านน่าจะมีประสบการณ์การใช้ Rails มาบ้างครับ

ขั้นแรกคือติดตั้ง merb ถ้าเป็น ubuntu ก็ทำประมาณ

$ sudo gem install merb
แล้วก็ติดตั้ง Datamapper และส่วนติดต่อกับ Sqlite3
$ sudo gem install datamapper merb_datamapper do_sqlite3
จากนั้นก็สร้าง Merb application
$ merb-gen app blog
คำสั่ง merb-gen app จะสร้าง application ว่าง ๆ ขึ้นมา ที่ใช้ Datamapper เป็น ORM

ใน blog/ จะมีโครงสร้างของไดเร็กทอรีที่ผู้ใช้ Rails น่าจะคุ้นเคย ไฟล์ที่เกี่ยวข้องกับการตั้งค่าจะอยู่ในไดเร็กทอรี config/ ไฟล์ที่น่าสนใจก็มีเช่น dependencies.rb (ระบุว่าใช้โมดูลไหนบ้าง --- การถอดประกอบเป็นจุดเด่นของ merb), init.rb (ใช้ระบุว่าจะเลือกใช้อะไรเป็น ORM, เป็นระบบทดสอบ และระบบ template), และ database.yml (เช่นเดียวกับใน Rails ที่ระบุข้อมูลเกี่ยวกับฐานข้อมูลที่ใช้).

ในขั้นนี้ของที่ merb สร้างมาให้น่าจะโอเคแล้ว เราก็จะใช้ไปตามนั้นก่อน

สร้างโมเดล

ได้เวลามาสร้างโมเดลกันครับ สั่งคำสั่งด้านล่าง (ในไดเร็กทอรี blog/)

$ merb-gen model post
เพื่อสร้างโมเดล Post ครับ ซึ่งโปรแกรมของโมเดลดังกล่าวจะอยู่ที่ app/models/post.rb

ในการพัฒนาใน Rails เราจะเขียนเพียงแค่ migration จากนั้นข้อมูลเกี่ยวกับโมเดลจะถูกอ่านจากตารางในฐานข้อมูล แต่ในการพัฒนาด้วย Datamapper นั้นเราจะระบุข้อมูลของโมเดล เช่นพวก property ในคลาสเลย ดังนั้นแก้ไฟล์ post.rb ให้เป็น

class Post
  include DataMapper::Resource
  
  property :id, Serial
  property :title, String
  property :body, String
end
หลังจากนั้นเราจะต้องจัดการสร้างตารางของโมเดลในฐานข้อมูล ในกรณีที่เราปรับเปลี่ยน property ง่าย ๆ (เช่น สร้างโมเดลหรือเพิ่ม property) merb สามารถจะปรับตารางให้เราได้โดยไม่ต้องเขียน migration เลย โดยเราสามารถเรียก
$ rake db:autoupgrade
เพื่อปรับโครงสร้างตารางได้ นอกจากนี้ยังมีอีกคำสั่งที่สามารถใช้เพื่อปรับโครงสร้างตารางเช่นกัน แต่การปรับดังกล่าวจะเป็นการปรับแบบทำลาย (ข้อมูลเดิมหายหมด) คือการเรียก rake db:automigrate ดังนั้นผมจะใช้ db:autoupgrade แทน

เมื่อสร้างเสร็จแล้ว เราจะลองเล่นใน interactive console ของ merb ก่อน เพื่อใส่ post ลงไปหลาย ๆ อันให้เรามีข้อมูลแสดงนะครับ เรียก interactive console โดยสั่ง

$ merb -i
irb(main):001:0> p1 = Post.new
irb(main):002:0> p1.title = 'Hello'
irb(main):003:0> p1.body = 'This is my first post'
irb(main):004:0> p1.save

 ~ INSERT INTO "posts" ("title", "body") 
          VALUES ('Hello!', 'This is my first post')
=> true
irb(main):001:0> p2 = Post.new
irb(main):002:0> p2.title = 'Second post'
irb(main):003:0> p2.body = 'Here I post again'
irb(main):004:0> p2.save
 ~ INSERT INTO "posts" ("title", "body") 
          VALUES ('Second post', 'Here I post again')
=> true

สร้างคอนโทรเลอร์

ตอนนี้เราก็มีข้อมูลพอจะให้แสดงได้แล้ว ไปสร้างคอนโทรเลอร์กันครับ เรียกคำสั่ง

$ merb-gen controller posts
เพื่อสร้างคอนโทรเลอร์ Posts ในแฟ้ม app/controllers/posts.rb สังเกตว่าจะไม่มีคำว่า Controller ตามหลังชื่อเหมือนใน rails นะครับ

เข้าไปเพิ่มให้เมท็อด index ค้น post ทั้งหมดออกมาครับ

class Posts < Application
  def index
    @posts = Post.all
    render
  end  
end
สังเกตว่าใน merb ทุกครั้งที่ทำงานเสร็จเราจะต้องเรียกเมท็อด render ให้แสดง view เอง (ใน rails จะทำให้อัตโนมัติ) การเรียกดังกล่าวจะนำ view สำหรับเมท็อดนี้มาแสดง ข้อสังเกตอีกประการคือเราเรียก Post.all แทนที่จะเป็น Post.find(:all) เหมือนตอนที่เขียนด้วย Active Record

จากนั้นไปสร้าง view เพื่อแสดงผล post กันครับ ไปแก้ไฟล์ views/posts/index.html.erb ให้เป็นดังนี้ครับ

<h1>My posts</h1>
<% for post in @posts %>
  <h2><%= post.title %></h2>
  <p><%= post.body %></p>
<% end %>

เราพร้อมที่จะเห็นหน้าเว็บแล้วนะครับ เรียกเว็บบราวเซอร์ที่มากับ merb ด้วยคำสั่ง
$ merb
จากนั้นเข้า browser เปิดไปที่ http://localhost:4000/posts หรือ http://127.0.0.1:4000/posts ได้เลยครับ!

เพิ่ม post ใหม่

ไปสร้างหน้าสำหรับป้อน post ใหม่กันครับ เราจะเพิ่มลิงก์ "add new post" ที่ด้านล่างของหน้า index ครับ โดยเติม คำสั่งด้านล่างเข้าไปใน index.html.erb ครับ

<%= link_to 'add new post', url(:controller => :posts, 
                                :action => :new) %>
เมท็อด url จะคืนค่า url สำหรับ action นั้นออกมา เท่าที่ผมทราบเราสามารถทำอะไรกับการอ้างชื่อ action ได้มากมาย โดยใช้ router ของ merb แต่ตอนนี้เราจะทำในลักษณะเดียวกับใน rails ไปก่อน

จากนั้นไปสร้างเมท็อด new ในคอนโทรเลอร์ Posts ที่แสดง view สำหรับแสดงฟอร์มครับ

class Posts < Application
  # ...
  def new
    @post = Post.new
    render
  end
end
แล้วก็ไปสร้าง view new.html.erb ในไดเร็กทอรี app/views/posts:
<h1>Creating new post</h1>
<%= form_for @post, :action => 'create' do %>
  <%= text_field :title, :label => 'Title' %><br/>
  <%= text_area :body, :label => 'Body' %><br/>
  <%= submit 'New post' %>

<% end =%>
สังเกตว่าเราสั่ง form_for และเหล่าเมท็อด text_field และ text_area ก็จะสร้าง input tag ที่ขึ้นกับตัวแปรที่ระบุใน form_for เอง (ใน rails เราต้องระบุ block parameter)

สุดท้ายเราก็ไปเพิ่มเมท็อด create ลงไปในคอนโทรเลอร์ Posts เพื่อสร้าง post ใหม่ครับ

class Posts < Application
  # ...
  def create
    @post = Post.new params[:post]
    @post.save
    redirect url(:action => 'index')
  end
end
ลองทดลองดูนะครับว่าสามารถเพิ่ม post ได้หรือไม่ครับ

ระบบความเห็น

ระบบ blog คงไม่มีความหมายถ้าไม่สามารถใส่ comment ได้

เราจะสร้างโมเดล comment โดยเรียก

$ merb-gen model comment

แล้วแก้ไฟล์ app/models/comment.rb:
class Comment
  include DataMapper::Resource
  
  property :id, Serial
  property :body, String
end

จากนั้นเราต้องไประบุในโมเดล Post ถึงการเชื่อมโยงกับโมเดล Comment เราทำโดยระบุ has ลงไปในไฟล์ app/models/post.rb


class Post
  # ...
  has n, :comments
end
การสั่งดังกล่าวจะมีลักษณะคล้าย ๆ กับสั่ง has_many ใน Active Record ถ้าต้องการความสัมพันธ์แบบ one-to-one สามารถสั่ง has 1, :comment แทนได้ครับ

จากนั้นไปปรับ view โดยแก้ app/views/posts/index.html.erb ให้แสดง comment และมี comment form ครับ

<h1>My posts</h1>

<% for post in @posts %>
  <h2><%= post.title %></h2>
  <p><%= post.body %></p>
  <ul>
    <% post.comments.each do |comment| %>
      <li><%= comment.body %></li>
    <% end %>
    <li>
      <%= form_for Comment.new, 
                   :action => url(:controller => 'posts', 
                                  :action => 'create_comment', 
                                  :id => post.id) do %>
        <%= text_field :body %>
        <%= submit 'New comment' %>
      <% end =%>
    </li>
  </ul>

<% end %>

<%= link_to 'add new post', url(:controller => :posts, 
                                :action => :new) %>
สังเกตว่าตรงเมท็อด url ค่อนข้างยาว คิดว่าน่าจะทำได้สั้นกว่านี้ถ้าเข้าใจ merb router (ไว้ค่อยมา update ครับ) นอกจากนี้เนื่องจากเราแสดงผลโดยใช้ลิสต์ แต่ตัว css ของ merb คงไปทำอะไรสักอย่างกับ li ผมเลยต้องไปลบบรรทัด link ของ stylesheet ใน app/views/layout/application.html.erb ด้วย ไม่งั้นไม่เห็นลิสต์

จากนั้นเราไปสร้างเมท็อด create_comment ในคอนโทรเลอร์ Posts


class Posts < Application
  # ...
  def create_comment
    post = Post[params[:id]]
    comment = Comment.new params[:comment]
    post.comments << comment
    post.save
    redirect url(:action => 'index')
  end
end
สังเกตว่าเราสามารถสั่ง Post[params[:id]] เพื่อเรียก post ที่มี id ตามที่ระบุได้

หวังว่าเอนทรีนี้น่าจะเป็นประโยชน์สำหรับผู้กำลังจะหัด merb และ Datamapper บ้างนะครับ ถ้ามีตำแนะนำคำแนะนำอะไรรบกวนใส่ด้านล่างเลยนะครับ

หมายเหตุ: English version

ขอบคุณครับ krajung

#5 By wonam on 2009-01-05 00:42

มีครับอาจารย์

"ถ้ามีตำแนะนำอะไรรบกวนใส่ด้านล่างเลยนะครับ"

คำว่า "ตำ" น่าจะเป็นคำว่า "คำ" นะครับ

sad smile

#4 By krajung (58.8.143.46) on 2008-12-29 15:36

ดูแล้วรู้สึกคุ้นเคยดี, learning curve สำหรับคนที่เป็น rails แล้วน่าจะไม่ยาก (ยกเว้นส่วน testing ที่ Punneng บ่นใน twitter บ่อยๆ)

#3 By pphetra (58.10.90.14) on 2008-12-29 12:13

เยอะ มึน

อยากลองเล่นบ้างจัง หุหุ

#2 By CyberAlchemist on 2008-12-28 22:34

แจ่มๆ ครับ
เห็นแล้วอยากจะปั่น merb66 ให้เสร็จโดยไว

#1 By PunNeng (58.8.116.152) on 2008-12-28 21:49