Tik’s Blog

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

Posts Tagged ‘rails

ArgumentError in Community#search ใน RailsSpace

without comments

สงครามยังไม่จบอย่าพึ่งนับศพทหาร…แก้บั๊กที่แล้วไปแป๊ปเดียว เจออันใหม่อีกแล้ว

หลังจากเราแก้ไขให้ will_paginate ทำงานได้ ปรากฏว่าพอทำตาม Listing 11.9 หน้า 336 ก็เจอปัญหาใหม่ ประมาณว่าพอเราค้นหาคำปุ๊ปจะเจอ error ว่า

  ArgumentError in Community#search
  Showing app/views/community/_user_table.html.erb where line #16 raised:
  The @community variable appears to be empty. Did you forget to pass the collection
    object for will_paginate?

จริงๆแล้วโค้ดตรงนี้ก็ไม่ใช่ปัญหาเท่าไร  ปัญหาจริงๆอยู่ที่ว่าเราต้องใช้โค้ดใน Listing 11.10 เพื่อเพิ่ม method ให้ทำงานได้ แต่ว่า Listing 11.10 มันเข้ากันไม่ได้กับ will_paginate และ Paginator ก็ไม่มีใน Rails 2.3 แล้วด้วย เพราะงั้นเราต้องเขียนโค้ดทดแทนที่ทำงานกับ will_paginate ได้แทน

คุ้ยๆโค้ด will_paginate ดู ได้โค้ดทดแทนก็ประมาณนี้ (โค้ดค่อนข้างห่วย แต่เวิร์คไปก่อน + โค้ดนี้เป็นโค้ดต่อท้ายใน method search ของ community_controller.rb เพราะยังไม่รู้จะทำให้เป็น generic ยังไง)

  def search
    @title = "Search RailsSpace"
    if params[:q]
       ...

      # Now combine into one list of distinct users sorted by last name.
      ...

      # Manual pagination.
      hit_specs = @users.collect { |user| user.spec }
      page = params[:page] || 1
      @specs = WillPaginate::Collection.create(page, 5, hit_specs.count) do |pager|
        pager.replace(hit_specs)
      end
      @users = WillPaginate::Collection.create(page, 5, @users.count) do |pager|
        pager.replace(@users)
      end
      @specs = @specs.paginate(:page => page, :per_page => 5)
      @users = @users.paginate(:page => page, :per_page => 5)
    end
  end

เท่านี้ก็เรียบร้อย กล้อมแกล้มให้ทำงานได้ไปก่อน ค่อยหาวิธีทำให้โค้ดสวยขึ้นเมื่อความรู้เพิ่มขึ้น

อธิบายโค้ดคร่าวๆ:

  • สร้าง @specs ขึ้นมาเพราะ _user_table partial ต้องใช้ส่งให้ will_paginate และเซ็ตค่าให้เป็น specs เฉพาะในหน้าที่ระบุเท่านั้น (เพราะ first_item และ last_item ใน _result_summary partial ต้องใช้)
  • สร้าง @users และเซ็ตค่าให้เป็นเฉพาะ users ที่อยู่ในหน้าที่ระบุเท่านั้นเพราะ _user_table partial ต้องใช้ คล้ายๆกับ @specs แหละ
  • method create ของ WillPaginate::Collection จะทำการสร้าง collection ใหม่ที่จะครอบ @specs/@users โดยสนับสนุนการแบ่งหน้าในตัว (method paginate ก็เป็น method ที่ define ใน will_paginate gem)

Written by sukita

เมษายน 8, 2009 at 10:13 pm

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

Tagged with

ผจญภัยใน will_paginate เพื่อเพิ่ม first_item ใน RailsSpace (ภาค 3)

without comments

ต่อจากตอนที่แล้ว…ว่า Rails เหมือนกับจะไม่โหลด class WillPaginate::Collection จาก gem ขึ้นมาเพื่อ extend definition ที่เราสร้างไว้ใน lib/will_paginate/collection.rb  เลยทดลองเปลี่ยนโค้ดโดยใช้ class_eval แทน

WillPaginate::Collection.class_eval do
  def first_item
    offset + 1
  end
end

