Displaying articles with tag simply_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: simply_restful

Ruby on Rails :: RESTful on Rails(2)

Posted by PunNeng, Wed Jan 31 03:12:00 UTC 2007

หลังจากที่ Rails 1.2(1.2.1 แล้ว) ถูกปล่อยออกมา หนึ่งใน feature หลักๆ ก็คือ RESTful โดยอาศัย plugin(ในอดีต) ที่ชื่อ simply_restful ซึึ่งถูกเพิ่มเข้าไปใน rails-core ใน edgerails ตั้งนานแล้ว

ok มาดูกัน ว่ามันจะทำอะไรยังไง

อันดับแรก จัดการให้ rails ของเราเป็น version ล่าสุดก่อน โดยการพิมพ์ใน terminal ว่า

sudo gem install rails --source http://gems.rubyonrails.org -y

-y คือ --include-dependencies

ถ้าใน windows ก็พิมพ์ใน cmd เลย เพียงแต่เอา sudo ออก

หลังจาก update กันเสร็จแล้ว ก็สร้าง project กันเลย โดยพิมพ์ว่า

yourpath$ rails project_name

แล้วไปสร้าง db ก่อน ผมก็สร้างเอาใน phpmyadmin(ใน ​MAMP) เนี่ยแหละ ง่ายดี(ปกติผมใช้ migration ครับ ไว้คราวหน้า จะมาเล่าให้ฟัง) สร้างชื่อว่า "stock_development" แล้วใส่ sql command ว่า

CREATE TABLE products (
  id int(11) NOT NULL,
  name varchar(255) NOT NULL,
  description text NOT NULL,
  PRIMARY KEY  (id)
)

แล้วก็ไปจัดการใน database.yml ให้เรียบร้อย(ตัวอย่าง)

จากนั้นก็เข้าไปใน project พิมพ์ต่อใน terminal ว่า

project_name$ ruby script/generate scaffold_resource product

มันก็จะสร้างชุด file มาชุดนึง ซึ่งเมื่อก่อนที่เคยใช้คือ generate scaffold เฉยๆ ซึ่งตัวใหม่นี้ จะทำการสร้างชุด CRUD มาให้พร้อมใช้เลย ลองไปไล่เปิด file ดูนะครับ โดยส่วนใหญ่จะอยู่ที่ controller

หลังจากน้ัน ไปที่ config/routes.rb ลองดูว่ามันทำการ map url ยังไง

map.resources :products

มีแค่นี้เอง แค่ใส่ให้เป็น symbol แบบ plural แล้วมันก็จะมีชุด url ที่เคยให้ดูในครั้งที่แล้ว โดยสร้างจาก Net::HTTP หน้าตาก็จะประมาณนี้ (ติ๊งต่างว่า messages คือ products ละกันครับ ผมก็ copy มาอีกที)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
Net::HTTP.start("localhost", 3000) do |http|
    # retrieve all messages
    response = http.get("/messages")

    # return an HTML form for defining a new message
    response = http.get("/messages/new")

    # create a new message
    response = http.post("/messages", "...")

    # retrieve message #1
    response = http.get("/messages/1")

    # return an HTML form for editing an existing message
    response = http.get("/messages/1;edit")

    # update an existing message
    response = http.put("/messages/1", "...")

    # delete an existing message
    response = http.delete("/messages/1")
  end

