Archive for มกราคม 2009
ArgumentError ตอน render partials
จริงๆก็ไม่ใช่ปัญหาจาก Rails แต่เป็นเพราะความมือใหม่ไม่รู้วิธีใช้งานมากกว่า…ในหน้า 246 ของ RailsSpace ได้แนะนำการใช้ partials เป็นครั้งแรกโดยให้ตัวอย่างโค้ดใน partials มาแบบนี้
<div class="form_row"> <label for="email">Email:</label> <%= form.text_field :email, :size => User::EMAIL_SIZE, :maxlength => User::EMAIL_MAX_LENGTH %> </div>
พอลองทดสอบในเบราเซอร์ก็เจอ error เข้าเต็มๆว่า
ArgumentError in User#register
Showing app/views/user/register.html.erb where line #15 raised: wrong number of arguments (0 for 1)
ปัญหาเกิดจากการที่เราส่ง argument ที่ไม่มีอยู่ใน template ไปให้ partials…นี่คือโค้ดบรรทัดที่ทำให้เกิดข้อผิดพลาด
<%= render :partial => "email_field_row", :locals => { :form => form } %>
จะเห็นว่าเราส่งตัวแปรชื่อ form ไปให้ partial แต่ว่าตัวแปรนี้มันไม่มีอยู่ใน template…เหตุผลที่ไม่มีก็เพราะว่า snippet ใน TextMate ที่ใช้อยู่มันตั้งชื่อตัวแปรสำหรับ form_for ว่า f นั่นเอง (form_for :user do |f|) เพราะงั้นจึงต้องปรับแก้นิดหน่อย โดยแก้ไขโค้ดที่เรียก partial เป็น
<%= render :partial => "email_field_row", :locals => { :form => f } %>
เท่านี้ Rails ก็ render ได้เรียบร้อย ซึ่งจริงๆก็มีวิธีแก้อีกวิธีที่ง่ายกว่า คือไม่ต้องส่ง :locals ไปให้ partial ด้วยซ้ำ ให้เรียกใช้ method ใน FormHelper ตรงๆใน partials เลย เช่น
<div class="form_row">
<label for="email">Email:</label>
<%= text_field :user,
:email,
:size => User::EMAIL_SIZE,
:maxlength => User::EMAIL_MAX_LENGTH %>
</div>
และเปลี่ยนโค้ดใน template เป็น
<%= render :partial => "email_field_row" %>
เหตุผลที่โค้ดนี้ทำงานได้นั่นก็เพราะ partials มองเห็นตัวแปรที่อยู่ใน template ที่เรียกใช้มันอยู่แล้ว เราจึงไม่จำเป็นต้องให้ Rails สร้างตัวแปรใน local scope ใหม่ (เท่าที่อ่านเจอ เหตุผลที่เราจะสร้างตัวแปรใน local scope ก็ต่อเมื่อ partials นั้นจะถูกใช้ในหลายๆที่ของโปรแกรม ซึ่งอาจจะยากที่จะควบคุมชื่อตัวแปรได้ เลยใช้ :locals เพื่อกำหนดชื่อตัวแปรมาตรฐานของ partials เลย)
Update: ด้วยเหตุผลดลใจบางอย่าง เราต้องส่ง :user ไปให้ text_field กรณีที่ไม่ได้ใช้ :locals hash…เหมือนว่าถ้าเราส่ง :locals แล้ว text_field จะสามารถรู้ได้โดยอัตโนมัติว่าโมเดลที่ form ทำงานอยู่คือ :user แต่ถ้าไม่ส่งจะไม่รู้ เราจึงต้องระบุแบบ explicit
FetchError ตอนพยายามติดตั้ง Thin
ตอนพยายามติดตั้ง Thin เจอ error ว่า
tiknb:~ Tik$ sudo gem install thin
Building native extensions. This could take a while...
ERROR: While executing gem ... (Gem::RemoteFetcher::FetchError)
SocketError: getaddrinfo: nodename nor servname provided, or not
known (http://gems.rubyforge.org/gems/daemons-1.0.10.gem)
เผอิญว่าในเครื่องยังไม่มีอะไรเลย ไม่ว่าจะเป็น Rack, Mongrel, และ Thin เลยติดตั้งผ่าน gem ตามลำดับก็แก้ไขได้ เพราะว่า Mongrel มี dependency ที่ Thin ก็ใช้เหมือนๆกัน และ gem ของ mongrel แลจะอยู่ในสภาพดีกว่า Thin เล็กน้อยเลยทำให้หมดปัญหาเรื่องหา dependency ไม่เจอได้โดยอัตโนมัติ
NoMethodError เมื่อเซ็ตค่า :authorization_token
ในหน้า 193 ของ RailsSpace มีโค้ดสำหรับเซ็ตค่าคุ๊กกี้ :authorization_token แต่หลังจากใช้โค้ดตามหนังสือจะเกิด error นี้ขึ้น
NoMethodError in UserController#login
private method `gsub' called for 1:Fixnum
ลอง search ดูใน Google Groups มีคนโพสต์ไว้ว่าวิธีแก้ง่ายๆคือใช้ to_s เพื่อแปลง authorization_token (ที่จริงๆแล้วคือ user.id เป็นตัวเลข) ให้เป็น string
สำหรับรายละเอียดเพิ่มเติมลองดูได้ที่บล๊อกของ RailsSpace
InvalidAuthenticityToken in User#register
ในบทที่ 6 หลังจากใช้คำสั่ง rake db:migrate เพื่อสร้าง session store ในฐานข้อมูล แล้วเปิดหน้า Register จะจ๊ะเอ๋กับ error นี้
ActionController::InvalidAuthenticityToken in User#register
No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store).
วิธีแก้ง่ายนิดเดียว อ่านเจอใน Google Groups ว่าเราต้องเอาเครื่องหมาย comment หน้า :secret ของบรรทัด protect_from_forgery ใน application.rb ออก บรรทัดที่มากับ Rails 2.2 จะเป็นประมาณนี้
protect_from_forgery #:secret => '...'
พอเอา # ออกหน้าจอ Register ก็จะทำงานได้ตามปกติ
rake stat กับ test syntax ใหม่ใน Rails 2.2
พอลองใช้ test syntax แบบใหม่แล้วรันคำสั่ง rake stats หลังจบบท 5 ของ RailsSpace จะได้ผลตามนี้
+----------------------+-------+-------+---------+---------+-----+-------+ | Name | Lines | LOC | Classes | Methods | M/C | LOC/M | +----------------------+-------+-------+---------+---------+-----+-------+ | Controllers | 44 | 30 | 3 | 5 | 1 | 4 | | Helpers | 12 | 10 | 0 | 1 | 0 | 8 | | Models | 34 | 28 | 1 | 1 | 1 | 26 | | Libraries | 0 | 0 | 0 | 0 | 0 | 0 | | Integration tests | 0 | 0 | 0 | 0 | 0 | 0 | | Functional tests | 118 | 88 | 4 | 2 | 0 | 42 | | Unit tests | 150 | 112 | 1 | 1 | 1 | 110 | +----------------------+-------+-------+---------+---------+-----+-------+ | Total | 358 | 268 | 9 | 10 | 1 | 24 | +----------------------+-------+-------+---------+---------+-----+-------+
ซึ่งดูเหมือน task stats นี้จะไม่เข้าใจ syntax แบบใหม่นี้ซักเท่าไร จริงๆแล้วจำนวน method ใน Functional tests ต้องเป็น 10 และจำนวน method ใน Unit tests ต้องเป็น 13
Error เกี่ยวกับ error message :too_short ใน UserTest
ใน Listing 5.17 (หน้า 117) ของ RailsSpace เขียน assertion ของ test ไว้แบบนี้
def test_screen_name_minimum_length user = @valid_user min_length = User::SCREEN_NAME_MIN_LENGTH # Screen name is too short. user.screen_name = "a" * (min_length - 1) assert !user.valid? # Format the error message based on minimum length. correct_error_message = sprintf(@error_messages[:too_short], min_length) assert_equal correct_error_message, user.errors.on(:screen_name) ... end
แต่พอ run test ใน Rails 2.2 จะเจอคำเตือนพร้อม assertion failure ว่า
DEPRECATION WARNING: ActiveRecord::Errors.default_error_messages has been deprecated.
Please use I18n.translate('activerecord.errors.messages')..
(called from default_error_messages at /..lib/active_record/validations.rb:24)
...
Finished in 0.528027 seconds.
1) Failure:
test_screen_name_minimum_length(UserTest)
[./test/unit/user_test.rb:41:in `test_screen_name_minimum_length'
/opt/../lib/active_support/testing/setup_and_teardown.rb:60:in `__send__'
/opt/../lib/active_support/testing/setup_and_teardown.rb:60:in `run']:
<"is too short (minimum is {{count}} characters)"> expected but was
<"is too short (minimum is 4 characters)">.
4 tests, 10 assertions, 1 failures, 0 errors
สำหรับ warning แรกบอกว่า ActiveRecord::Errors.default_error_messages กำลังใกล้ถูกปลดระวางแล้ว ให้เปลี่ยนไปใช้ i18n support ที่เพิ่งมีใน Rails 2.2 ซึ่งปัญหานี้แก้ไขได้ไม่ยาก แค่แก้ setup ให้ดึง error messages แบบนี้
def setup
@error_messages = I18n.translate('activerecord.errors.messages')
end
เท่านี้ warning ก็จะหายไปเรียบร้อย แต่ปัญหาที่สองก็ยังอยู่ นั่นคือ assertion failure
ปัญหานี้เกิดจากการที่ Rails 2.2 เปลี่ยน syntax การแทนค่าใน error message จากการใช้ %d/%s เป็นชื่อตัวแปรแบบ explicit ชื่อ {{count}} และ {{value}} แทน (ดู commit)…แต่วิธีการแก้ก็ค่อนข้างง่ายคือใช้ hash สำหรับ interpolate ค่าใน message ผ่าน i18n แบบนี้
correct_error_message = I18n.t 'activerecord.errors.messages.too_short',
:count => min_length
assert_equal correct_error_message, user.errors.on(:screen_name)
เท่านี้ test เราก็จะผ่านฉลุย
ลองอ่านวิธีการใช้งานเพิ่มเติมดูที่ RoR i18n core API ได้ครับ
ปล. Rails มี shorthand สำหรับ method translate คือ t เพราะฉะนั้นแทนที่จะเขียน I18n.translate เราสามารถใช้ I18n.t ก็ได้ สั้นกว่าแต่อาจจะไม่เคลียร์เท่าแบบแรก
test_helper (LoadError) ใน Rails 2.2
ถ้าเราลองใช้คำสั่ง test ตามตัวอย่าง RailsSpace ในหน้า 100 ใน Rails 2.2 จะเจอ error แบบนี้
tiknb:rails_space Tik$ ruby test/functional/site_controller_test.rb test/functional/site_controller_test.rb:1:in `require': no such file to load -- test_helper (LoadError) from test/functional/site_controller_test.rb:1
ดูเหมือนว่า Rails 2.2 จะชอบให้เราใช้ rake task เพื่อ run test มากกว่า ถ้าเราเปลี่ยนเป็นแบบนี้ก็จะไม่มีปัญหา
tiknb:rails_space Tik$ rake test:functionals ..
Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader Started .. Finished in 0.151827 seconds.
บิงโก เท่านี้ก็เรียบร้อย
Controller testing ใน Rails 2.2
ในหนังสือ RailsSpace หน้า 100 มีโค้ด test ที่ generate โดย Rails ประมาณว่า
class SiteControllerTest < Test::Unit::TestCase def setup @controller = SiteController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new end # Replace this with your real tests. def test_truth assert true end end
แต่ Rails 2.2 สร้างโค้ดให้เราแบบนี้
class SiteControllerTest < ActionController::TestCase # Replace this with your real tests. test "the truth" do assert true end end
สิ่งที่ต่างไปคือแบบหลังไม่มีการ declare setup แล้วเพราะว่า ActionController::TestCase จะสร้าง @controller, @request, และ @response ให้เราโดยอัตโนมัติตั้งแต่ Rails 2.0
อีกอย่างนึงที่ต่างไปใน 2.2 คือ syntax ใหม่ที่ดูคล้ายๆ Rspec สำหรับแต่ละ test case นั่นเอง โดย syntax ใหม่นี้เป็นฟีเจอร์ใหม่ที่เรียกว่า declarative block syntax สำหรับเขียน test (ดู commit)
ความแตกต่างระหว่าง link_to และ button_to
ทั้งสองต่างกันตรง HTTP request method ที่ส่งโดย method ทั้งสอง
link_to จะใช้ GET ส่วน button_to จะใช้ POST