เพราะคิดว่าการเรียกใช้ class จะทำให้ Rails โหลด gem ให้ แต่วิธีนี้มีสองปัญหา คือหนึ่ง Rails  ไม่ได้โหลด gem ให้และสอง การที่ไม่ได้ define class แบบ class Collection ทำให้ Rails ยิง error ว่า

Expected /Users...rails_space/lib/will_paginate/collection.rb to define
  WillPaginate::Collection
/opt/.../activesupport-2.3.2/lib/active_support/dependencies.rb:426:in
  `load_missing_constant'

จาก stack trace เราสรุปได้ว่า Rails  รับผิดชอบในการโหลด dependencies ต่างๆ ไม่ใช่ Ruby เพราะฉะนั้น Rails สามารถที่เลือกโหลดหรือไม่โหลดไฟล์ได้  ข้อสังเกตุที่สองคือวิธีการโหลด Collection class นั้นต่างกับวิธีการโหลด class อื่นๆที่มาจาก gem  ของ Collection class เป็นแบบนี้

/...activesupport-2.3.2/lib/active_support/dependencies.rb:426:in
  `load_missing_constant'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:80:in
  `const_missing'
/...rails_space/lib/will_paginate/collection.rb:1
/...activesupport-2.3.2/lib/active_support/dependencies.rb:380:in
  `load_without_new_constant_marking'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:380:in
  `load_file'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:521:in
  `new_constants_in'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:379:in
  `load_file'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:259:in
  `require_or_load'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:425:in
  `load_missing_constant'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:80:in
  `const_missing'
/...rails_space/lib/will_paginate/collection.rb:1
/...rubygems/custom_require.rb:31:in `gem_original_require'
/...rubygems/custom_require.rb:31:in `require'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:156:in
  `require'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:521:in
  `new_constants_in'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:156:in
  `require'

ส่วนของ class อื่นๆจะประมาณนี้

/...gems/mislav-will_paginate-2.3.8/lib/will_paginate/array.rb:1
/...rubygems/custom_require.rb:31:in `gem_original_require'
/...rubygems/custom_require.rb:31:in `require'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:156:in
  `require'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:521:in
  `new_constants_in'
/...activesupport-2.3.2/lib/active_support/dependencies.rb:156:in
  `require'

จะเห็นได้ว่า แทนที่จะโหลด Collection class จาก gem จริงๆแล้ว Rails พยายามโหลด Collection มาจาก lib/will_paginate/collection.rb ของเราต่างหาก

หลังจากอุตส่าห์บ้าลองนู่นนี่อยู่นาน เพิ่งนึกได้ว่ามีหนังสือ The Rails Way อยู่ เลยลองเปิดดูบทแรก ปรากฏว่านี่มันเป็นพฤติกรรมที่มีมาตั้งแต่ Rails 1.2 แล้วนี่หว่า – -”

คือ Rails จะโหลด class หรือ module ที่ยังไม่ถูกโหลดขึ้นมาโดยอัตโนมัติ โดยพยายามหาจาก load_paths ที่ define ตอน Rails บู๊ตขึ้นมา สำหรับกรณีนี้ สรุปว่าปัญหาน่าจะเกิดจากการที่ /lib อยู่ใน load_paths ก่อน gem path นั่นเอง ลองดูได้จาก

$ script/console
>> $:

(คลื่นแทรก: ลอง google ดู rails auto-loading ปรากฏว่ามีคนเจอปัญหาแบบเดียวกันเดี๊ยเลย ที่นี่)

เอาล่ะ หลังจากบ้าๆบอๆมานาน ก็ถึงวิธีแก้ เพราะไม่อยากมีภาคต่อแล้ว มันยาวเกิน

วิธีแก้วิธีแบบแรกก็คือใช้โค้ดแบบในลิงก์คนที่เจอปัญหาแบบเดียวกันนี่แหละ เพิ่มโค้ดนี้ต่อท้ายสุดใน environment.rb

module WpCollectionExtensions
  def first_item
    offset + 1
  end
end

WillPaginate::Collection.instance_eval { include WpCollectionExtensions }