แล้วก็จะมี options อีกต่างๆ นานา เช่น

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
map.resources :messages, :path_prefix => "/thread/:thread_id"
  # --> GET /thread/7/messages/1

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

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

  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"
  • :path_prefix ก็คือส่วนที่เราอยากจะให้อยู่ข้างหน้า url ดูตามตัวอย่างไปเลยนะครับ
  • :collection ก็คือ action(method ใน controller) ที่เราอยากจะเพิ่มเข้าไป แต่ข้อสังเกตคือ ใช้กับ index เท่าน้ัน ซึ่งส่วนใหญ่ index จะทำหน้าที่ list all item ก็หมายความว่าเป็น action ที่ทำกับ resource รวมๆ โดยระบุ method เข้าไปได้ด้วย แล้ว named route ก็จะถูกสร้างเอาไว้ใช้ใน controller และ view1 โดยจะมีชื่อว่า "rss_messages" (option ต่อไปดู named route จากตัวอย่างละกันครับ)
  • :member อันนี้ก็เหมือนกับ :collection เพียงแต่มันจะไปกระทำกับ resource ตัวใดตัวหนึ่ง เช่นในตัวอย่าง มันจะไปพิจารณาตัวที่มี id เป็น 1
  • :new อันนี้เหมือนกับ :collection เหมือนกัน แต่ไว้จัดการกับ resource ของ new
  • อีกรูปนึงของ new แต่ว่าอันนี้ จะทำการเซ็ตค่าเพิ่มหน่อย ตรงที่ new จะถูกเรียกจาก request อันไหนก็ได้(ปกติ new จะเรียกได้จาก get อย่างเดียว​​)
  • :controller ใช้เซ็ตตัว controller ให้กับ resource ของเรา เช่น ถ้าเซ็ตให้
    resources :categories, :controller =>"products"
    
    เวลาเรียก /categories ก็จะเสมือนว่าเรียก /products
  • :singular ไว้เซ็ตค่า singular กับ named route เช่น :singular => "pd" ตัว named route ของ product ที่เป็น singular จะถูกเปลี่ยนไปตามที่เซ็ตไว้ ถ้าเป็น new จะเป็น new_pd ถ้าไม่เซ็ตค่านี้ ค่า default ของมันจะเป็น new_product ซึ่งไว้ใช้ในกรณีที่รูปของ singular กับ plural มันตรงกัน เช่น sheep หรือ fish หรือ news
  • :name_prefix ก็เอาไว้ใส่ prefix หน้า named route (ดูจากตัวอย่างได้เลย)

กลับมาที่ terminal กันต่อ สั่ง run server เลย

project_name$ ruby script/server

มาลองเริ่มต้นที่ /products ดูก่อน ลองจิ้มไปเรื่อยๆ จะพบว่า form มันหายไป อันนี้เป็นหน้าที่เราแล้ว ที่ต้องไปใส่เอง ไปที่ app/view/products/index.rhtml แล้วเพิ่ม

  1
  2
  3
  4
  5
  6
  7
...
<tr>
    <td><%= product.name %></td>
    <td><%= product.description %></td>
    <td><%= link_to 'Show', product_path(product) %></td>                       
...
</tr>

อันนี้เข้าไป ลองสังเกตดูนะครับ จะมี product_path อยู่ ซึ่งเป็น named route จะทำการสร้้าง url เป็น /products/:id

แล้วสร้าง _form.rhtml ในนี้แหละ

<p><label for="product_name">Name:</label><br />
<%= form.text_field "name" %></p>
<p><label for="product_description">Description</label><br />
<%= form.text_area "description:" %></p>

เนื่องจากมันถูกสร้างมาในฟอร์มของ FormBuilder เลยต้องสร้างในรูปของ FormBuilder ต่อไป โดยใช้ form.text_field และ form.text_area

จากนั้นต่อกันด้วย new.rthml และ edit.rhtml

<%= render :partial => 'form' , :object => f %>

โดยเพิ่มไประหว่าง form_for และ submit_tag ทั้ง new.rhtml และ edit.rhtml ใน edit.rhtml ลองดูใน form_for ดู จะเจอ :method => :put อยู่ เนื่องจากมันมีปัญหากับ browser เลยต้องใช้วิธีนี้เซ็ตเอา โดยมันจะเพิ่ม hidden tag เข้าไป

<input name="_method" type="hidden" value="put" />

พอ submit มันก็จะไปเพิ่ม ?_method=put เข้าไป แล้วมันจะมีกลไกต่อเอง โดยมันจะตรวจจาก _method นี้แล ไปต่อที่ show.rhtml

<%= @product.name %> <%= @product.description %>

เพิ่ิมอันนี้เข้าไป

จากนั้นมาลอง test กันดูใหม่ ลองเพิ่มลอง update ดู

ย้อนกลับมาที่ index.rhtml ในส่วนของ delete จะเจอ

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

