แสดงบทความที่มีป้ายกำกับ rails แสดงบทความทั้งหมด
แสดงบทความที่มีป้ายกำกับ rails แสดงบทความทั้งหมด

วันพฤหัสบดี, กรกฎาคม 05, 2550

อ่าน Agile Web Development with Rails ( 11 )

ทีนี้มาดูว่าเขาใช้ AJAX ใน Rails กันอย่างไร

ปกติเราใส่ปุ่มเข้าไปใน Rails โดยแทรกโค้ดง่ายๆดังนี้

<%= button_to "Add to Cart" , :action => :add_to_cart, :id => product %>

ซึ่งในเวลาทำงานจะสร้างโค้ด html อย่างนี้

<form method="post" action="/store/add_to_cart/1" class="button-to" >
<input type="submit" value="Add to Cart" />
</form>

เราเอา AJAX มาใช้ตรงนี้โดยการแทนที่แท็ก button_to โดยโค้ดนี้

<% form_remote_tag :url => { :action => :add_to_cart, :id => product } do %>
<%= submit_tag "Add to Cart" %>
<% end %>

คำสั่ง form_remote_tag ใช้สร้างฟอร์ม โดยโค้ดระหว่างบล็อก do - end จะเป็น elements ในฟอร์มที่สร้างขึ้น

นอกจากนี้ เพื่อให้ Rails รับรู้การใช้งาน AJAX เราต้องไปเพิ่มคำสั่ง javascript_include_tag ที่ head ใน app/views/layouts/store.rhtml ดังนี้

<head>
<title>Pragprog Books Online Store</title>
<%= stylesheet_link_tag "depot" , :media => "all" %>
<%= javascript_include_tag :defaults %>
</head>

โค้ดที่เขียนขึ้นจะถูกเรียกโดย add_to_cart แต่เราจะไม่ใช้ app/views/store/add_to_cart.rhtml อันเก่า (ลบทิ้งซะ) แต่จะใช้ app/views/store/add_to_cart.rjs แทน ดังนี้

page.replace_html("cart" , :partial => "cart" , :object => @cart)

เช่นนี้เอง

วันพุธ, กรกฎาคม 04, 2550

อ่าน Agile Web Development with Rails ( 10 )

ทีนี้เรื่อง partial templates หรือที่เรียกย่อๆว่า partials

ลองดูโค้ดนี้

<table>
<% for cart_item in @cart.items %>
<tr>
<td><%= cart_item.quantity %>×</td>
<td><%= h(cart_item.title) %></td>
<td class="item-price" ><%= number_to_currency(cart_item.price) %></td>
</tr>
<% end %>
<tr class="total-line" >
<td colspan="2" >Total</td>
<td class="total-cell" ><%= number_to_currency(@cart.total_price) %></td>
</tr>
</table>


เขียนใหม่โดยใช้ partials ได้อย่างนี้

<table>
<%= render(:partial => "cart_item" , :collection => @cart.items) %>
<tr class="total-line" >
<td colspan="2" >Total</td>
<td class="total-cell" ><%= number_to_currency(@cart.total_price) %></td>
</tr>
</table>

ส่วน cart_item จะแยกไว้อีกไฟล์หนึ่ง app/views/store/_cart_item.rhtml

<tr>
<td><%= cart_item.quantity %>×</td>
<td><%= h(cart_item.title) %></td>
<td class="item-price" ><%= number_to_currency(cart_item.price) %></td>
</tr>

คำสั่ง render ในไฟล์แรกจะแสดงผล app/views/store/_cart_item.rhtml (โปรดสังเกตว่ามี _ นำหน้า) โดยจะวนลูปให้อัตโนมัติครับ

วิธีนี้ทำให้เราสามารถเขียน partials ไปแปะแทรกไว้ที่ไหนก็ได้ที่ต้องการได้ทุกเมื่อครับ

วันอังคาร, กรกฎาคม 03, 2550

อ่าน Agile Web Development with Rails ( 9 )

เก็บหนังสือไปนาน กลับมาอ่านกันต่อครับ