แบบทีสองคือใช้ after_initialize ของ config ใน environment.rb ประมาณว่าเพิ่มโค้ดนี้ก่อนจบ block ใน  environment.rb วิธีนี้คือการเพิ่ม hook ให้ Rails รันโค้ดใน block ที่ส่งไปหลัง initialize ทุกอย่างเสร็จเรียบร้อย

  config.after_initialize do
    class WillPaginate::Collection
      def first_item
        offset + 1
      end
    end
  end

จริงๆแล้วสองวิธีนี้มันก็ไม่ต่างกันเท่าไรหรอก เพราะว่ามันรอจน Rails บู๊ตจบกระบวนความแล้วก็เอาโค้ดมาใช้

วิธีที่สามคือสร้างไฟล์ไว้ใน config/initializers แล้วเอาโค้ดไปใส่ไว้ในนี้แทน เพราะ Rails โหลดไฟล์ที่มีนามสกุล .rb ใน initializers นี้ด้วย เช่น สร้างไฟล์ชื่อ config/initializers/extend_will_paginate.rb แล้วใส่โค้ดเดียวกันก็ได้

    class WillPaginate::Collection
      def first_item
        offset + 1
      end
    end

เป็นอันจบซีรี่ส์โคตรยาวซะที จริงๆคงมีวิธีอื่นอีก แต่แค่นี้ก็เวิร์คแล้ว ไม่รู้จะหามันเพิ่มไปทำไมอีก

Written by sukita

เมษายน 3, 2009 at 11:35 pm

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

Tagged with

ร้อยแปดพันเก้า ผจญภัยใน will_paginate เพื่อเพิ่ม first_item ใน RailsSpace (ต่อ)

without comments

ในโพสก่อนหน้า เนื่องจากง่วงเลยหยุดไว้ตรงการสร้าง class WillPaginate::Collection ไว้ใน lib/will_paginate/collection.rb ไว้ประมาณนี้:

class WillPaginate::Collection
  def first_item
    offset + 1
  end
end

ซึ่งการวิเคราะห์ตอนง่วงนอนก็เหมือนจะใกล้เคียงอยู่ครึ่งนึง แต่ตอนนั้นยังไม่เข้าใจว่าทำไมเราถึงไม่สามารถ extend class ที่อยู่ในปลั๊กอินที่เป็น gem ได้ เลยลองนั่งไล่โค้ดใน Rails 2.3 เพิ่มเติม ได้มาซึ่งความน่าจะเป็นใหม่ แต่ก่อนอื่นต้องลองลำดับความเข้าใจก่อน

ใน Ruby เราสามารถ define class หรือ method ทับ class/method ที่มีอยู่แล้วได้ เช่น ถ้าเราเขียนโค้ดแบบนี้

module My
  class Test
    def oops
      puts "oops"
    end
  end
end

obj = My::Test.new
obj.oops

class My::Test
  def oops
    puts "oops, i did it again"
  end
end

obj.oops # prints "oops, i did it again"

ผลลัพธ์ที่ได้คือ “oops, i did it again” เพราะว่า Ruby อนุญาติให้เรา override method ได้

ปัญหาคือ ถ้า Ruby อนุญาติให้เรา override class definition ได้ ทำไมเราถึงไม่สามารถทำได้ใน Rails ล่ะ? เพราะสิ่งที่เราทำก็แค่พยายาม redefine WillPaginate::Collection ที่มาจาก gem และเพิ่ม method ใหม่เข้าไปนี่?

คำถามแรกคือ ตอนที่ Rails process ไฟล์ collection.rb ที่เราสร้างขึ้นมานั้น will_paginate gem ได้ถูกโหลดขึ้นมาใช้หรือยัง? เพราะถ้ามันถูกโหลดขึ้นมาใช้งานแล้ว ในทางทฤษฏีเราน่าจะสามารถ override class definition ได้ เพราะฉะนั้นถ้าเราคอมเม้นต์โค้ดที่มีอยู่ทั้งหมดในไฟล์ collection.rb และเพิ่มบรรทัดนี้เข้าไป:

puts defined? WillPaginate::Collection

