Showing posts with label rails. Show all posts
Showing posts with label rails. Show all posts

Monday, April 14, 2008

new migration

ตอนนี้ใน edge rails ได้มีการปรับปรุง migration แล้ว
จากของเดิมที่ migration จะใช้ เลข running version 1,2,3,...
ไปเป็น utc timestamp แทน เพื่อแก้ปัญหากรณี branch 2 branch ต่างคนต่างสร้าง migration ทำให้เลข version ชนกัน
สำหรับผมถือเป็นการปรับปรุงที่สำคัญมาก
เนื่องจากมันเป็นปัญหาที่กวนใจผมมานานมากแล้ว

ลองดูตัวอย่าง file ที่ script/generate migration สร้างให้

pphetra@mypann:~/projects/ruby/mg$ ls db/migrate/
20080414002052_create_users.rb 20080414002059_create_posts.rb


นอกจากนี้ยังได้เปลี่ยนแปลง table ที่ทำหน้าที่เก็บข้อมูล version
จาก schema_info ไปเป็น schema_migrations
โดยจะเปลี่ยนวิธีการเก็บ จากเดิมที่เก็บเฉพาะเลข current version
ไปเป็น เก็บทุก version ที่มีการ apply แล้ว

sqlite> select * from schema_migrations
...> ;
20080414002052
20080414002059


สุดท้ายก็คือมีการเพิ่ม rake task เข้ามาใหม่คือ
rake db:migrate:up กับ
rake db:migrate:down
โดย algorithm ที่ใช้ จะไม่เหมือนเดิม
จากเดิมเป็นการไล่เลข running ขึ้นลงเพียงอย่างเดียว
ไปเป็นการไล่ตรวจสอบด้วยว่า มีการ apply migration version นั้นๆแล้วหรือยัง
(เขาใช้ศัพท์เรียก migration พวกที่ยังไม่ apply ว่า interleaved migration)

ยกตัวอย่างการสั่ง up
ถ้าไล่แบบเดิม มันจะไล่ apply ตั้งแต่ current version + 1 ไปจน ล่าสุด (หรือถึง VERSION ที่ระบุ)
แต่ถ้าเป็นของใหม่ มันจะตรวจว่า มี migration อันไหนที่ยังไม่ apply บ้างแล้วจัดการ apply ให้เรา
กรณี down ก็เช่นกัน
ถ้ามันตรวจพบว่ามี migration ไหน ยังไม่ apply มันก็จะ skip migration นั้นๆให้

Links

Related link from Roti

Thursday, January 17, 2008

หนังสือ The Rails Way

เห็นชื่อคนเขียนแล้วสนใจ, น้อง Roof ก็เลยแวะเอาหนังสือมาให้ผมยืมอ่านดู
เปิดอ่านบทแรกก็ประทับใจเลย เพราะมันว่าด้วย process bootstrap ของ Rails
นั่งอ่านได้อย่างเพลิดเพลิน เพราะคนเขียนรู้จริงและรู้ลึก

อย่างบทที่สองที่ว่าด้วย Controller ก็มีพูดถึงการ send_file จาก controller
คนเขียนก็พูดถึงว่า method send_file ถ้าเราไปใช้ใน mongrel
มันจะ load เนื้อหาทั้งหมดเข้ามาไว้ใน memory
ทำให้มีปัญหากับ file ขนาดใหญ่ๆได้
เขาแนะนำทางออก ด้วยการเปลี่ยนไปใช้ กลไก X-Sendfile ซึ่ง support บน Apache(module mod_xsendfile), Lighttpd
ด้วยการ ส่ง response ที่มี header เป็น

response.headers['X-Sendfile'] = path_to_file

เจ้า web server ก็จะจัดการส่ง file กลับไปให้เราเอง

หลังจากอ่านจบบท ผมก็เข้าไป check feed
ก็พบว่าใน Rails edge พึ่งมีการ submit changeset 8628
ซึ่งเป็นการปรับปรุงคำสั่ง send_file ให้มี option แบบนี้

send_file '/path/file.png', :x_sendfile => true, :type => 'image/png'

นี่ถ้าไม่อ่านมาก่อน ก็คงไม่ได้สนใจเจ้า feed นี้แน่ๆเลย

ตอนนี้วางแผนคร่าวๆไว้ว่า จะพยายามอ่านให้ได้วันละ 1 บท
จะได้รีบเอาไปคืนน้อง Roof

Related link from Roti

Friday, December 21, 2007

protected with_scope