มาดูการจัดการ error บ้าง สิบปากว่าไม่เท่าตาเห็น ดูโค้ดเอาเองก็แล้วกัน

app/controllers/store_controller.rb

def add_to_cart
begin
product = Product.find(params[:id])
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid product #{params[:id]}" )
flash[:notice] = "Invalid product"
redirect_to :action => :index
else
@cart = find_cart
@cart.add_product(product)
end
end

คราวนี้ถ้าเรียก http://localhost:3000/store/add_to_cart/wibble แทนที่จะเจอ error เจ้า error ก็จะไปอยู่ใน log/development.log แล้ว อย่างงว่า wibble คืออะไร สำหรับ Rails แล้ว สิ่งที่ใส่ต่อท้ายก็คือ parameter ID นั่นไงครับ

ปัญหาอีกอย่าคือ error message ที่เราคาดหวังยังไม่โผล่ ต้องไปใส่โค้ดนี้ที่ app/views/layouts/store.rhtml ด้วย

<% if flash[:notice] -%>
<div id="notice" ><%= flash[:notice] %></div>
<% end -%>

โดยทั่วไปจะไว้บบบนสุดก่อนข้อความอื่นใดนะครับ

วันศุกร์, มิถุนายน 22, 2550

อ่าน Agile Web Development with Rails ( 8 )

ทีนี้ลองดัดแปลง cart ให้มีส่วนของจำนวนสินค้าด้วย

เพิ่ม model ดังนี้ app/models/cart_item.rb

class CartItem
attr_reader :product, :quantity
def initialize(product)
@product = product
@quantity = 1
end
def increment_quantity
@quantity += 1
end
def title
@product.title
end
def price
@product.price * @quantity
end
end


แก้ไฟล์ app/models/cart.rb เพิ่ม mothod นี้

def add_product(product)
current_item = @items.find {|item| item.product == product}
if current_item
current_item.increment_quantity
else
@items << CartItem.new(product)
end
end

แล้วแก้ app/views/store/add_to_cart.rhtml ให้เป็นดังนี้

Your Pragmatic Cart
<ul>
<% for cart_item in @cart.items %>
<li><%= cart_item.quantity %> × <%= h(cart_item.title) %></li>
<% end %>
</ul>

แก้เสร็จแล้วต้อง
rake db:sessions:clear
เพื่อล้างข้อมูล sessions เก่าที่เก็บไว้ใน database

ตอนนี้สั้นๆแค่นี้เอง ถือว่าพักผ่อน

อ่าน Agile Web Development with Rails ( 7 )

คราวนี้เราจะมาดูเรื่อง cart กันครับ โดยจะเก็บข้อมูล cart ไว้ใน session

Rails เตรียมเรื่องนี้ไว้ให้แล้ว เราสามารถเพิ่ม session เข้าไปใน data model ดังนี้
rake db:sessions:create
จะได้ไฟล์ db/migrate/004_add_sessions.rb ขึ้นมา แล้วก็
rake db:migrate
table ชื่อ sessions จะถูกสร้างขึ้นมาครับ

สิ่งที่เราต้องทำต่อคือ เปิดไฟล์ config/environment.rb เข้าไปปลด # ให้บรรทัดนี้ทำงาน
config.action_controller.session_store = :active_record_store
เพราะ default จะบันทึกข้อมูลใน session ลงไฟล์ครับ

ต้อง restart เว็บเซิร์ฟเวอร์ใหม่ session ถึงจะเริ่มทำงานครับ

ขั้นต่อไปคือการสร้าง data model ของ cart โดยสร้างไฟล์ชื่อ app/model/cart.rb ดังนี้
class Cart
attr_reader :items

def initialize
@items = []
end

def add_product(product)
@items << product
end
end
โปรดสังเกตว่าเราไม่สร้างไฟล์นี้โดย script/generate เพราะอันนั้นใช้สร้างเฉพาะ model ที่เชื่อมต่อกับ table ในฐานข้อมูลโดยตรงครับ กรณีนี้ไม่ใช่ คราวนี้ไปที่ app/controllers/store_controller.rb เพิ่ม 2 method นี้เข้าไป