เราจะเห็นได้ว่ามีการเซ็ต :method => :delete เพื่อเป็นการบอก method ถ้าเรา view source จะเจอ js อยู่ก้อนนึง เป็นตัวเซ็ต delete method ซึ่งจะทำการสร้าง form มาตัวนึง แล้วเซ็ต method เข้าไป เหมือนกับของ put แต่ว่าอันนี้ใช้ js เป็นตัวสร้าง

ถ้าต้องการเพิ่มเติม ไปที่ api ของ rails ได้เลย

ปล. ติดไว้หลายเรื่องแฮะ จดไว้ก่อน มี migration, route file, respond_to(ลองแงะเข้าไปดูใน controller ดูครับ)

1 เป็นลักษณะเหมือน alias ตัวที่ถูกสร้างขึ้นมา สามารถใช้ใน controller ได้ เช่น redirect_to rss_messages_path หรือฝั่ง view เช่น link_to "rss", rss_messages_path ซึ่ง path จะสามารถเปลี่ยนเป็น url ได้ ถ้า view source จะเห็นเป็น url เต็มๆ แปะเรื่อง route ไว้ก่อน แล้วจะมาเจอกันเต็มๆ

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

0 comments | Filed Under: Ruby on Rails | Tags: simply_restful

Ruby on Rails :: RESTful on Rails

Posted by PunNeng, Wed Jan 17 14:45:00 UTC 2007

มะ ไม่ต้องเสียเวลา เริ่มกันเลย

จากที่ค้างไว้คราวที่แล้วเกี่ยวกับ RESTful มันมีความหมายว่า ระบบที่รองรับหลักของ REST แต่จะมีอีกคำนึงซึ่งพวกคลั่งไคล้พวกนี้จะเรียกตัวเองว่า RESTafarians (ขอแปลขำๆ ว่า ชนเผ่า REST ละกัน)

แล้วระบบที่รองรับ ​REST ต้องมีสภาพเป็นอย่างไร ?? ผมเองบอกได้ไม่หมดครับ จริงๆ ก็ยังไม่รู้แจ้งเห็นจริงเท่าไหร่ และตัว simply_restful ชื่อมันก็บอกแต่แรกแล้วว่า simply มันง่ายๆ มันจะไม่เป็น REST 100% เหมือนที่ใช้กับ WS แต่ที่แน่ๆ มีอยู่ข้อนึงที่ผมน่าจะพอถูไถไปได้คือ มีหลักการพื้นฐานอยู่ข้อนึงที่ว่า มันจะมีการจัดการ resources ต่างๆ ผ่านทาง verb ซึ่ง verb หรือคำกริยาพวกนี้ ในที่นี้ จะหมายถึง method ของ HTTP คือ get, post, put และ delete

มาดูตารางความสัมพันธ์ของสิ่งต่างๆ ว่ามันจะสัมพันธ์กันยังไง ถ้าใช้ simply_restful โดย resource ที่จะพิจารณา ผมจะติ๊งต่างให้เป็น products ละกันครับ ซึ่งจะแสดงผ่านทาง URL

URL structure Verb Method
/products get index
/products/1 get show
/products/new get new
/products/1 post create
/products/1;edit get edit
/products/1 put(post) update
/products/1;delete delete(post) destroy

สำหรับ get ก็มีหน้าที่ที่จะไว้ดู(Read) หรือเอามันออกมาดู ใน Rails method ก็จะใช้กับ index, show, edit, new ส่วน post ก็มีหน้าที่สำหรับการสร้าง(Create) ก็คือการ create method แล้ว put ทำหน้าที่สำหรับการปรับปรุงหรือเปลี่ยนแปลง(Update) ก็คือ update method แต่แล้วก็มีข้อสงสัย แล้วทำไมเราไม่เราไปใช้กับ post ล่ะ ก็เพราะว่าเรากำลังพิจารณาในแง่ของ resource(Noun) แล้วมันจะกระทำการใดๆ โดยผ่าน verb ตามเงื่อนไขของ ​REST จึงใช้ put เป็นตัวแทนของกริยานี้(ผมคิดเอาเอง แต่มันก็ match กันพอดี ทำให้ดูสวยไปด้วย) สุดท้าย delete ไว้สำหรับลบ(Delete) ก็คือ destroy method