feature หนึ่งใน rails ที่มีคนใช้บ่อยก็คือ with_scope
ใน rails 2.0, with_scope ได้เปลี่ยน visibility จากเดิมที่เป็น
public method ไปเป็น protected method แล้ว
นั่นหมายความว่า เราจะใช้ with_scope ได้เฉพาะใน model เท่านั้น (ระวังประโยคนี้ด้วย ใน ruby เราสามารถหลายๆอย่างที่ไม่น่าเป็นไปได้ได้)

ที่เป็นดังนี้ ก็เพราะว่ามีคนเอา with_scope ไปใช้ที่ระดับ controller กันเยอะ
แล้วก็เจอผล ที่ไม่ตรงตามที่คาดหวัง
เช่น เอาไปใช้ใน filter แบบนี้
แล้วไปติดปัญหาตอน save ที่มันไม่ยอม save ข้อมูล default จาก with_scope ให้
class ApplicationController  < AC::Base 
def scope_story_by_account
Story.with_scope(:find =>
{:conditions => {:account_id => current_account.id},
:create => {:account_id => current_account.id}) {
yield }
end
around_filter :scope_by_account
end

class StoryController < ApplicationController
def create
@story = Story.new(params[:story])
# do some stuff to @story here...
@story.save #...@story.account_id won't be set
end
end


แน่นอน การเปลี่ยนแปลงแบบนี้ ก็ต้องมีคนบ่น
บางคนก็บ่นว่า
I think it's a poor choice to limit something that people find useful just because it doesn't fit your idea of useful. Why not let the coder make these
decisions?


คำตอบที่ได้น่าสนใจ (David Heinemeier คนเขียน rails เป็นคนตอบเองเลย)
You must be new here :). Rails is opinionated software. We make things that we consider good style easy to do and bad style hard. Or rather, we make good style beautiful and bad style ugly. So even though with_scope is going protected, you can still use it in filters if you really, really want to. Just use send(:with_scope) -- that'll side-step the access control. Yes, that'll be ugly and it's intended to be.


อ่านตัวอย่างการใช้ที่เหมาะสมจากที่นี่ “WITH_SCOPE WITH SCOPE”

Related link from Roti

Wednesday, December 19, 2007

Caching in AssetTagHelper

ใน rails 2.0 มี feature อยู่ตัวหนึ่งที่ช่วย combine javascript หรือ CSS หลายๆ file ให้รวมกันเป็น file เดียว
ซึ่งจะช่วยเพิ่มความเร็วของการ load web ของเราได้
อย่างเช่น สมมติว่าเรามีการ include javascript ดังนี้
javascript_include_tag "prototype", "cart"

ปกติมันจะ render เป็นแบบนี้
<script type="text/javascript" src="/javascripts/prototype.js"></script>
<script type="text/javascript" src="/javascripts/cart.js"></script>

แต่ถ้า เราเปิด feature cache ด้วยการใส่ option แบบนี้
javascript_include_tag "prototype", "cart", :cache => "shop"

มันก็จะ render แบบนี้แทน
<script type="text/javascript" src="/javascripts/shop.js"></script>

โดยเจ้า shop.js เกิดจากการ concat prototype.js เข้ากับ cart.js
และ gzip ก่อนส่งให้ browser

เงื่อนไขสำคัญที่จะทำให้ cache ทำงาน ก็คือ ใน environment file ต้องมีการกำหนด
config.action_controller.perform_caching             = true

ซึ่งใน default environment ค่านี้จะถูกกำหนดเป็น true ใน production mode
และเป็น false ใน development mode

อ่านรายละเอียดใน

Related link from Roti

Friday, September 07, 2007

before_filter

ช่วงนี้อ่านที่พี่ Conductor กับ อาจารย์ธวัชชัยกำลัง Tune performance
site gotoknow.org ที่เขียนด้วย Rails
ในชื่อ opensource project ว่า knowleageVolution หรือย่อๆว่า kv

ผมก็เลยฉวยโอกาส checkout เจ้า kv ออกมาเรียนรู้เสียเลย
สำหรับผมการอ่าน code ของคนอื่น ถือเป็นการเรียนรู้ที่ดีมาก
เพราะจะทำให้เราเห็นมุมมองอะไรบางอย่างที่เราไม่เคยเห็น
(ยิ่งของอาจารย์ธวัชชัย ยิ่งต้องควรอ่าน)

พอเปิด code ของ Controller มา
สิ่งแรกที่สะดุดสายตา ก็คือ การ reuse code โดยใช้ filter feature ของ rails

class BlogController < ApplicationController

before_filter :authenticate_user, :only => [:create, :post, :edit, :theme, :enable, :disable, :blog_post_edit, :blog_post_delete, :blog_post_delete_comment]

