Tik’s Blog

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

Archive for มกราคม 2009

ArgumentError ตอน render partials

without comments

จริงๆก็ไม่ใช่ปัญหาจาก 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

Written by sukita

มกราคม 26, 2009 at 11:54 pm

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

Tagged with

FetchError ตอนพยายามติดตั้ง Thin

without comments

ตอนพยายามติดตั้ง 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 ไม่เจอได้โดยอัตโนมัติ

Written by sukita

มกราคม 20, 2009 at 10:27 pm

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

Tagged with

NoMethodError เมื่อเซ็ตค่า :authorization_token

without comments

ในหน้า 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

Written by sukita

มกราคม 15, 2009 at 11:11 pm

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

Tagged with

InvalidAuthenticityToken in User#register

without comments

ในบทที่ 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 ก็จะทำงานได้ตามปกติ

Written by sukita

มกราคม 11, 2009 at 10:36 pm

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

Tagged with

rake stat กับ test syntax ใหม่ใน Rails 2.2

without comments

พอลองใช้ 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

Written by sukita

มกราคม 7, 2009 at 9:49 pm

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

Tagged with

Error เกี่ยวกับ error message :too_short ใน UserTest

without comments

ใน 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 ก็ได้ สั้นกว่าแต่อาจจะไม่เคลียร์เท่าแบบแรก

Written by sukita

มกราคม 7, 2009 at 8:09 pm

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

Tagged with

test_helper (LoadError) ใน Rails 2.2

with 2 comments

ถ้าเราลองใช้คำสั่ง 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.

บิงโก เท่านี้ก็เรียบร้อย

Written by sukita

มกราคม 5, 2009 at 8:52 pm

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

Tagged with

Controller testing ใน Rails 2.2

without comments

ในหนังสือ 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)

Written by sukita

มกราคม 5, 2009 at 8:44 pm

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

Tagged with

ความแตกต่างระหว่าง link_to และ button_to

without comments

ทั้งสองต่างกันตรง HTTP request method ที่ส่งโดย method ทั้งสอง

link_to จะใช้ GET ส่วน button_to จะใช้ POST

Written by sukita

มกราคม 4, 2009 at 3:54 pm

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

Tagged with