โอ๊ะโอ๋....... เฮ้ย ในความเป็นจริง มันใช้ put กับ delete ไม่ได้นี่หว่า​!!!!! เพราะ web browser ไม่รองรับ

จริงๆ 2 ตัวนี้ มันใช้ post ละครับ ใน url เราเลยต้องมีการระบุ _method เพิ่มเข้าไป เพื่อเป็นการบอก verb ให้กับมัน ซึ่งในการใช้งาน simply_restful จริงๆ ขั้นตอนพวกนี้ จะมี code ส่วนหนึ่ง(hidden tag)ไปจัดการให้ โดยจะเพ่ิม ?_method=put หรือ delete ให้โดยอัตโนมัติ แล้วมันจะไปจัดการพิจารณาแยกกับ post กันอีกที ไว้คราวหน้าครับ จะมาแสดงให้ดูสำหรับการใช้งาน simply_restful

เรามาเรียงใหม่ตามหน้าที่คือ Create Read Update Delete ก็ย่อมันเป็น CRUD ซะเลย ซึ่ง CRUD เป็น function พื้นฐานสำหรับเว็บทั่วๆ ไปที่ต้องต่อกับ database ซึ่ง CRUD กับ verb 4 ตัวนี้ ช่างลงตัวได้อย่างพอดี จากการทำงานมา 3-4 ชิ้นกับ Rails รู้สึกว่าชีวิตง่ายขึ้นเยอะ เมื่อใช้ simply_helpful ในการจัดการกับ CRUD

แล้วเราจะได้อะไรจากตัว RESTful ??? จากที่อ่านๆ มา และศึกษามา มันมีอะไรเยอะแยะไปหมด แต่เนื่ององค์ความรู้ที่มีอยู่ ยังไปไม่ถึง เลยเอาแค่ที่เจอมากับตัวละกันครับ

นอกเหนือจากสิ่งที่ได้จากการเป็น REST แล้ว อย่างแรกเลยคือ เราจะได้ ​URL ที่ดูเท่ อย่างที่สอง ลด url mapping ใน route.rb ได้เยอะเลย ดูในตัวอย่างนี้ได้ อย่างที่สาม สำหรับ get เราแน่ใจได้แน่นอน ว่า get ของเรา ทำหน้าที่ไว้แค่ดู จะไปเปลี่ยนแปลงอะไรไม่ได้ เพราะจะไม่มีการใช้ get ในการเปลี่ียนแปลงข้อมูล ปลอดภัยขึ้นมาหน่อย อย่างที่สี่ ทำงานร่วมกับ CRUD ได้อย่างง่ายดาย อย่างที่ห้า ไว้คิดออกแล้วจะมาเพิ่มทีหลังครับ

ก็อย่างที่ว่าไปตั้งแต่แรก มันเป็นแค่แนวคิด โชคดีที่ Rails มีเจ้า plugin ตัวนี้(จริงๆ มันไม่ใช่ plugin แล้วครับ มันถูกเพิ่มเข้าไปแล้วใน edge rails) เลยทำให้ชีวิตนี้ง่ายขึ้นมาอีกหน่อย

ข้อมูลจาก http://microformats.org/wiki/rest/rails http://cfis.savagexi.com/articles/2006/03/26/rest-controller-for-rails

ปล. มีตัวอย่างอีกอันครับ สำหรับผู้ที่สนใจ REST ผมว่าเขาอธิบายได้เข้าท่าเลยทีเดียว คุณ Ryan Tomayko เขาอธิบาย REST ให้เมียเขาฟัง ลองอ่านดูครับ
ปอ. ได้คุยกับพี่ patrickz เกี่ยวกับ REST ที่ใช้งานจริงๆ กับ WS น่าสนุกมากครับ ไว้จะมาว่าให้ฟังทีหลัง

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

0 comments | Filed Under: Ruby | Tags: simply_restful

codegent: we're hiring