Tik’s Blog

ร้อยแปดพันเก้า

Archive for มีนาคม 2009

แก้ unit test failure ใน RailsSpace หลังอัพเกรดเป็น Rails 2.3

with one comment

โพสก่อนหน้านี้จบด้วยการเจ๊งของ unit test และก็ไปอาบน้ำนอน

วันนี้เลยลองหาต้นเหตุดูว่าปัญหาเกิดจากอะไร ได้ความว่าต้นเหตุของปัญหาเกิดจาก 2 method ใน user_controller_test เท่านั้นเอง คือ cookie_expires กับ cookie_value

ปัญหาแรกคือ

1) Error:
test_login_success_with_remember_me(UserControllerTest):
NoMethodError: undefined method `expires' for "1":String
/test/functional/user_controller_test.rb:268:in `cookie_expires'
/test/functional/user_controller_test.rb:145:in
`test_login_success_with_remember_me'

ซึ่งเกิดเพราะว่าใน Rails 2.3 นั้นการเรียกใช้ cookies[symbol.to_s] จะไม่คืน cookie object แล้วแต่ว่าคืนค่าของคุ๊กกี้นั้นๆเลย (ลองดู API)  ทำให้เราไม่สามารถใช้ expires ได้แล้ว

ที่ทำให้ซับซ้อนยิ่งกว่านั้น (และยังหาทางลัดใน Rails ไม่เจอ) คือเราเข้าถึงคุ๊กกี้ผ่าน @response.headers["Set-Cookie"] ได้เท่านั้น และค่าที่คืนมาจาก header นี้เป็น array ของ string ที่แต่ละ element แทนคุ๊กกี้หนึ่งอัน  เช่น

(rdb:1) @response.headers["Set-Cookie"]
["remember_me=1; path=/; expires=Wed, 19-Mar-2014 16:29:09 GMT", "authorization_token=56d1799d501a6ea5f0e612d22ddba211dc4066ae; path=/; expires=Wed, 19-Mar-2014 16:29:09 GMT"]

ในกรณีนี้สิ่งที่เราต้องการ (expires) อยู่ที่ element แรกของ array  ซึ่งเราสามารถแก้โค้ดเป็นแบบนี้ได้

def cookie_expires(symbol)
Time.zone.parse(@response.headers["Set-Cookie"][0].split("\;")[2].split("=")[1])
end

ไม่ได้เขียนให้อ่านง่ายมากนัก และก็ไม่ค่อยยืดหยุ่นด้วย แต่ดูเหมือนจะใช้งานได้  สิ่งที่เราทำก็คือเอา item แรกของ array ซึ่งตรงกับคุ๊กกี้ที่เราต้องการ (remember_me) มาแบ่ง string ด้วยเครื่องหมาย semicolon ก่อน แล้วก็เอา item สุดท้ายจากที่แบ่งได้ (expires) มาแบ่งด้วยเครื่องหมายเท่ากับอีกทีเพื่อเอาค่าของ expires มาแปลงเป็นเวลาเพื่อเปรียบเทียบ

แน่นอนว่าอ๊อบเจ็คต์ที่เราคืนกลับไปอาจไม่ใช่เวลาที่ตรงกับเวลาของคุ๊กกี้ที่เราเซ็ตค่าไว้ผ่านคอนโทรลเลอร์ แต่ว่าก็ยังอยู่ใน range การเปรียบเทียบของ test ได้ ซึ่งเพียงพอจะทำให้ผ่านการทดสอบ

อีกปัญหานึงที่เกิดกับ cookie_value ก็ด้วยเหตุผลเดียวกันกับที่เขียนไว้ด้านบน คือการ access hash คุ๊กกี้นั้นจะคืนค่าของคุ๊กกี้เลย ไม่ได้คืน array อีกต่อไป ทำให้เราต้องแก้เป็น

def cookie_value(symbol)
cookies[symbol.to_s]
end

หลังจากแก้ไขเรียบร้อยก็ test อีกทีเพื่อความแน่ใจ

ปล. เดาว่าการที่เราต้องเข้าถึงคุ๊กกี้ผ่าน “Set-Cookie” น่าจะเป็นผลจากการที่ Rails 2.3 เปลี่ยนมาสนับสนุน Rack เต็มรูปแบบแล้วในแบบของ middleware จึงทำให้โครงสร้างของเฟรมเวิร์คเปลี่ยนไปเก็บคุ๊กกี้ที่ HTTP request/response แทน

Written by sukita

มีนาคม 19, 2009 at 11:46 pm

บันทึกโพสใน Programming

Tagged with

อัพเกรด RailsSpace เป็น Rails 2.3

without comments

เนื่องจาก Rails 2.3 เพิ่งออกเมื่อสองวันที่แล้ว เลยอัพเกรด RailsSpace ไปใช้เวอร์ชั่นใหม่ซะเลย วิธีอัพเกรดก็ง่ายๆ

> sudo gem install rails
> cd rails_space
> rake rails:update

หรือจะลองดูในวิกิก็ได้  จากนั้นก็ทำการแก้ไขเวอร์ชั่นใน environment.rb ให้เป็น

RAILS_GEM_VERSION = '2.3.2' unless defined? RAILS_GEM_VERSION

แล้วก็ลองเล่นๆดู ในหน้าเว็บก็ดูปกติดี แต่ว่ามีเตือนเรื่องโค้ดปลดระวางนิดหน่อย คือ

DEPRECATION WARNING: protect_from_forgery only takes : only and :except
options now. :digest and :secret have no effect.
(called from ../rails_space/app/controllers/application_controller.rb:12)

วิธีแก้ก็คือ comment โค้ดบรรทัด protect_from_forgery ออกจาก application_controller.rb แล้วคำเตือนก็จะหายไป

ขั้นตอนต่อไปคือลองรันเคสทดสอบ เพื่อดูว่า unit test เรายังอยู่ดีหรือเปล่า

> rake

ปรากฏว่าเจ๊งบางอันว่า

./test/test_helper.rb:22: undefined method `use_transactional_fixtures='
for Test::Unit::TestCase:Class (NoMethodError)

