Displaying articles with tag restful

Ruby on Rails :: Default mime types

Posted by PunNeng, Sun Feb 24 15:55:00 UTC 2008

ต้นกล้ามาถามว่า เวลาใช้ respond_to แล้วเกิดรับ format ของ mime type ที่ไม่ได้เซ็ตไว้ มันก็จะทำงานไม่ถูกต้อง ผลที่ได้คือ จะได้ error 406 Not Acceptable
วิธีซ่อมง่ายๆ จากการแงะ plugin ที่ชื่อว่า exception_notifier คือใช้ format.all

  1
  2
  3
  4
respond_to do |format|
  format.html 
  format.all { redirect_to :action => "index" }
end

ถ้าไม่ใส่ block เพิ่มไป มันจะไปเรียก index.all.erb ในกรณีที่ใช้ restful
แต่ถ้าไม่ใช้ ก็จะเป็น html ไป

ก็มาสงสัยอีว่า default mime type ที่เซ็ตไว้มีอะไรบ้าง ก็ไปแงะมา เจอ

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
# Build list of Mime types for HTTP responses
# http://www.iana.org/assignments/media-types/

Mime::Type.register "*/*", :all
Mime::Type.register "text/plain", :text, [], %w(txt)
Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )
Mime::Type.register "text/css", :css
Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
Mime::Type.register "application/rss+xml", :rss
Mime::Type.register "application/atom+xml", :atom
Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml )

Mime::Type.register "multipart/form-data", :multipart_form
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form

# http://www.ietf.org/rfc/rfc4627.txt
Mime::Type.register "application/json", :json, %w( text/x-json )

ถ้าจะเพิ่ม ก็ไปประกาศได้ที่ environment.rb ว่า

  1
Mime::Type.register "image/jpg", :jpg

1 comment | Filed Under: Ruby on Rails | Tags: restful

Ruby on Rails :: Restful URL Customizing

Posted by PunNeng, Sun Sep 02 19:52:00 UTC 2007

อีกหนึ่งส่วนสำคัญของเรื่องนี้ คือ url ของการใช้งาน restful ของ rails
ลองอ่าน restful ของ rails ก่อน ผมติดไว้ว่าจะเขียนเรื่อง routing ไว้ ก็จะเขียนต่อตรงนี้เลยละกัน

หลังจากที่ประกาศ

map.resources :products

ใน config/route.rb แล้ว จะมีชื่อของ route ที่ถูกสร้างไว้เพียบเลย แต่ที่ใช้บ่อยๆ กับ CRUD คร่าวๆ ตามนี้

  • products_path (get -> index) # /products
  • products_path (post -> create) # /products
  • product_path(:id) (get -> show) # /products/:id
  • product_path(:id) (put -> update) # /products/:id
  • product_path(:id) (delete -> destroy) # /products/:id
  • new_product_path (get -> new) # /products/new
  • edit_product_path (get -> edit) # /products/:id;edit

ซึ่งจริงๆ แล้ว _path ก็จะมี _url ไว้ให้เรียกใช้อีกชุดนึง

สำหรับการ map เข้าในแต่ละ action จะทำได้ด้วยการกำหนด request method

Get (Read)

จะใช้ get สำหรับการ request ขอข้อมูล

link_to "List Products", products_path
link_to @product.title, product_path(@product)

ปกติจะใช้กับ link

Post (Create)

จะใช้ post สำหรับการสร้าง หรือการเพิ่มเข้าฐานข้อมูล

<% form_for(:product, :url => products_path) do |f| %>

ปกติจะใช้กับ form

Put (Update)

จะใช้ put สำหรับการปรับปรุงข้อมูลที่มีอยู่แล้ว

<% form_for(:product, :url => product_path(@product), :html => { :method => :put }) do |f| %>

ปกติใช้กับ form เช่นกัน

Delete (Delete)

จะใช้ delete สำหรับการลบ

<%= link_to 'Destroy', product_path(product), :confirm => 'Are you sure?', :method => :delete %>

จริงๆ ไม่จำเป็นเท่าไหร่ว่า method อันไหน ใช้กับอะไร เพียงแต่ใส่ method ให้ได้ ก็ใช้ได้เหมือนๆ กัน

ต่อไป จะทำการกำหนด action ที่นอกเหนือจาก CRUD
ซึ่งผมเคยเขียนไปครั้งนึงแล้ว ลองหยิบตัวอย่างมาสองสามอันที่ใช้บ่อยๆ ละกัน

map.resources :messages, :path_prefix => "/threads/:thread_id"
# --> GET /threads/7/messages/1

หรือใช้ nest

map.resources :threads do |thread| thread.resources :messages end

