Tik’s Blog

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

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

leave a comment »

ในโพสก่อนหน้า เนื่องจากง่วงเลยหยุดไว้ตรงการสร้าง 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 ที่ 11:49 pm

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

Tagged with

ใส่ความเห็น