ผมได้ทดลองใช้ merb เมื่อสองสามวันก่อน อย่างไรก็ตามหลังจากได้ทดลองหาตัวอย่างที่จะได้หัดตามจากอินเทอร์เน็ต กลับพบว่าตัวอย่างพวกนั้นเก่าเกินไปแล้ว หรือไม่ก็ไม่ได้ใช้ Datamapper เป็น ORM เหมือนที่ผมต้องการทดลองเล่น
แน่นอนว่าผมทราบข่าว Merb 2 = Rails 3 แล้ว และนี่ก็เป็นสาเหตุที่เอา Merb มาหัด เพราะว่าอยากเล่น Merb สักหน่อยก่อน จะได้ทราบว่าอะไรที่มันน่าจะน่าสนใจบ้าง
บทความนี้เอาขั้นตอนหลัก ๆ มาจากบทความ "merb + datamapper + noob: quick start" แต่ตัดบางอย่างออก และปรับการใช้งานให้ตรงกับ Merb เวอร์ชั่นที่ผมใช้อยู่ (1.0.6) นอกจากนี้ยังคาดว่าผู้อ่านน่าจะมีประสบการณ์การใช้ Rails มาบ้างครับ
ขั้นแรกคือติดตั้ง merb ถ้าเป็น ubuntu ก็ทำประมาณ
แล้วก็ติดตั้ง Datamapper และส่วนติดต่อกับ Sqlite3
$ sudo gem install datamapper merb_datamapper do_sqlite3
จากนั้นก็สร้าง Merb application
คำสั่ง
merb-gen app จะสร้าง application ว่าง ๆ ขึ้นมา ที่ใช้ Datamapper เป็น ORM
ใน blog/ จะมีโครงสร้างของไดเร็กทอรีที่ผู้ใช้ Rails น่าจะคุ้นเคย ไฟล์ที่เกี่ยวข้องกับการตั้งค่าจะอยู่ในไดเร็กทอรี config/ ไฟล์ที่น่าสนใจก็มีเช่น dependencies.rb (ระบุว่าใช้โมดูลไหนบ้าง --- การถอดประกอบเป็นจุดเด่นของ merb), init.rb (ใช้ระบุว่าจะเลือกใช้อะไรเป็น ORM, เป็นระบบทดสอบ และระบบ template), และ database.yml (เช่นเดียวกับใน Rails ที่ระบุข้อมูลเกี่ยวกับฐานข้อมูลที่ใช้).
ในขั้นนี้ของที่ merb สร้างมาให้น่าจะโอเคแล้ว เราก็จะใช้ไปตามนั้นก่อน
สร้างโมเดล
ได้เวลามาสร้างโมเดลกันครับ สั่งคำสั่งด้านล่าง (ในไดเร็กทอรี blog/)
เพื่อสร้างโมเดล 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: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 ด้วยคำสั่ง
จากนั้นเข้า 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 โดยเรียก
แล้วแก้ไฟล์
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