เราก็จะพบคำตอบว่า…will_paginate ยังไม่ถูกโหลดขึ้นมาใช้งาน ตอน Ruby process ไฟล์นี้ (ลองดูใน log ของ script/server หรือ script/console มันจะพิมพ์บรรทัด nil ซึ่งเป็นผลลัพธ์จากโค้ดบรรทัดนี้นั่นเอง)

นี่หมายความว่าถ้าเรา define WillPaginate::Collection ไว้ใน lib/will_paginate/collection.rb เราก็จะสร้าง class ใหม่ขึ้นมา โดย class นี้จะมีแค่ method first_item ที่เรา define เอาไว้ ทำให้เกิด NoMethodError ที่เกิดขึ้นในโพสก่อนหน้านี่เอง

สมมุติว่าเราสันนิฐฐานว่า Rails ไม่โหลด will_paginate ก่อน process ไฟล์ collection.rb มาลองอะไรเล่นๆกันอีกหน่อย เรามาลอง require ‘will_paginate’ กันใน collection.rb ก่อนจะ redefine class ดูว่าจะเกิดอะไรขึ้น

require 'will_paginate'
puts defined? Spec.paginate

class WillPaginate::Collection
  def first_item
    offset + 1
  end
end

หลังแก้โค้ดเมื่อเรารัน script/server ก็จะมี output เหมือนเดิมคือ nil และถ้าเปิดเบราเซอร์ไปที่หน้า community และเลือกซักตัวอักษรนึงก็จะเจอ error แบบเดียวกันในโพสที่แล้วเลย คือ

NoMethodError in CommunityController#index
  undefined method `create' for WillPaginate::Collection:Class

ดูเหมือนว่าการ require will_paginate ก็ไม่โหลด definition ของ Collection จาก gem แต่ปัญหาคือเราสามารถเรียกใช้งาน Spec.paginate ได้ตามปกติใน community_controller.rb ซึ่งหมายความว่า gem ต้องถูกโหลดขึ้นมาแล้วแน่นอน

สรุปข้อมูลที่พบคร่าวๆก่อน:

  1. เกิดการ process ไฟล์ lib/will_paginate/collection.rb ขึ้นตอน Rails initialize
  2. will_paginate ยังไม่ถูกโหลดขึ้นมาตอน Rails process ไฟล์ lib/will_paginate/collection.rb
  3. การ require ‘will_paginate’ ใน collection.rb ก็ไม่โหลด gem ขึ้นมา
  4. gem ถูกโหลดขึ้นมาใช้งานแล้วเมื่อเราเปิดหน้า community page ในเบราเซอร์ (เพราะมีการเรียก paginate method จาก Spec model ได้จริงๆ จึงเกิด error)
  5. gem ที่ถูกโหลดขึ้นมาไม่รู้จัก Collection class ที่ define อยู่ในตัว gem เอง แต่กลับมาใช้ Collection class ที่ define ใน lib/will_paginate/collection.rb

จุดสังเกตุที่ 4 และ 5 น่าสนใจมาก ตอนนี้สันนิฐฐานว่า Rails ไม่ได้โหลด definition จาก gem ถ้า class ถูก define ไว้แล้วในที่อื่นๆ (ในกรณีนี้คือใน lib/will_paginate/collection.rb) อาจจะเป็นเพราะ Rails 2.3 เพิ่มฟีเจอร์การ lazy load/autoload เพื่อประสิทธิภาพการทำงาน

ข้อสันนิฐฐานนี้มีความเป็นไปได้ตรงที่ class อื่นๆของ gem เช่น WillPaginate และ finder extension ก็ถูกโหลดขึ้นมาใช้งาน จะเว้นก็แค่ Collection class จาก gem ที่ไม่ถูกโหลดขึ้นมาเลยทำให้เกิด NoMethodError

สิ่งที่เหลือต้องยืนยันคือว่า จริงหรือเปล่าที่ autoload ใน Rails 2.3 จะไม่โหลด class ที่ถูก define ไว้แล้วซ้ำสองครั้ง ถึงแม้ว่าการโหลดครั้งที่สองจะโหลด class ที่มี method เพิ่มเติมด้วย?

ติดตามตอนต่อไป…

Written by sukita

เมษายน 2, 2009 at 11:49 pm

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

Tagged with

ผจญภัยใน will_paginate เพื่อเพิ่ม first_item ใน RailsSpace

with one comment

ในโพสก่อนหน้าเกี่ยวกับการแบ่งหน้าใน RailsSpace ได้เขียนอัพเดตต่อท้ายว่าไปเจอตัวอย่างของบล๊อก RailsSpace ที่เจ๋งกว่าเพราะว่าเอาโค้ด first_item, last_item, ฯลฯ ไปไว้ใน class WillPaginate เลย ทำให้ไม่ต้องสร้างตัวแปรพิเศษใน view

ปัญหามันอยู่ที่ว่าไอ้โค้ดตัวอย่างในไฟล์​ lib/will_paginate_ext.rb มันเหมือนจะไม่ถูกโหลดขึ้นมาใช้งานใน Rails 2.3 นี่สิ  ไม่รู้ว่าคนอื่นเจอปัญหาเดียวกันหรือเปล่าแต่ว่าถ้าเอาโค้ดไปอยู่ในไฟล์นี้แล้วเรียกใช้ first_item จะเจอปัญหาว่า:

NoMethodError: undefined method `first_item' for #<WillPaginate::Collection:0x291ec20>