before_filter :get_blog_and_owner, :except => [:index, :tag_index, :tag, :create, :enable, :rss20, :rss20_redirect]
before_filter :authenticate_owner_as_user, :only => [:post, :edit, :theme, :disable, :blog_post_edit, :blog_post_delete, :blog_post_delete_comment]

before_filter :get_post, :only => [:blog_post_view, :blog_post_edit, :blog_post_delete, :blog_post_comment, :blog_post_delete_comment]
before_filter :make_feed, :except => [:index, :create, :tag_index, :tag, :enable, :rss20, :rss20_redirect, :blog_post_newer, :blog_post_older, :recently_commented_posts]

before_filter :set_icon
before_filter :set_theme, :except => [:index, :tag_index, :tag, :create, :edit, :theme, :enable, :rss20, :rss20_redirect, :recently_commented_posts]

before_filter :process_page, :only => [:recently_commented_posts]

before_filter :set_blog_context_menu, :only => [:blog, :toc, :blog_post_view]

after_filter :expire_blog_rss, :only => [:post, :blog_post_edit, :blog_post_delete]
after_filter :expire_planet_rss, :only => [:post, :blog_post_edit, :blog_post_delete]

after_filter :expire_blogs_fragments, :only => [:create, :edit, :enable, :disable]
after_filter :expire_blog_fragments, :only => :edit
after_filter :expire_post_fragments, :only => [:blog_post_edit, :blog_post_delete, :blog_post_comment, :blog_post_delete_comment]

after_filter :expire_home_index_posts, :only => [:edit, :enable, :disable, :post, :blog_post_edit, :blog_post_delete, :blog_post_comment, :blog_post_delete_comment]

after_filter :expire_post_related_fragments, :only => [:post, :blog_post_edit, :blog_post_delete]

caches_page :blog_post_rss20

def index
@subject = _("Recent Blogs")
@pages, @blogs = paginate(:blogs, :conditions => "disabled = false", :order => "created_at DESC")
@feed = [_("Recent Blogs"), url_for(blog_rss20_url)]
render :template => "blog/blog_list"
end

...

ใน rails ทุกๆ action ที่ request เข้ามา เราสามารถ attach filter เข้ากับ
request นั้นได้ โดยมี filter ให้เลือก 3 แบบคือ before, after, around
(เหมือน AOP ไหม)
ปกติที่ใช้กัน ก็มักจะเอาไปทำพวก logging, authenticate, authorize
แต่ของ kv ไปไกลกว่านั้น ก็คือเอามา reuse logic ส่วนที่ใช้ร่วมกันด้วย

ยกตัวอย่าง สมมติเราขอ request ไปที่ http://gotoknow.org/blog/periphery/125115
ตัว route.rb จะ map url request ของเราเข้ากับ blog_controller.rb และเรียกใช้ method ที่ชื่อ blog_post_view

# route.rb
map.blog_post_view "blog/:address/:id", :controller => "blog", :action => "blog_post_view", :id => /\d+/

# blog_controller.rb
def blog_post_view
@post.increase_hit(session.session_id, request.remote_ip)
process_comment_page @post
@context_menu << :post
end

จาก code จะเห็นว่า logic ใน blog_post_view มันดูน้อยเสียเหลือเกิน จนไม่น่าจะทำงานอะไรได้
ที่เป็นเช่นนี้เพราะ logic ส่วนใหญ่ถูกดึงออกไปอยู่ใน fiter หมดแล้ว
ถ้าเราไปไล่ดู code ในส่วน filter เราก็จะเห็นว่า ก่อนที่จะเข้า blog_post_view
มันจะมี sequence ดังนี้

+ get_blog_and_owner
+ get_post
+ make_feed
+ set_icon
+ set_theme
+ set_blog_context_menu

=> blog_post_view

จุดอ่อนของวิธีนี้ ก็คือตอนที่ไล่ code ครั้งแรกมันอาจจะทำให้สับสนว่า logic ต่างๆมันเป็นอย่างไรบ้าง
เพราะวิธีการ declare filter มันมีทั้งแบบที่บอกว่า ฉันครอบคลุมเฉพาะอันนี้นะ (only)
กับ ฉันไม่ครอบคลุมเรื่องนี้นะ (exclude) ทำให้ต้องสลับ logic ไปมา เวลาไล่สายตา
(โชคดีอย่างหนึ่งที่ rails มันสามารถรวม code ของ filter ไว้ใน file เดียวกันได้
ทำให้การไล่ code ไม่ยากจนเกินไป)
แต่หลังจากคุ้นกับ code แล้ว การแยก logic ใน pattern แบบนี้
ก็ไม่ได้ทำให้เกิดอุปสรรคอะไร