private
def find_cart
session[:cart] ||= Cart.new
end

def add_to_cart
@cart = find_cart
product = Product.find(params[:id])
@cart.add_product(product)
end

ยังจำได้ไหมครับ ตอนที่แล้ว ใน app/views/store/index.rhtml มีบรรทัดนี้อยู่
<%= button_to "Add to Cart" , :action => :add_to_cart, :id => product %>

ถ้าเราเรียกตอนนี้ก็จะขึ้น error เพราะยังไม่ได้กำหนด view ให้เลย
ก็ต้องสร้างไฟล์ app/views/store/add_to_cart.rhtml

<ul>
<% for item in @cart.items %>
<li><%= h(item.title) %></li>
<% end %>
</ul>

ทีนี้ cart ก็เริ่มใช้งานได้แล้วครับ

วันพฤหัสบดี, มิถุนายน 21, 2550

อ่าน Agile Web Development with Rails ( 6 )

ตอนที่แล้วเรายุ่งอยู่กับหลังบ้านของ depot คราวนี้มาจัดหน้าบ้านบ้างครับ

เราจะสร้าง controller ใหม่ขึ้นมาอีกอันหนึ่ง ให้ชื่อว่า store
ruby script/generate controller store index
index ที่แปะท้ายเพิ่มมาจากที่เราคุ้นเคยนั้นมีเพื่อบอกให้ Rails สร้าง action ชื่อ index ใส่ไว้ใน controller นี้ด้วยครับ

การทำอย่างนี้ นอกจาก Rails จะสร้าง app/controllers/store_controller.rb แล้ว ยังสร้าง app/views/store/index.rhtml ให้เราด้วยเลย

ที่ app/controllers/store_controller.rb เราจัดการแก้โค้ดให้เป็นดังนี้
class StoreController < ApplicationController
def index
@products = Product.find_products_for_sale
end
end

แล้วเราก็ไปแก้ product model เพิ่ม mothod ชื่อ find_products_for_sale เข้าไปดังนี้
def self.find_products_for_sale
find(:all, :order => "title" )
end

คำสั่ง find จะคืนค่ากลับมาเป็น array ของข้อมูลจาก product ที่มีเงื่อนไข :all (เอาหมด) และเรียงลำดับตามฟิลด์ "title" ที่ระบุโดย :order => "title"

คราวนี้ไปจัดการกับ app/views/store/index.rhtml ให้เป็นอย่างนี้

Your Pragmatic Catalog
<% for product in @products -%>
<div class="entry" >
<img src="<%= product.image_url %>" />
<%= h(product.title) %>
<%= product.description %>
<%= number_to_currency(product.price) %>
<%= button_to "Add to Cart" , :action => :add_to_cart, :id => product %>
</div>
<% end %>


คำสั่ง h() ใช้ถอด HTML Elements ออกจากข้อความ
- ที่อยู่นำหน้า %> ใช้ บอกให้ไม่ต้องขึ้นบรรทัดใหม่หลังคำสั่งนี้

ส่วนเจ้า add_to_cart นี่เราจะไปคุยกันตอนหน้าครับ

อ่าน Agile Web Development with Rails ( 5 )

จากตอนที่แล้ว คำสั่ง scaffold ใน controller มีอิทธิฤทธิ์ยิ่งนัก สามารถสร้างสิ่งมหัศจรรย์จากสูญญากาศ
แต่สุดยอดวิชาย่อมต้องมีจุดอ่อน อะไรที่ดีมากๆ จุดอ่อนก็คือ มันดีเกินไป

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

แต่ทางออกย่อมมีเสมอ เมื่อมี dynamic scaffold ก็ย่อมต้องมี static scaffold อยู่คู่กัน เป็นอีกทางเลือกหนึ่งซึ่งยุ่งยากกว่านิดหน่อย แต่ก็สามารถพลิกแพลงได้หลากหลายกว่ามากครับ

ruby script/generate scaffold product admin
product บอก model ที่ใช้ ในขณะที่
admin ระบุ controller ครับ