เวลาเรียกใช้ จะเรียกแบบนี้

<%= link_to @message.title, message_path(@thread, @message) %>
<%= link_to "Message List", messages_path(@thread) %>
<%= link_to @message.title, message_path(:thread_id => @thread, :id => @message.id) %>
<%= link_to "Message List", messages_path(:thread_id => @thread) %>

ยืดหยุ่นสุดๆ อยากใช้แบบไหน ก็ได้เหมือนๆ กัน ถ้าใส่ instance ลงไป ก็จะยิงไปที่ id อัตโนมัติ หรือถ้าเป็นค่าอื่น ก็ระบุไป ถ้าไม่ระบุ ก็เป็น id

ข้อสังเกตอีกนิดนึง คือการระบุให้เป็น singular/plural ถ้าเป็น singular ก็ต้องระบุไป ว่าอันไหน ถ้าไม่ต้องการทำกับ instance ตัวไหนตัวนึงหรือต้องการทำทีละหลายๆ ตัว ก็ระบุเป็น plural ไป จริงๆ เรื่องนี้ใช้ความเข้าใจเอาก็ได้ เพราะมันก็เหมือนๆ english นั่นแหละ :)

map.resources :messages, :collection => { :rss => :get }
# --> GET /messages;rss (maps to the #rss action)
# also adds a url named "rss_messages"

:collection ไว้ใช้กับ plural
วิธีใช้

<%= link_to "RSS", rss_messages_path %> # --> GET /messages;rss (maps to the #rss action)

จะเซ็ตเป็น post ก็ได้ แล้วใส่ที่ form ก็ได้เหมือนๆ กัน สำหรับตัวอย่างการใช้งานกับ form ดูได้จากการที่ใช้ scaffold_resource สร้างมาให้ก็ได้

map.resources :messages, :member => { :mark => :post }
# --> POST /messages/1;mark (maps to the #mark action)
# also adds a url named "mark_message"

คราวนี้ ไว้ใช้กับ singular เช่น

<% form_for :message, @message, :url => :url => mark_message_path(@message) do |f| %>

ผมขี้เกียจละ ตัวอย่างที่่เหลือ คิดว่าคงจะพอเข้าใจอะไรลางๆ แล้วละครับ

map.resources :messages, :new => { :preview => :post }
# --> POST /messages/new;preview (maps to the #preview action)
# also adds a url named "preview_new_message"

map.resources :messages, :new => { :new => :any, :preview => :post }
# --> POST /messages/new;preview (maps to the #preview action)
# also adds a url named "preview_new_message"
# --> /messages/new can be invoked via any request method

map.resources :messages, :controller => "categories",
:path_prefix => "/categories/:category_id",
:name_prefix => "categories_"
# --> GET /categories/7/messages/1
# has named route "categories_messages"

อีกนิดนึง ใน rails 2.0 มันเปลี่ยนเครื่องหมายนิดหน่อย จาก /messages;rss เปลี่ยนมาเป็น /messages/rss แทน

ปล. มันมี map.resource อีกตัวนึง แต่ไว้ใช้สำหรับ singleton ซึ่งผมยังไม่เคยใช้เลย

1 comment | Filed Under: Ruby on Rails | Tags: restful

Ruby on Rails :: RESTful on Rails(3) >> respond_to

Posted by PunNeng, Sat Feb 03 23:40:00 UTC 2007

มาต่อกัน ยังไม่จบ

คราวนี้เข้ามาข้างใน controller กันบ้าง ฝั่ง logic ข้ามไป มาดูตรง respond_to กันดีกว่า

  1
  2
  3
  4
  5
  6
respond_to do |format|
      format.html # index.rhtml
      format.js     # index.rjs ผมเพิ่มเอง
      format.xml  { render :xml => @products.to_xml }
      format.rss   { render :action => "rss.rxml" }  # อันนี้ผมก็เพิ่มเอง
    end

คงจะเห็น code อันนี้ใน index หรือใน create จะเห็น

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
respond_to do |format|
      if @product.save
        flash[:notice] = 'Product was successfully created.'
        format.html { redirect_to product_url(@product) }
        format.xml  { head :created, :location => product_url(@product) }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @product.errors.to_xml }
      end
    end

มันไว้ทำอะไร ??? มันจัดการกับ mime type นั่นแล ซึ่งขึ้นอยู่กับ request ที่ส่งมา ว่าต้องการ respond เป็นอะไรกลับไป ถ้าต้องการ html ก็ใช้วิธีปกติที่เคยทำกัน แต่ถ้าเกิดต้องการในรูปของ xml ก็เพียง request มาว่าต้องการ xml ซึ่ง Rails จะไปจัดการกับ HTTP Accept header จากที่ request มาเอง