ข้อสังเกต: rails implement feature Filter ในแบบ recursive
ดังนั้นเวลาเกิด error ขึ้นมา, stack trace ก็เลยยาวเหยียดแบบนี้

...
.//app/controllers/application.rb:275:in `add_object_comment'
.//app/controllers/blog_controller.rb:275:in `blog_post_comment'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:1095:in `perform_action_without_filters'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:632:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:634:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:438:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:438:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:438:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:438:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:634:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:438:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:634:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:438:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:438:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:438:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:438:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:449:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:634:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:449:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:449:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:634:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:449:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter'
...

Related link from Roti

Monday, May 21, 2007

unit_of_work

ใน rails เวลาเราเขียน action ส่วนที่เกี่ยวกับการ save, update
หน้าตาของ code ตรงนั้นส่วนใหญ่จะยึดตามแบบนี้
  def create
@role = Role.new(params[:role])
if @role.save
flash[:notice] = 'Role was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end

ซึ่งดูแล้วก็รู้เรื่องดี, แต่สำหรับผมเวลาเขียน code ตรงส่วนนี้ทีไรมันรู้สึกขัดๆ

Obie Fernandez ก็คงรู้สึกแบบเดียวกัน
แต่เขาหาวิธีแก้ไขได้เนียนดี
โดยเขาเปลี่ยนวิธี save เป็นแบบนี้แทน
  def create
case unit_of_work do
@role = Role.new(params[:role])
end

when Success
redirect_to :action => 'list'

when ValidationError
render :action => 'new'
end
end

เป็นการเอา block + case statement มา combine กันได้สวยดี
โดยตัว unit_of_work เป็น method ที่รับ block เข้าไป

Related link from Roti

Tuesday, March 06, 2007

Ruby 's Require ภาค 2

ปกติถ้าเราจะใช้ class อะไร
เราก็ต้อง load class นั้นขึ้นมาใน context ของเราก่อน
เช่นเรามี file p.rb
class Pok
def hi
"hello"
end
end

ลอง สั่ง irb
$ irb
>> Pok.new
NameError: uninitialized constant Pok
from (irb):1
>> require 'pok'
=> true
>> Pok.new
=> #<Pok:0x11158a8>

นี่คือกลไกปกติใน ruby
แต่ถ้าใครเคยใช้ rails แล้ว จะเห็นว่า
มันไม่ได้เป็นไปตาม pattern ข้างบนนี้
สมมติเรามี file $RAILS_ROOT/app/model/activity.rb
class Activity < ActiveRecord::Base
end

ลองสั่ง script/console ดู

$ script/console
Loading development environment.
>> Activity.new
=> #<Activity:0x245efcc @new_record=true, @attributes={"name"=>nil}>

คำถามก็คือ rails ไป load activity.rb มา โดยเราไม่ต้องสั่ง require สักแอะได้อย่างไร

คำตอบอยู่ใน file ที่ชื่อ
/..verylong../active_support/dependencies.rb
ภายในมี method อยู่ตัวหนึ่งที่ชื่อ const_missing

สำหรับคนที่ไม่คุ้นกับ method นี้
method นี้เป็น method ใน class Module
หน้าที่ method นี้ก็คือ ในกรณีที่มีการเรียกใช้ Constant ที่ไม่มีอยู่จริง
>> class Module
>> def const_missing(name)
>> puts 'hi'
>> end
>> end
=> nil
>> Bunn
hi
=> nil
>> P
hi
=> nil
>>


ใน dependency.rb ก็มีการ override method นี้ดังนี้
class Module #:nodoc:
# Rename the original handler so we can chain it to the new one
alias :rails_original_const_missing :const_missing

# Use const_missing to autoload associations so we don't have to
# require_association when using single-table inheritance.
def const_missing(class_id)
file_name = class_id.to_s.demodulize.underscore
file_path = as_load_path.empty? ? file_name : "#{as_load_path}/#{file_name}"
begin
require_dependency(file_path)
...
long long code for error case

Magic ก็คือ require_dependency ที่ rails เรียกใช้
,require_dependency คือ function ที่ rails สร้างขึ้นมา
เพื่อใช้แทน require โดยมีข้อแตกต่างคือ
ในกรณีของ development environment
module, class ที่ load โดย require_dependency จะถูก
flush ออกไปหลังจากที่จบ request แล้ว
ทำให้ developer สามารถแก้ไข source code ได้อย่างสบายใจ
ไม่ต้อง restart web server ใหม่ทุกครั้งหลังจากแก้ไข code