สิ่งสำคัญที่เราได้จากการนี้คือ rhtml หนึ่งพวงใหญ่อยู่ใน /app/views/admin/
ทีนี้อยากแก้ตรงไหนก็ตามสบายเลยครับ

เมื่อส่วนที่ rhtml พวกนี้จัดการคือ html ย่อมต้องมีส่วนของ css อยู่ที่ไหนสักที่
นั่นคือ public/stylesheets/ ครับ โดยปกติถ้าไม่ระบุ css file ที่ใช้ public/stylesheets/scaffold.css จะถูกเรียกใช้ by default ครับ

หากเราต้องการเปลี่ยนไปใช้ css file อื่น ก็ควรสร้างและเก็บไว้ที่ public/stylesheets/ นี้เช่นเดียวกันครับ

ส่วนที่ระบุเรื่อง css นี้จะเก็บแยกอยู่ใน app/views/layouts/con_name.rhtml ครับ ไฟล์นี้จะถูกสร้างตอนที่เราใช้คำสั่ง ruby script/generate scaffold model_name con_name ครับ

เปิดไฟล์ layout นี้ดูแล้วมองหาส่วนที่กำหนด css ครับ
<%= stylesheet_link_tag 'scaffold' %>
สมมติว่าเราอยากให้ใช้ depot.css ด้วยก็แก้เป็น
<%= stylesheet_link_tag 'scaffold' , 'depot' %>

แค่นี้เองครับ

อ่าน Agile Web Development with Rails ( 4 )

ทีนี้ลองปรับเปลี่ยน product ดู
ruby script/generate migration add_price
จะได้ไฟล์สำคัญที่ชื่อ db/migrate/002_add_price.rb
ยังจำได้ไหมว่าครั้งที่แล้วเรา generate model product แล้วได้ db/migrate/001_create_products.rb มา

เลข 001, 002 นี้ Rails สร้างขึ้นเพื่อควมคุมลำดับของการเปลี่ยนแปลง data model

ทีนี้แก้ไข db/migrate/002_add_price.rb ให้เป็นอย่างนี้
class AddPrice < precision =""> 8, :scale => 2, :default => 0
end
def self.down
remove_column :products, :price
end
end

แล้ว rake db:migrate

เรียก http://host_name/admin คงพอเดาได้ไม่ยากนะครับว่าจะเกิดอะไรขึ้น

ทีนี้ลองไปที่ app/models/product.rb แล้วใส่โค้ดเพิ่มเติมเข้าไป

class Product < ActiveRecord::Base
validates_presence_of :title, :description, :image_url
validates_numericality_of :price
validates_uniqueness_of :title
validates_format_of :image_url,
:with => %r{\.(gif|jpg|png)$}i,
:message => "must be a URL for a GIF, JPG, or PNG image"
protected
def validate
errors.add(:price, "should be at least 0.01" ) if price.nil? || price < 0.01
end
end

พระเจ้าจ๊อดมันยอดมากเลยใช่ไหมครับ model มันมีไว้ทำงี้นี่เอง

อ่าน Agile Web Development with Rails ( 3 )

ตอนที่แล้วทำให้เห็นภาพของสององค์ประกอบหลักในสถาปัตยกรรม MVC ใน Rails ยังขาดอยู่อีกตัวหนึ่งคือ M - Model ซึ่งเราจะได้เห็นกันในระหว่างการทดลองสร้าง application ตัวอย่างที่ชื่อ depot นี้ครับ

Rails รองรับการใช้งานฐานข้อมูลหลายตัว เราสามารถกำหนดได้ว่าจะใช้ตัวไหนตั้งแต่ตอนสร้าง application เช่น
rails app_name --database=oracle
หากไม่กำหนดดังนี้ rails จะเลือกใช้ค่า default คือ mysql ครับ

นอกจากนี้เรายังสามารถแก้ไขโดยตรงในไฟล์ config/database.yml พร้อมกับการกำหนดชื่อฐานข้อมูล และ username/password ก็ได้
Rails ไม่ได้บังคับเรื่องชื่อของฐานข้อมูลที่ใช้ แต่ตามข้อตกลงแล้ว ควรจะเป็นชื่อเดียวกับ application_*
เช่น application ชื่อ depot ฐานข้อมูลที่ใช้ระหว่างพัฒนาก็ควรเป็น depot_development