ทำไมต้อง xml ?? ก็เพราะว่า Web Services หนะสิ แค่ request มา แล้วมันก็จะ return เป็น xml ไปให้ แค่นี้ไม่ว่าภาษาใดๆ ก็จะเข้าใจได้้้ด้วย xml แล้ว

มาดูในตัวอย่าง ถ้าเกิดส่ง url มาเป็น /products แน่นอน มันย่อม render ออกไปเป็น index.rhtml แต่ถ้าเป็น /products.xml มันก็จะ render เป็น xml ให้ โดยใช้ attribute เป็นตัวกำหนด node (อันนี้ลองเล่นกันเองนะครับ)

ถ้าเป็น ajax เรียกมา มันก็จะไป render ตัว index.rjs(อันนี้ก็สนุก สำหรับ ajax แปะไว้ๆ) หรือถ้าเป็น /products.rss มันก็จะไป render ตัว index.rxml ซึ่งทั้ง .js และ .rxml ก็มีรูปแบบของมันอยู่ ไว้จะย้อนมาเล่าให้ฟังทีหลัง สำหรับ rss หรือ atom จริงๆ แล้วมี plugin ช่วย ใช้ plugin จะผ่อนแรงกว่าเยอะ

ส่วนอันที่เป็น create ถ้าใช้ HTML ก็ปกติดี แต่ถ้าเกิดต้องการเป็น Web Services ให้สร้าง header ให้มีหน้าตาแบบนี้(ประยุกต์และอ้างอิงจาก DHH)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
POST /products/create HTTP/1.1
Host: example.org
User-Agent: Thingio/1.0
Accept: application/xml
Content-Type: application/xml
Content-Length: nnn

<product>
  <name>First product!!</name>
  <description>This is a book</description>
</product>

เนื่องจาก Content-Type เป็น application/xml ตัว rails เลยทำการแปลงให้อยู่ในรูปของ "product[name]=First%20product!!&product[description]=This%20is%20a%20book" ซึ่งเป็น form ปกติที่ใช้อยู่ ซึ่งตัว Content-Type จะเป็นตัวบอกว่าให้ xml มานะ ส่วนตัว Accept จะเป็นตัวบอกว่า ของที่คุณต้องเรียกใช้ ก็เป็น xml เหมือนกันนะ เพื่อให้มันรับรูปอย่างชัดเจนในการ respond แล้วมันก็จะวิ่งไปที่ format.xml เอง ถ้าจะใช้ WS กับ rails ชุดนี้ อย่าลืมระบุ Accept มาด้วยนะครับ

หลังจากที่ทำการสร้างให้เสร็จแล้ว มันก็จะส่ง header กลับไป หน้าตาที่มันถูกสร้าง จะเป็นแบบนี้

HTTP/1.1 201 Created
Content-Length: 0  
Location: http://example.org/products/show/1

ในกรณีที่เราต้องการที่จะระบุ mime type เพิ่มเติม สามารถไปเพิ่มได้ที่ config/environment.rb

Mime::Type.register "image/jpg", :jpg

เพิ่งนึงถึง DRY ได้ ว่ามันเข้า concept ของ DRY พอดี เราไม่ต้องทำซ้ำ ไม่ว่าจะต้องการ respond แบบไหนเราก็ทำเพียงครั้งเดียว ง่ายต่อการ maintenance

นี่แล ความเมามันส์ของ RESTful

ปล. rails มันส์กว่า ruby เพียวๆ แฮะ แก้ไขครั้งที่ 1 : เพิ่งนึกได้ว่า ถ้าหากเป็น method ที่ไม่ได้เกี่ยวกับ CRUD ถ้าจะ render ตามที่สั่งใน format ไว้ ให้เพิ่ม ?format=your_format ไปด้วย เช่น ?format=xml

แก้ไขล่าสุด วันที่ 28 กรกฏาคม 2550 เวลา 1.34 น.

1 comment | Filed Under: Ruby on Rails | Tags: restful

REST

Posted by PunNeng, Sun Dec 24 12:07:00 UTC 2006

ผมตั้งใจจะเขียนถึง Simply Restful ที่เป็น plugin ของ Rails แต่ที่มาของมันนี่สิครับ ยาวหลายทอดเหลือเกิน เห็นว่ามันน่าสนใจดี เลยเอามาเล่าสู่กันฟัง

มาเริ่มกันที่ REST REST ย่อมาจาก Representational State Transfer ถูกสร้างขึ้นมาโดย Roy Fielding ตอนทำปริญญานิพนธ์ปริญญาเอกของเขา