ส่วนประโยค file_name = class_id.to_s.demodulize.underscore
ก็คือการ solve หาชื่อ file ที่จะ require
โดยมีหลักประมาณว่า
"Activity" -> 'activity'
"OrderItem" -> 'order_item'

Related link from Roti

Friday, March 02, 2007

Ruby 'require'

คำสั่งง่ายๆคำสั่งหนึ่งที่หลายคน (รวมผมด้วย) มองข้ามไปก็คือ require
คำว่ามองข้าม หมายถึง ใช้พอเป็น, แต่่ไม่สนใจกลไกของมัน
ถ้ามีปัญหาก็มั่วอย่างเดียว จนกว่าจะใช้ได้

วันนี้ก็เลยนั่งสะสาง ความเข้าใจเกี่ยวกับเจ้า require

require คือกลไกการ include source code อย่างหนึ่งของ ruby
มีคำสั่งที่เหมือนกันอีกคำสั่งหนึ่งก็คือ load
สิ่งที่ต่างกันก็คือ ถ้าสั่ง require ซ้ำๆกัน
มันจะ load แค่ครั้งแรกครั้งเดียว

ลองสร้าง file p.rb ที่ current directory
a = 1
b = 2
@c = 3
def d
"hi"
end

จากนั้นก็เรียก irb แล้วสั่ง
$ irb
>> require 'p'
=> true
>> require 'p' # load ครั้งที่ 2 return false เพราะถือว่า load ไปแล้ว
=> false

เงื่อนไขอย่างหนึ่งของการ require ก็คือ
local variable ที่อยู่ใน p.rb จะไม่ตามมาด้วย
>> require 'p'
=> true
>> a
NameError: undefined local variable or method `a' for #
from (irb):3
>> b
NameError: undefined local variable or method `b' for #
from (irb):4
>> @c
=> 3
>> d
=> "hi"


คำถามถัดมาก็คือ แล้วเวลาสั่ง require 'p'
มันมองหา file ที่ชื่อ p.rb ที่ไหนบ้าง,
ruby มีตัวแปร array อยู่ตัวหนึ่งที่ชื่อ $LOAD_PATH
ซึ่งตัวแปรนี้มี short-name อีกชื่อหนึ่งคือ $:
(คนไม่ชอบ perl เห็นแล้วคงร้องยี้)
>> $LOAD_PATH
=> ["/usr/local/lib/ruby/gems/1.8/gems/wirble-0.1.2/bin",
"/usr/local/lib/ruby/gems/1.8/gems/wirble-0.1.2/.",
"/usr/local/lib/ruby/site_ruby/1.8",
"/usr/local/lib/ruby/site_ruby/1.8/powerpc-darwin8.7.0",
"/usr/local/lib/ruby/site_ruby", "/usr/local/lib/ruby/1.8",
"/usr/local/lib/ruby/1.8/powerpc-darwin8.7.0", "."]
>> $:
=> ["/usr/local/lib/ruby/gems/1.8/gems/wirble-0.1.2/bin",
"/usr/local/lib/ruby/gems/1.8/gems/wirble-0.1.2/.",
"/usr/local/lib/ruby/site_ruby/1.8",
"/usr/local/lib/ruby/site_ruby/1.8/powerpc-darwin8.7.0",
"/usr/local/lib/ruby/site_ruby",
"/usr/local/lib/ruby/1.8",
"/usr/local/lib/ruby/1.8/powerpc-darwin8.7.0", "."]
>>

ดังนั้นถ้าเราอยาก add load path ส่วนตัวของเรา
เราก็สามารถ append path เข้าไปใน ตัวแปรนี้ได้เลย
>> $LOAD_PATH << '/tmp' 


ที่นี้ลองไปดูที่ rails บ้าง
rails เตรียม custom configuration ให้เรา set load_path
ไว้ใน file $RAILS_ROOT/config/environment.rb
Rails::Initializer.run do |config|
config.load_paths += %W( #{RAILS_ROOT}/extras )
end

การทำงานภายใน ก็คือมันไปเรียกใช้ตัวแปร $LOAD_PATH
แต่แทนที่จะ append ไปข้างหลัง $LOAD_PATH array
มันใช้วิธีไส่ไว้ข้างหน้าแทน เพราะกลไกการหามันจะเริ่มจาก
หน้าไปหลัง การใส่ไว้ข้างหน้า ทำให้สามารถ override file ข้างหลังได้
# Set the <tt>$LOAD_PATH</tt> based on the value of
# Configuration#load_paths. Duplicates are removed.
def set_load_path
configuration.load_paths.reverse.each { |dir| $LOAD_PATH.unshift(dir) if File.directory?(dir) }
$LOAD_PATH.uniq!
end

Related link from Roti