เมื่อกำหนดข้อมูลใน config/database.yml แล้ว สามารถทดสอบ config ได้โดย
rake db:migrate
เจ้า rake นี่ก็คือ make ของ ruby นั่นเองครับ

อ้อ... อย่าลืม create database และ user พร้อมกำหนดสิทธิ์ให้เรียบร้อยก่อน rake นะครับ ไม่งั้นไม่ผ่านแน่ๆครับ

เอาเป็นว่าตอนนี้เราสร้าง app ชื่อ depot และอยู่ใน working directory แล้ว โดยมีฐานข้อมูลพร้อมแล้วด้วยนะครับ

ทีนี้ก็กำหนด data model สำหรับสินค้าใน depot ชื่อโมเดล (และตาราง) คือ product ครับ
ruby script/generate model product
จะได้ไฟล์มากองนึง แต่ที่น่าสนใจมี 2 ไฟล์คือ app/models/product.rb และ db/migrate/001_create_products.rb (โปรดสังเกตว่าอันหลังมี s แถมท้ายชื่อ model มาให้ด้วย)

ไฟล์ app/models/product.rb คือตัว model ซึ่งเราจะมาดูทีหลังครับ
ตอนนี้ดูที่ db/migrate/001_create_products.rb กันก่อน เราจะแก้ไขไฟล์นี้ให้เป็นอย่างนี้ครับ

class CreateProducts < ActiveRecord::Migration
def self.up
create_table :products do |t|
t.column :title, :string
t.column :description, :text
t.column :image_url, :string
end
end

def self.down
drop_table :products
end
end

method ชื่อ up จะถูกเรียกให้ทำงานเมื่อเราสั่ง rake db:migrate
จะจัดการสร้าง table ชื่อ productsให้เราครับ

วิธีนี้ทำให้การพัฒนา application สะดวกขึ้น เพราะสามารถควบคุม version ได้
กรณีนี้ ถ้าเรียก rake db:migrate -VERSION=0 เจ้า method ชื่อ down ก็จะถูกเรียก tabel ชื่อ products ก็จะหายไป
การ rake db:migrate โดยไม่ระบุ VERSION ก็จะสร้าง VERSION ปัจจุบัน ทั้งนี้ Rails จะรู้ได้ว่า VERSION ในฐานข้อมูลคือ VERSION ไหนได้โดยสร้าง table ชื่อ schema info มาเก็บค่า VERSION ไว้ครับ

ทีนี้มาสร้าง controller ที่ใช้จัดการ products
ruby script/generate controller admin
เช่นเดิม เราจะสนใจเฉพาะ app/controllers/admin_controller.rb โดยเข้าไปเพิ่มเพียงหนึ่งบรรทัดให้เป็นอย่างนี้
class AdminController < ApplicationController
scaffold :product
end
คำสั่ง scaffold ใช้กำหนดให้ Rails สร้างโค้ดสำหรับจัดการ ข้อมูลใน model product ระหว่าง runtime

ไม่เชื่อก็ต้องเชื่อ เรียก http://host_name/admin จะเห็นได้ว่าเราสามารถจัดการกับข้อมูลใน product ได้แล้ว ทั้ง add, edit, remove ได้หมดเลย

คำสั่งเดียวแท้ๆ

อ่าน Agile Web Development with Rails ( 2 )

เราสร้าง application โดย Rails โดย
rails app_name
ซึ่งจะสร้าง directory ชื่อ app_name และไฟล์อะไรกระจุกกระจิกจำนวนหนึ่งซึ่งเราจะยังไม่สนใจตอนนี้
cd app_name

ทีนี้สร้าง controller โดยใช้คำสั่ง
ruby script/generate controller con_name
ซึ่งจะสร้างไฟล์หลายไฟล์เหมือนกัน แต่เราจะสนใจแค่อันเดียวก่อนคือ app/controllers/con_name_controllers.rb ซึ่งเป็น class กลวงๆอันนึง หากเราสร้าง method ขึ้นมาใน class นี้ จะเรียกว่าเป็น action