เลยลองแก้วิธีแรกด้วยการเปลี่ยนชื่อไฟล์เป็น will_paginate.rb แต่เจอปัญหาใหม่ว่า

Expected /Users/Tik/temp/rails_space/lib/will_paginate.rb to define WillPaginate

อันนี้ก็พอเข้าใจได้ เพราะตาม convention แล้ว Rails คงคาดหวังให้เรามี class ชื่อ WillPaginate ในไฟล์ will_paginate.rb เลยลองใหม่ คราวนี้เปลี่ยนชื่อไฟล์เป็น collection.rb แต่พอเปลี่ยนชื่อแล้วก็กลับไปเจอปัญหาแรกอีก นั่นคือ NoMethodError

จากนั้นก็ลองย้ายไฟล์อีกที เพราะคิดว่า Rails อาจจะอยากให้มีไดเร็คทอรี่สำหรับ will_paginate ก็ได้ เลยย้ายไฟล์ไปที่ lib/will_paginate/collection.rb แทน หลังแก้ครั้งนี้ปรากฏว่าเจอปัญหาใหม่แทน:

NoMethodError: undefined method `create' for WillPaginate::Collection:Class
    from /opt.../mislav-will_paginate-2.3.8.../finder.rb:76:in `paginate'

ฮืมมม … จากอาการคร่าวๆ ดูแล้วน่าจะเกิดจากการที่เรา define class นี้ใน collection.rb ทำให้ Ruby ใช้ class นี้แทนสิ่งที่อยู่ใน will_paginate gem (เสมือนว่าเราสร้าง class ชื่อเดียวกันขึ้นมาเองอีกหนึ่ง class และ class ที่เราสร้างไม่มี method ต่างๆที่ will_paginate ต้องการ)

ปัญหาโลกแตกที่รอคอยการแก้ไขต่อไป…เนื่องจากดึกแล้วนั่นเอง

Written by sukita

เมษายน 1, 2009 at 11:50 pm

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

Tagged with

แก้ 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

NameError เมื่อเพิ่ม profile route ใน RailsSpace

without comments

เมื่อไรที่เจอ error เกี่ยวกับ route ว่า

NameError
undefined local variable or method `show' for main:Object
...
    config/routes.rb:38
    config/routes.rb:1

และถ้าลองรีเฟรชเบราเซอร์อีกทีหลัง error แรกก็จะเจอ error ที่สอง:

Routing Error
No route matches "/profile/foobar" with {:method => :get}

ให้ลองเช็ค syntax ของ route ของคุณใน routes.rb ดูดีๆ ว่าคุณได้ escape string ชื่อ action ด้วย ” หรือ “” แล้วหรือเปล่า…error นี้จะเกิดถ้าคุณใช้โค้ดผิดๆแบบนี้

map.connect ‘profile/:screen_name’, :controller => ‘profile’, :action => show

Written by sukita

กุมภาพันธ์ 7, 2009 at 10:54 pm

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

Tagged with