แล้วมันคืออะไร มันเป็นแนวความคิดของสถาปัตยกรรม software เป็นแค่แนวนะครับ ทำไมต้องเป็น REST ? มาดูคำอธิบายจากที่นี่ดีก่า เขาอธิบายไว้ว่า

เว็บเนี่ย มันก็ประกอบกันไปด้วยทรัพยากร(resource ต่อไปผมจะใช้คำนี้ แทนคำว่าทรัพยากรนะครับ)ต่างๆ นานา resource อันนี้ หมายถึงสิ่งที่เรากำลังสนใจอยู่ เช่น โรงงานทำเครื่องบิน Boeing ได้กำหนดทรัพยากรของเครื่องรุ่น 747 คนใช้ ก็สามารถเข้าถึงได้ด้วย URL:

http://www.boeing.com/aircraft/747

เราก็จะได้ตัวแทน(representation)ของ resource นั้นๆ ออกมา ในที่นี้คงเป็นหน้า html ที่ระบุถึง resource ของมัน มันจะมองการกระทำของเราเป็น state ทีนี้ เมื่อเราเปลี่ยนหน้าเว็บ เช่น เข้าไปดูในหน้าต่างๆ ของเว็บนั้นๆ ก็จะทำการส่งตัวแทนมาใหม่อีก และทำการเปลี่ยน state ตัวใหม่ด้วย ดังนั้น การที่มันเปลี่ยน state ของมันนี่แหละ คือ Representational State Transfer

#$%@เข้ มันช่างซับซ้อนอะไรเช่นนี้ มาดูฝั่งเจ้าของแนวความคิดกันบ้าง เขาบอกประมาณว่า

Representational State Transfer มันถูกสร้างขึ้นมาเพื่อให้เห็นถึงรูปของการออกแบบพฤติกรรมของ web app ที่ดี หมายถึง ในหน้า page เนี่ย มันจะมี links ต่างๆ นานา เต็มไปหมด ให้ติ๊งต่างว่าเป็น state-machine จำลองละกัน เมื่อมีคนใช้มาทำการกด link ก็เสมือนว่ามันเป็นการเปลี่ยน state จิ้มทีก็เปลี่ยนที แล้วมันก็จะเปลี่ยนหน้าเว็บไปอีกหน้านึง ซึ่งมันก็คือ state ถัดไปที่มันจะต้องเปลี่ยนนั่นเอง

ข้อสรุปของไอ้เหน่ง ก็คือ มองมันให้เป็น state นั่นละครับ มีการกระทำทีนึง ก็เปลี่ยน state ทีนึง แต่แค่นี้มันจะเป็น REST หรอ มันต้องมีเงื่อนไขอะไรอีกต่างๆ นานา แต่มันก็ไม่เป็นมาตรฐาน ถึงขนาดต้องออก specification ของมันมา เพราะมันเป็นแค่แนวความคิด เราสามารถเขียนงานของเรา ให้ทำงานตามแนวนี้ได้ มันใช้ของพื้นๆ เช่น

  • HTTP
  • URL
  • XML/HTML/GIF/JPEG/etc (Resource Representations)
  • text/xml, text/html, image/gif, image/jpeg, etc (MIME Types)

การที่จะทำให้งานของเรา อยู่ในรูปของ REST ได้ ก็ใช่ว่ามองทุกอย่างให้เป็น state เท่านั้น มันยังมีเงื่อนไขอื่นๆ อีก แต่มันจะเน้นไปที่การใช้ทรัพยากรต่างๆ ที่มันมีอยู่ สังเกตจาก list ข้างบน ไม่มีอะไรใหม่เลย ในโลกของ www มันใช้ของเก่าที่มีอยู่แล้วให้เกิดประสิทธิภาพมากขึ้น(เหมือน Ajax เลยแฮะ) มันก็มีหลักของมันอยู่ เข้าไปดูได้ที่ wikipedia ได้ครับ

ตัวอย่างง่ายๆ ของ REST ก็คือ เว็บนี่ละครับ ที่เราใช้อยู่ทุกวันนี้ มองมันให้อยู่ในรูปของ state-machine สิครับ แล้วจะเห็นเอง คงพอจะมองเห็น� าพของ REST คร่าวๆ มาบ้าง ทีนี้ Key อีกตัวนึงก็คือ RESTful ที่จะเป็นตัวนำเข้าไปสู่ Simply Restful ไว้คราวหน้าละกันครับ

แก้ไขล่าสุด วันที่ 21 กรกฏาคม 2550 เวลา 23.51 น.

0 comments | Filed Under: General | Tags: restful

codegent: we're hiring