นั่นคงเป็นเพราะว่า Rails เปลี่ยนมาใช้ ActiveSupport::TestCase แทน Test::Unit แล้ว เราจึงต้องแก้ไข test_helper.rb จาก

class Test::Unit::TestCase

เป็น

class ActiveSupport::TestCase

จากนั้นก็ลองรันเทสดูอีกครั้ง ซึ่งคราวนี้สามารถรันได้จนจบกระบวนความ แต่ว่ามี error ใน functional test สามอันคือ

  1) Error:
test_login_success_with_remember_me(UserControllerTest):
NoMethodError: undefined method `expires' for "1":String
    /test/functional/user_controller_test.rb:268:in `cookie_expires'
    /test/functional/user_controller_test.rb:145:in
      `test_login_success_with_remember_me'

  2) Error:
test_logout_clears_session_and_cookie(UserControllerTest):
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.first
    /test/functional/user_controller_test.rb:272:in `cookie_value'
    /test/functional/user_controller_test.rb:191:in
      `test_logout_clears_session_and_cookie'

  3) Error:
test_test_a_valid_login(UserControllerTest):
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.first
    /test/functional/user_controller_test.rb:272:in `cookie_value'
    /test/functional/user_controller_test.rb:125:in `test_test_a_valid_login'

อา ดึกแล้ว ไว้ค่อยลองแก้วันหลังละกัน

Written by sukita

มีนาคม 17, 2009 at 11:18 pm

บันทึกโพสใน Programming

Tagged with

SyntaxError in Community#index ใน RailsSpace

without comments

ตามโค้ดใน Listing 11.1.1 ของ RailsSpace (หน้า 328) เขียนโค้ดแบบนี้

<% form_tag({ :action => "search" }, :method => "get") do %>
  ...
<% end %>

แต่ว่าถ้าเราเขียนตามตัวอย่างใน Rails 2.2 เราจะเจอ error ว่า

SyntaxError in Community#index
Showing app/views/community/_search_form.html.erb where line #1 raised:
compile error
/Users/Tik/temp/rails_space/app/views/community/_search_form.html.erb:1: syntax error, unexpected tASSOC, expecting '}'
...e=true ;  form_tag {:action => "search"}, :method => "get" d...
                              ^
/Users/Tik/temp/rails_space/app/views/community/_search_form.html.erb:1: syntax error, unexpected ',', expecting kEND
...orm_tag {:action => "search"}, :method => "get" do ; @output...

วิธีแก้ก็ง่ายๆ แค่เอา :action ออกจาก hash ก็เรียบร้อย เพราะดูเหมือนว่า Rails 2.2 ไม่ต้องการ hash สำหรับ :action แล้ว

Written by sukita

มีนาคม 17, 2009 at 9:53 pm

บันทึกโพสใน Programming

Tagged with

Result Summary สำหรับ will_paginate ใน RailsSpace

with one comment

ต่อจากโพสต์ที่แล้ว ใน Listing 10.19 มีใช้ item_count, current_page, first_item, และก็ last_item ซึ่งเราไม่มีใน Rails 2.2

การเปลี่ยนโค้ดให้ทำงานกับ will_paginate ก็ไม่ยากนัก เพราะว่า array ที่คืนมาโดย will_paginate ก็มี method พิเศษต่างๆเช่นกันคือ total_pages, total_entries, current_page, และก็ offset เพื่อแทนค่าจำนวนหน้าทั้งหมด, จำนวน item ทั้งหมดในผลลัพธ์, เลขที่หน้าปัจจุบัน, และก็ index ของ item แรกของหน้า (อย่าลืมว่า index ของ array เริ่มต้นที่เลขศูนย์ เพราะฉะนั้นเราต้องบวก 1 ตอนแสดงผล)

โค้ดที่แทนก็กลายเป็น​:

  Found <%= pluralize(@specs.total_entries, "match") %>.
  <% if @specs.total_pages > 1 %>
    <% first_item = @specs.offset + 1 %>
    <% last_item = @specs.offset + @specs.length %>
    Displaying users <%= first_item %>–<%= last_item %>
  <% end %>

เท่านี้ก็เรียบร้อย (เนื่องจากว่า will_paginate ไม่มี first_item และ last_item เราจึงต้องคำนวนเอง)

อัพเดท: เพิ่งเปิดเจอลิงก์ของ RailsSpace เมื่อปีที่แล้วที่อธิบายการแปลงเป็น will_paginate  คล้ายๆกัันแต่เจ๋งกว่าตรงที่โค้ดของลิงก์นี้ย้าย first_item, last_item, และ paginated ไปไว้ใน WillPaginate class เลย เจ๋ง…ลืมไปว่ากำลังทำงานกับ Ruby อยู่ :p

Written by sukita

มีนาคม 12, 2009 at 10:36 pm

บันทึกโพสใน Programming

Tagged with

RailsSpace Pagination ใน Rails 2.2

without comments

ใน Listing 10.16 ของ RailsSpace เริ่มแนะนำการใช้งาน pagination หรือการแบ่งหน้าของผลลัพธ์ที่มาจากการ find หา record ในฐานข้อมูล แต่ว่าโค้ดดังกล่าวใช้ไม่ได้ใน Rails 2.2 และเราจะเจอ error แบบนี้:

NoMethodError in CommunityController#index
undefined method `paginate' for #<CommunityController:0x2532e7c>