action นี้จะทำงานเมื่อเราเรียก http://host_name/con_name/action_name

แต่เราต้องกำหนดรูปแบบการแสดงผลให้ด้วยโดยการสร้าง view ขึ้นมา โดย view ที่จะตอบสนองกับ action นี้จะเป็นไฟล์ app/views/con_name/action_name.rhtml

ไฟล์ rhtml นี้ก็คือไฟล์ html ที่อนุญาตให้แทรกโค้ด ruby เข้าไปได้ ระหว่างแท็ก <% %> เรียกว่า ERb (Embeded Ruby) เหมือนกับ php หรือ asp นั่นเอง

โดยรูปแบบนี้เองเราจะเห็นว่า controller กับ view นั้นทำงานใกล้ชิดกัน โดยเราจะกำหนดข้อมูลที่จะแสดงผลไว้ที่ attribute ของ controller ซึ่งเราสามารถดึงมาใช้ได้ที่ view

ตัวอย่างดังนี้ครับ
ฝั่ง controller (apt/controllers/say_controller.rb)
class SayController < ApplicationController
def hello
@time = Time.now
end
def goodbye
end
end

ที่ฝั่ง view (apt/views/say/hello.rhtml)
แทรกโค้ดนี้เข้าไปเพื่อแสดงเวลา
<%= @time %>
แทรกโค้ดนี้เข้าไปเพื่อแสดงลิงก์ไปยัง action goodbye
<%= link_to "Goodbye!" , :action => "goodbye" %>

เป็นตัวอย่างง่ายๆของการใช้งาน Rails ครับ

วันพุธ, มิถุนายน 20, 2550

อ่าน Agile Web Development with Rails ( 1 )

// นี่คือชอร์ตโน้ตระหว่างอ่านเพื่อทบทวนความเข้าใจของตัวเอง
หากอ่านไม่รู้เรื่องขอให้ทำใจ ขออภัยในความไม่สะดวกไว้ ณ ที่นี้

สถาปัตยกรรมของ Rails อิงแนวคิด MVC ของลุง Trygve Reenskaug
คือ Model-View-Controller

Model จัดการข้อมูลและ Business Rules
View จัดการเรื่องการแสดงผล
Controller รับคำสั่งจากผู้ใช้ เพื่อไปประมวลผล

สำหรับ Rails คำสั่งจากผู้ใช้จะมาที่ Router ก่อน โดย Router จะส่งต่อคำสั่งไปยัง action ใน Controller ที่ใช้จัดการคำสั่งนั้น

หากมีส่วนที่เกี่ยวข้องกับข้อมูลก็จะติดต่อกับส่วน Model ซึ่งใน Rails นี้คือ Active Record Model

ผลลัพธ์จะถูกนำเสนอสู่ผู้ใช้ทาง View ครับ

Active Record ของ Rails จะยึดตาม ORM (Object Relational Mapping) Layer การดีไซน์ฐานข้อมูลโดยเริ่มจาก Active Record Model ของ Rails เป็นความคิดที่ดีมาก เพราะสะดวกกับการเลือกใช้ DB Engine ที่หลากหลายในภายหลัง

View ใน Rails, dynamic content ที่สร้างจาก templating scheme มีอยู่ 3 แนวด้วยกัน
- rhtml ซึ่งเป็นวิธีพื้นฐานที่สุด แทรก ruby code ลงใน html เรียกว่า ERb (Embeded Ruby)
- rmxl ใช้ ruby code สร้าง xml
- rjs สร้าง javascipt code เพื่อให้ไปรันที่ฝั่ง browser เหมาะสำหรับ dynamic AJAX

Controller เป็นหัวใจของ application รับงานจาก user, ติดต่อกับ model, ส่งต่อไปให้ view แสดงผล, จัดการ session รับงานสารพัดสารเพทุกอย่างที่เหลือทั้งหมดแม้กระทั่งการจัดการ cache เพื่อเพิ่ม performance