ปัญหานี้เกิดขึ้นจากการที่ paginate ถูกย้ายออกไปเป็นปลั๊กอินแล้วตั้งแต่ Rails 2.0 ซึ่งหมายความว่าเราไม่มี method นี้ให้ใช้ใน 2.2

เลยลองหาดูว่ามีตัวเลือกอะไรนอกจาก classic_pagination ที่ระบุไว้ในเอกสารอ้างอิงบ้าง หาไปหามาก็เจอสองตัวเลือกคือ will_paginate และ paginator ซึ่งทั้งคู่เป็น gem ที่เราสามารถติดตั้งแล้วเอามาใช้ทดแทน paginate ใน Rails 2.0 ได้

เนื่องจากว่าในวิกิมีขั้นตอนแนะนำการติดตั้งอยู่ เลยลองใช้ will_paginate แทนดูซะ เพราะมีตัวอย่างอยู่ด้วย แรกๆลองใช้งานก็งงเล็กน้อย แต่ก็ถึงบางอ้อในเวลารวดเร็วเพราะใช้งานไม่ยากเลย คล้ายๆกับตัว paginate ที่มากับ Rails 2.0 แต่ง่ายกว่าซะด้วยซ้ำ

เจ้า will_paginate นี้จะทำการเพิ่ม method paginate ที่ทำงานได้เหมือนๆกับ find ของ ActiveRecord  แค่คุณแทน find ด้วย paginate แล้วก็ส่ง argument ที่ method นี้ต้องการ ซึ่งก็คือเลขที่หน้าและจำนวนข้อมูลที่แสดงในแต่ละหน้า (ถ้าคุณไม่ระบุ will_paginate จะแสดง 30 record ในแต่ละหน้า)

ลองดูตัวอย่างทดแทน Listing 10.16 กัน:

  def index
    ...
    if params[:id]
      @initial = params[:id]
      @specs = Spec.paginate(:conditions => ["last_name LIKE ?", @initial+'%'],
                             : Read the rest of this entry »

Written by sukita

มีนาคม 12, 2009 at 10:07 pm

บันทึกโพสใน Programming

Tagged with