มีของขวัญวันปีใหม่+คริสต์มาสมาฝากครับ

Merb 2.0 และ Rails 3.0 จะกลายเป็นสิ่งเดียวกัน

ผู้พัฒนาทั้งสองฝั่ง คือ Merb กับ Rails ตัดสินใจ ที่จะพัฒนา framework ทั้งสองตัวนี้ร่วมกัน โดยจะเอาของดีหลักๆ ของ Merb ไปใส่ใน Rails คือ

  • Agnosticism - คือการไม่ยึดติด เช่น ไม่ชอบ AcitveRecord เอาตัวอื่นได้ไหม
  • Modularity - คือการแยกเป็นส่วนๆ แยกไปเลยเป็น module ไว้ทำอะไร เวลาเขียน code ก็อย่าให้มันตีกัน
  • Improved performance - แน่นอน ประสิทธิภาพดีขึ้น เร็วขึ้น
  • Public API - บังคับให้คนใช้ คือบางกรณี ดันไปเรียก method ที่ protected/private(ไม่เห็นใน API) ไว้ แต่ถ้าที่ protected/private นั้น มีการ update ล่ะ แน่นอน พังแน่ๆ ก็ต้องตามไป update code ใหม่

แต่ก็ใช่ว่า Merb จะตายไป ทีมก็ยังพัฒนาต่อ และก็ยังพัฒนาร่วมกับ Rails ด้วย แต่ออก Rails 3.0 เมื่อไหร่ มันจะเป็นสิ่งเดียวกัน

สำหรับคนที่ใช้ Merb อยู่ เขาแนะนำให้ใช้ต่อเลย เพราะมันคงใช้เวลาสักพักใหญ่ๆ ในการปรับปรุง ถ้า Rails 3.0 ออกมาอย่างเป็นทางการ ก็ไม่ต้องห่วงเพราะ Yehuda Katz บอกว่า

You will not be left in the cold and we’re going to do everything to make sure that your applications don’t get stuck in the past.

สำหรับคนที่กำลังศึกษาหรือสนใจที่จะศึกษา Merb ผมว่าน่าจะลองเลยครับ เพราะในที่สุดแล้ว แนวทางการเขียนหลักๆ ก็คงจะไม่ต่างจากที่เขียน Merb ในทุกวันนี้หรอก รูัก่อน เป็นก่อน ย่อมได้เปรียบ

ปล. นอกจากผมและสมาชิก codegent แล้ว มีใครใช้ Merb ในงานจริงๆ บ้างเนี่ย

links ที่เกี่ยวข้อง
http://merbist.com/2008/12/23/rails-and-merb-merge/
http://yehudakatz.com/2008/12/23/rails-and-merb-merge/
http://weblog.rubyonrails.org/2008/12/23/merb-gets-merged-into-rails-3
http://brainspl.at/articles/2008/12/23/merb-is-rails
http://splendificent.com/2008/12/the-merb-rails-merger-announcement-an-inside-opinion/
http://rubyonrails.org/merb

Comments 6 Comments »

ฟีเจอร์ใหม่อีกอย่างหนึ่งของ Rails 2.2 ก็คือความสามารถในการทำ internationalization พื้นฐานที่ถูกบรรจุอยู่ใน core (อ่าน api, เว็บหลัก Rails I18n)

ก่อนจะใช้ได้ก็ต้องไปใช้ Rails 2.2 เสียก่อน ถ้าจะทดลองกับ project ใหม่ก็ติดตั้ง Rails 2.2 แล้วก็สั่งสร้าง project ได้เลย ถ้าจะแก้ project เก่าก็แก้ไฟล์ /config/environment.rb ตามนี้ครับ

# เปลี่ยน version rails
RAILS_GEM_VERSION = '2.2.2' unless defined? RAILS_GEM_VERSION

require File.join(File.dirname(__FILE__), 'boot')
  # ..
  # Setting locales
  config.i18n.default_locale = 'en'  # เปลี่ยนเป็น 'th' สำหรับไทย
  # ..
end

เปลี่ยนเสร็จก็อย่าลืมสั่ง rake rails:update ให้ปรับแก้อะไรต่าง ๆ ด้วยนะครับ

หลักการคร่าว ๆ ของการทำ i18n ใน rails ก็คือ เราจะแยกสตริงที่ใช้ไปใส่ไว้ในไฟล์ต่างหาก โดยแบ่งตามภาษา จากนั้นเวลาเราจะแสดงสตริงเหล่านั้นก็จะเรียกผ่านฟังก์ชัน I18n.translate หรือย่อ ๆ ว่า I18n.t แทนที่จะเขียนสตริงเหล่านั้นออกไปตรง ๆ

ที่เก็บของไฟล์สตริงเหล่านี้จะอยู่ใน /config/locales/ โดยสามารถเก็บเป็นไฟล์ yml หรือเป็น ruby hash ก็ได้ คราวนี้เราจะลองอะไรง่าย ๆ กันก่อน โดยลองไปเพิ่ม (หรือแก้) ไฟล์ en.yml ในไดเร็กทรอรีดังกล่าวเป็นดังด้านล่างครับ

en:
  hello: "Hello world"
  hello_name: "Hello, {{name}}"

  config:
    hello: "Hello, admin"

ทีนี้ เราไปทดลองเรียกสตริงดังกล่าวใน script/console ครับ

I18n.translate :hello                #=> "Hello world"
I18n.t :hello                         #=> "Hello world"
I18n.t :hello_name, :name => 'John'  #=> "Hello, John"
I18n.t 'config.hello'                 #=> "Hello, admin"

จากตัวอย่างด้านบน แสดงการเรียกสตริงแบบทั่วไป ส่วน hello_name เป็นสตริงที่รับพารามิเตอร์ name ส่วน config.hello เป็นการระบุสตริงแบบที่มีขอบเขต (อยู่ใน config)

ทีนี้ ลองไปสร้างไฟล์ th.yml แล้วใส่ข้อมูลตามด้านล่างนะครับ

th:
  hello: "สวัสดี"
  hello_name: "สวัสดี, {{name}}"

  config:
    hello: "กราบสวัสดีท่านผู้ดูแล"

แล้วไปทดลองใหม่ใน script/console ครับ

>> I18n.locale = 'th'                   # ตั้ง locale
>> I18n.translate :hello                #=> "สวัสดี"
>> I18n.t :hello_name, :name => 'John'  #=> "สวัสดี, John"
>> I18n.t 'config.hello'                #=> "กราบสวัสดีท่านผู้ดูแล"

ทีนี้ ถ้าเราต้องการให้ locales เริ่มต้นของเราเป็น th เลย ก็ไปแก้บรรทัด config.i18n.default_locale ใน environment.rb นะครับ

เพื่อความสะดวกใน view ฟังก์ชัน I18n.t สามารถเรียกสั้น ๆ ได้ด้วย t ดังนั้นเราสามารถเขียน <%=t :hello %> ได้เลย

อันนี้เป็นการใช้งานแบบขั้นต้นนะครับ ถ้ามีเวลาจะมาเขียนเกี่ยวกับการแปลอื่น ๆ เช่นการแปลชื่อ model และ attributes ใน Active Record ต่อครับ ถ้าใครอยากเล่นก่อนก็ไปโหลดไฟล์ locale th.rb ที่มีการแปลข้อความใน Active Record และส่วนอื่น ๆ เช่นวันที่และจำนวนนับ (แปลโดยคุณ Sikachu! ขอบคุณมากครับ!) แล้วมาเล่นดูได้ครับ เอาไปใส่เพิ่มไว้ใน /config/locales แล้วก็เปลี่ยน locale เป็น th ดู

ไฟล์คำแปลดังกล่าวผมไปโหลดมาจาก github ซึ่งเป็นไฟล์ที่ Sven Fuchs เอามาจาก demo application (อ่านเพิ่ม) โดยการแปลในนั้นทำโดยคุณ Prem Sichanugrist (หรือคุณ Sikachu! นั่นเอง) อย่างไรก็ตาม ตอนผมเอามาลองแล้วพบว่าเหมือนการอ้างถึงสตริงใน active record มันจะเปลี่ยนไป ผมเลยแก้กลายมาเป็นไฟล์ด้านบนครับ (ส่ง patch ไปให้ Sven Fuchs แล้ว)

Comments 4 Comments »

แนวคิดเกี่ยวกับ Dependency Injection เป็นแนวคิดที่สำคัญมากในการโปรแกรมสำหรับภาษาเช่น Java ด้วยสาเหตุหลาย ๆ ประการ

สาเหตุหนึ่งก็คือมันทำให้เราสามารถทำ unit test กับโปรแกรมที่มีการขึ้นต่อกันได้ ยกตัวอย่างเมท็อดด้านล่าง

public class RegistrationController {
	// ...
	void sendConfirmationEmail(User newUser) {
		MailSender sender = new MailSender();

		String msg = buildEmailMessage(newUser);
		sender.send(msg,myemail,newUser.getEmail());
	}
}

แทบเราจะไม่สามารถ test ได้เลยว่าเมท็อดดังกล่าวเรียก MailSender ได้ถูกต้องหรือเปล่า ที่ผมนึกออกคงจะต้องเข้าไปจัดการแก้โปรแกรมหลายจุดอยู่

ปัญหาก็มาจากการที่เมท็อดนี้สร้าง MailSender ขึ้นมาเอง ทำให้เราไม่สามารถเข้าไปแก้ไขได้ วิธีการที่นิยมใช้ในการจัดการเรื่องเหล่านี้ก็คือการแยกการขึ้นต่อกันของคลาส MailSender ออกมา โดยทำเป็นเมท็อดให้กำหนดค่าเข้าไป อาจจะที่ constructor หรือเขียนเป็นเมท็อดแยก หรือไม่ก็ใช้ DI framework ต่างๆ

เช่นแก้โปรแกรมเป็นแบบนี้

public class RegistrationController {
	// ...
	private MailSenderInterface mailSender;

	void setMailSender(MailSenderInterface sender) {
		mailSender = sender;
	}

	void sendConfirmationEmail(User newUser) {
		String msg = buildEmailMessage(newUser);
		mailSender.send(msg,myemail,newUser.getEmail());
	}
}

ในปัจจุบัน Java มี dependency injection framework หลายตัว (เท่าที่ผมทราบ) ซึ่งทำให้การ “ร้อย” (ของยืมพี่ป๊อกหน่อย) component ต่าง ๆ เข้าด้วยกันเป็นไปได้สะดวกมาก

แนวคิดดังกล่าวได้รับการตอบรับจากทางฝั่งนักพัฒนา Ruby เช่นเดียวกัน เช่น Jim Weirich ได้เขียนบล็อกเกี่ยวกับเรื่องนี้เอาไว้เมื่อปี 2004 ใน Ruby ก็มี framework ทำ DI อยู่หลายตัวเช่น Needle เขียนโดย Jamis Buck (คนทำ Capistrano) Jamis Buck ถึงขนาดเขียน di framework มาสองตัวเลยทีเดียว (อีกตัวคือ Copland)

อย่างไรก็ตาม ก็เป็นที่น่าสงสัยว่าทำไม DI framework ไม่เป็นที่นิยมใน Ruby

หนึ่งปีถัดมา Jim Weirich ได้ไปพูดที่ OSCON ในหัวข้อว่า “Dependency Injection: Vitally Important or Totally Irrelevant?” โดยสรุปว่าเนื่องจาก Ruby ไม่เหมือน Java ในปัจจุบันยังไม่เห็นความจำเป็นของ DI framework

Jamis Buck เองก็ออกมาเขียนถึงเรื่องดังกล่าวเช่นกัน โดยเขาแก้โปรแกรมในไลบรารี Net::SSH ใหม่ โดยเอา DI (ที่เขาเขียนเอง) ออก แล้วพบว่าโปรแกรมเล็กลงและอ่านง่ายขึ้น

ทำไม DI ดูเหมือนจะยังไม่จำเป็นใน Ruby?

พิจารณาจากตัวอย่างข้างต้น ถ้าเอาเมท็อด sendConfirmationEmail มาเขียนเป็นโปรแกรม Ruby จะได้ประมาณด้านล่างครับ

class RegistrationController
  # ..
  def send_confirmation_email(new_user)
    sender = MailSender.new
    msg = build_email_message(new_user)
    sender.send(msg, self.myemail, new_user.get_email)
  end
end

แล้วจะ test อย่างไร?

สิ่งที่เรามักจะลืมไปก็คือภาษาแต่ละภาษามีลักษณะที่แตกต่างกัน บางอย่างที่ไม่สามารถทำได้เลยในบางภาษา อาจเป็นสิ่งธรรมดามากในบางภาษา

ใน Ruby มีความสามารถ (หรือความบกพร่อง?) อย่างหนึ่งคือ Open Class

นั่นคือเราสามารถแกะคลาสมาแก้ได้ตลอดเวลา (รวมถึงตอน run-time) นอกจากนี้เรายังแก้ไขการทำงานของเมท็อดของแต่ละวัตถุได้โดยง่าย (ในระหว่างที่โปรแกรมทำงานอยู่เช่นกัน)

เมท็อดด้านบนถ้าจะเขียน test case ใน rspec ก็เป็นประมาณนี้ครับ

describe RegistrationController do
  #..
  it "should send mail to user's address from admin's mail" do
    my_email = 'jittat@internet.com'
    user_email = 'user@space.com'
    user = mock_model(User, :email => user_email)
    sender = mock("mock sender")
    sender.should_receive(:send).
      with(anything, my_email, user_email)
    MailSender.should_receive(:new).and_return(mock_sender)
    controller = RegistrationController.new :adm_mail => my_email
    controller.send_confirmation_email(user)
  end
end

สังเกตว่าเนื่องจาก class และ object ใน ruby แก้ได้ตลอดเวลา mock framework จึงสามารถเข้าไปปรับแก้อะไรต่าง ๆ ได้มากมาย โดยไม่ต้องแยก dependency ออกมา

ไม่รู้ว่าผลที่ได้จะดีหรือไม่ดี? แต่ก็ทำให้ความจำเป็นของการใช้ DI framework ใน Ruby ลดลงไป

อ่านเพิ่มเติมได้ใน slide ของ Jim Weirich นะครับ อธิบายเห็นภาพมาก (มีอีกอันที่น่าสนใจเหมือนกันคือ 10 Things Every Java Programmer Should Know About Ruby ลองไปกดเล่นได้ครับ)

Comments No Comments »

JBoss on Rails: Deploying Rails Apps to a JBoss App Server
สูตรหลักๆ สำหรับการ deploy Rails มักจะไปจบที่ Apache/Nginx + Mongrel/Passenger ซึ่งปัญหาส่วนใหญ่ที่เจอคือ มันช้า หน่วงๆ ชอบกล ทางเลือกใหม่ตอนนี้ก็มี deploy บน Jboss ผมเองยังไม่ได้ดูอย่างละเอียด โหลด slide ไปดูกันได้

อีกสูตรนึงคือใช้ Jruby on glassfish อันนี้ลองแล้ว แต่มีปัญหานิดหน่อยถ้าเอามาใช้กับ project ที่มีอยู่แล้ว เพราะอาจจะต้อง rewrite path ใหม่นิดหน่อย

รวมๆ ก็น่าสนใจทั้งสองสูตรครับ

———————————————————————————————————-

New 15-minute blog video on Rails 2.2

ตัวใหม่สำหรับ 2.2 ดูได้ที่ http://rubyonrails.org/screencasts
น่าจะเป็นตัวช่วย guide ได้บ้างสำหรับคนที่พร้อมจะเปลี่ยนจากตัวเก่ามาเป็นตัวใหม่

—————————————————————————————————-

Merb loves Rails

blog ขำๆ สำหรับชาว Merb/Rails ครับ หลังจากไปดู myth ของ dhh แล้วมาดูอันนี้ก็ดีขำดีครับ

Top 10 reasons why we ♡ Rails:

  • Without Rails, the Ruby language would not be one of the top 10 programming languages
  • Without Rails, we would still be writing thousand-line configuration files in XML to start your small app
  • Without Rails, most developers would not know what MVC stands for
  • Without Rails, I would not be a Ruby web developer
  • Without Rails, we would not have Merb
  • Without Rails, we would not have all the other cool Ruby frameworks
  • Without Rails, testing would be something only the elite would do
  • Without Rails, serving Ruby web apps would be a pain in the neck
  • Without Rails, Zed Shaw would not be famous

Bonus items:

  • Without Matz, there would be no Ruby
  • Without Ruby, there would be no Rails

Comments 1 Comment »

เวลาเพิ่มเติมไลบรารีต่าง ๆ ใน Ruby เราก็มักจะติดตั้ง gem ลงในระบบที่เราใช้ (ด้วยคำสั่งเช่น gem install XXX)

ถ้าโปรแกรมที่พัฒนาบน rails ที่เราเขียน ต้องใช้บรรดา gem ที่เราติดตั้งลงไปนี้ เวลาจะนำไป deploy เราก็มักจะต้องระบุกับคนติดตั้ง หรือใน script สำหรับติดตั้งว่าให้ติดตั้ง gem ตัวที่ต้องการ หรือไม่ก็เอา gem มาติดตั้งใน rails app เลย (จะอยู่ใน dir vendor/)

ใน Rails 2.1 มีความสามารถที่เพิ่มเข้ามา (ที่หลายคนอาจเคยได้ใช้แล้ว, แต่ผมเพิ่งได้ลอง อิอิ) คือการระบุ gem dependencies ลงใน rails config เลย

วิธีใช้ก็ไม่ยาก เราไประบุใน config/environment.rb ดังเช่นตัวอย่างด้านล่างครับ

Rails::Initializer.run do |config|
  # ...

  # Require Haml and Pony
  config.gem "haml"
  config.gem "pony"

  # Require rspec-rails (แต่ gem นี้เวลาเรียก require เรียกเป็น 'spec')
  config.gem "rspec-rails", :lib => "spec" 

  # ติดตั้งจาก source อื่น ๆ
  config.gem "hpricot", :source => "http://code.whytheluckystiff.net"
end

ในตัวอย่างข้างต้น rspec เราติดตั้ง gem ชื่อหนึ่ง (rspec-rails) แต่เวลาเรียกด้วย require ใช้อีกชื่อหนึ่ง (spec) ก็ต้องระบุลงไปใน :lib ด้วย นอกจากนี้ถ้าติดตั้ง gem จากที่มาอื่น ๆ ก็สามารถระบุลงไปได้ด้วย เช่น

ของที่มาพร้อมกันก็คือ rake task อีกชุดใหญ่คือ

rake gems                            # List the gems that this rails applic...
rake gems:build                      # Build any native extensions for unpa...
rake gems:install                    # Installs all required gems for this ...
rake gems:unpack                     # Unpacks the specified gem into vendo...
rake gems:unpack:dependencies        # Unpacks the specified gems and its d...

ถ้าต้องการดูว่า rails app ต้องการใช้ gem อะไรก็สั่ง rake gems

$ rake gems
(in /home/jittat/xxxx)
[I] rspec-rails
[I] haml
[I] pony 

I = Installed
F = Frozen

ถ้าต้องการติดตั้งก็สั่ง rake gems:install (ใน unix อย่าลืม sudo) ถ้าต้องการจับ gem มาใส่ใน rails vendor เลย ก็สั่ง rake gems:unpack

น่าจะทำให้การจัดการกับ gem ที่โปรแกรมบน rails ของเราใช้ทำได้ง่ายขึ้นนะครับ

ที่มา: What’s New in Edge Rails: Gem Dependencies

Comments 2 Comments »

รวม links อีกที ไม่ว่างครับ งานเยอะ

Cucumber: The Latest in Ruby Testing
Delayed Gratification with Rails
What’s New in Edge Rails: render Stops Being High-Maintenance
What’s New in Edge Rails: Object.try
HappyMapper: Easy XML / Object Mapping for Rubyists (อันนี้หน้าตาดี)
JBoss on Rails: DeployinRails Podcast Brasil, QCon Special - John Straw (YellowPages.com) and Matt Aimonetti (Merb)g Rails Apps to a JBoss App Server (เจ๋ง)
jnunemaker: jQuery on Rails: Why Bother?
Rails Podcast Brasil, QCon Special - John Straw (YellowPages.com) and Matt Aimonetti (Merb)
Rails Profiling: Getting Easier
Git’n Your Shared Host On
Episode 137: Memoization
jnunemaker: RubyGems: Yours, Mine and Ours

Comments 2 Comments »

ไม่ได้หายไปไหน งานเยอะครับ กับหัด emacs อยู่ ไว้หัด emacs สำเร็จแล้ว คงจะได้มาเขียนบ่อยขึ้น

3 Ways To Build Fake Demo Data For Your Rails App

ถ้าจะสร้างข้อมูลหลอกๆ มาใช้งาน สามารถทำได้ง่ยๆ ด้วย
http://faker.rubyforge.org/
http://random-data.rubyforge.org/
http://github.com/sevenwire/forgery/tree/master

ทุกวันนี้ผมเขียนเป็น array เก็บไว้ แล้ว random ออกมาแล้ว save ต่อไปคงจะสบายขึ้นด้วยเครือ่งมือพวกนี้

———————————————————————————

What’s New in Edge Rails: Default Scoping

feature ใหม่สำหรับ active record คือ default_scope
ชื่อก็สื่อความหมายได้ง่ายๆ อยู่แล้ว

มาดูหน้าตาหน่อย

class Article < ActiveRecord::Base
  default_scope :order => 'created_at DESC'
  named_scope :published, :conditions => { :published => true }
end

Article.published #=> "SELECT * FROM `articles` WHERE published = true ORDER BY created_at DESC"

ก็ใส่ condition ให้เป็น default ในทุกๆ ครั้งที่มีการ query

รวมกันเลยละกัน

What’s New in Edge Rails: Application.rb Duality is no More

อีกหน่อย application.rb จะเป็น application_controller แล้ว
สั้นๆ เหอๆ

———————————————————————————————

Rails 2.2 Released
Rails 2.2 Released - 27 Links and Resources To Get You Going

หลักๆ คงจะหนักไปที่เรื่อง i18n
thread safe
รองรับ jruby/ruby 1.9
และอีกหลายๆ อย่างที่ผมเคยรายงานไปแล้วในข่าวเก่าๆ ครับ อย่าง etag/connection pool

ประมาณนี้ครับ

Comments 2 Comments »

อย่างที่รู้ๆ กันว่าโครงสร้างข้อมูลแบบ Hash มันไม่มีการเรียงลำดับ คงเป็นเพราะว่าธรรมชาติของข้อมูลนั้นไม่ต้องการการเรียงลำดับ แต่ว่านะ ก็มีบางครั้งที่เราอยากจะได้ที่มันเรียงลำดับด้วยล่ะ (อย่างงานที่ผมเจอวันนี้) จะทำไง

Hash ธรรมดาก็คงไม่เพียงพอซะแล้ว

ที่ ActiveSupport ของ Rails ก็ได้เตรียมการสำหรับเรื่องนี้ไว้แล้ว มันมี OrderedHash ใว้ให้เราใช้กัน

>> h = ActiveSupport::OrderedHash.new
=> []
>> h["x"] = "xx"
=> "xx"
>> h["z"] = "zz"
=> "zz"
>> h["y"] = "yy"
=> "yy"
>> h
=> [["x", "xx"], ["z", "zz"], ["y", "yy"]]
>> h["y"]
=> "yy"

ต้องเขียนอย่างนี้ครับ หากเขียนแค่ OrderedHash.new ตอนที่ใช้งานที่ controller มันจะบอกว่าไม่รู้จัก

จากนั้นเราก็เอาไปใช้ได้ตามใจ และหากเราเอา Hash ตัวที่เราสร้างมา view ข้อมูลดูก็จะเห็นว่ามันเรียงลำดับตามที่เราใส่ลงไป

จะเห็นว่าจริงๆ แล้วมันไม่ได้เป็น Hash จริงหรอกนะ มันเป็น Array แต่สามารถเข้าถึงข้อมูลได้คล้ายๆ Hash เท่านั้นเอง

อืม… Magic จริงๆ เลย

Comments 1 Comment »

Roxy - An Object Proxying Library for Ruby

Roxy ช่วยทำ proxy ง่ายๆ ให้กับ object ที่มีความสัมพันธ์กัน ยกตัวอย่าง

require 'roxy'
class Person

# Add in proxy ability
include Roxy::Moxie

attr_accessor :first, :last, :parents, :children

# Add ability to ask the parents collection if they are divorced
proxy :parents do
def divorced?
proxy_target.size > 1 and
proxy_target.collect { |parent| parent.last }.uniq.size > 1
end
end

# Add ability to ask the children collection for only the step-children
proxy :children do
def step
proxy_target.select { |child| proxy_owner.last != child.last }
end
end

def initialize(first, last)
@first, @last = first, last
end
end

# Now the following is possible:
person.parents.divorced? #=> true|false
person.children.step #=> [<Person...>, <Person...>]

ขออภัย สำหรับ indent จิ้มไปจิ้มมา มันเอา indent ออกหมดเลยครับ ผมขี้เกียจต่อสู้กับมันละ ==’

idea ของการทำ proxy นี้ ก็อารมณ์ประมาณ has_many :associated_objects ของ active record แหละครับ ทำให้เราเรียกใช้อะไรต่างๆ ใน class ที่เราอยากจะให้มีความสัมพันธ์ได้

———————————————————————————————————————————–

Creating and Integrating Bookmarklets with Rails

video ตัวอย่างการสร้าและ integrate bookmarklets

————————————————————————————————————————————-

Rails Envy Podcast - Episode #055: 11/12/2008

Subscribe via iTunes - iTunes only link.
Download the podcast ~26:30 mins MP3.
Subscribe to feed via RSS by copying the link to your RSS Reader

Comments No Comments »

บังเอิญต้องไปทำเว็บด้วย Drupal แล้วก็มีข้อมูลจากเว็บเก่าที่ต้องโอนย้ายมา ด้วยความช่วยเหลือจากนิสิตหลาย ๆ ท่าน ในการเขียนโปรแกรมแกะข้อมูลจากเว็บเก่า ปัญหาก็เหลือแค่ว่าจะเอาไปใส่ใน Drupal ได้อย่างไรง่าย ๆ

ผมได้ข่าวว่ามี API แต่ก็หาไม่เจอ แถมรีบอีก.. ก็เลย… ตกลงเขียนเองเลย โดยใช้ Active Record เข้าไปครอบตารางของ Drupal

แม้ว่าตารางของ Drupal จะเยอะแยะ แต่ก็ไม่เกินความสามารถในการปรับแต่ง Active Record (และ gem อื่น ๆ)

ใน Drupal ทุกอย่างเป็น node ดังนั้นตารางพื้นฐานของ Drupal คือตาราง node ซึ่งมีอะไรที่คล้าย ๆ กับ id คือ nid กับ vid โดย nid คือหมายเลขโหนด และ vid คือหมายเลข revision (โหนดหนึ่ง ๆ แก้ได้หลาย revision) ทีนี้มีปัญหาอีกนิดคือในตาราง node มีฟิลด์ชื่อ type ซึ่งจะไปชนกับการใช้ single-table inheritance ใน Active Record ทำให้เราต้องหลบชื่อดังกล่าวไปเป็นชื่ออื่น (ในด้านล่างใช้เป็น type_id)

ดังนั้นสิ่งที่ไม่ตรงกับ convention ของ Rails ที่ต้องปรับคือ table_name, primary_key และ inheritance_column โมเดลของ node แสดงด้านล่าง

class Node < ActiveRecord::Base
  self.inheritance_column = 'type_id'
  set_table_name "node"
  set_primary_key "vid"
end

ใน Drupal ผมใช้โมดูล CCK สำหรับใช้สร้างประเภท (content type) ของ node ทีนี้ ผมมีการสร้างประเภท lect_profile เอาไว้ ซึ่ง Drupal จะไปสร้างตารางชื่อ content_type_lect_profile เอาไว้

ไล่ไปไล่มา ก็พบว่ามันเชื่อมกับตาราง node ด้วย field vid ทำให้เราเพิ่ม model และความสัมพันธ์ดังกล่าวเข้าไปใน Node ได้ดังด้านล่าง

class Node < ActiveRecord::Base
  # ...
  has_one :profile,
          :class_name => 'Profile',
          :foreign_key => 'vid'
end

class Profile < ActiveRecord::Base
  set_table_name "content_type_lect_profile"
  set_primary_key "vid"
end

พอเชื่อมได้แล้ว ก็ยังมีเรื่องยุ่งยากก็ตามมาอีก เพราะว่าในประเภทที่สร้างมีฟิลด์ที่มีค่าได้หลายค่า คือประวัติการศึกษา ทีนี้ใน Drupal จะเก็บข้อมูลโดยใช้ primary key ประกอบด้วย vid กับ delta (delta คือบอกว่าเป็นข้อมูลค่าที่เท่าใด)

ทีนี้ชักมึนครับ.. เพราะว่าใน Active Record จะต้องใช้ primary key เป็นหลักในการลบหรือปรับค่าเสมอ

แต่ก็โชคดี (รอดตาย) เพราะว่ามีคนทำ gem Composite Primary Keys เอาไว้ครับ!

(ในการติดตั้ง Composite Primary Key ถ้าเป็นรุ่นล่าสุดจะต้องใช้ Rails 2.2.0 ซึ่งยังไม่ออก ถ้าต้องการรุ่นที่ใช้ได้ สามารถติดตั้งรุ่น 1.1.0 แทน ด้วยคำสั่ง gem install composite_primary_keys -v 1.1.0)

ด้วย gem ดังกล่าว เราสามารถเพิ่มการเชื่อมโยงระหว่าง Profile กับโมเดล EduHistory ได้ดังด้านล่าง

class Profile < ActiveRecord::Base
  # ...
  has_many :edu_histories,
           :class_name => 'EduHistory',
           :foreign_key => 'vid'
end

class EduHistory < ActiveRecord::Base
  set_table_name "content_field_edu_history"
  set_primary_keys :vid, :delta
end

ทำเสร็จ เรียกใช้สคริปต์ปรับค่า ปรากฏว่าทุกอย่างหายกลายเป็นสตริงว่าง! ปรากฏว่าทำ encoding ผิด เพราะว่าข้อมูลจากเว็บเก่าเป็น tis620 ของใหม่เป็น utf8

ทีนี้ก็เลยต้องไปพึ่ง iconv ด้วยเมท็อดด้านล่าง

require 'iconv'
def to_utf(st)
  Iconv.conv('utf-8','tis620',st)
end

แก้เสร็จ ปรับค่าเรียบร้อย ดูฐานข้อมูลปรับค่าตามต้องการ รีโหลด Drupal ไม่มีอะไรเปลี่ยน!

ตกใจไปสักพัก

Drupal มี cache! ใน Drupal 6 สั่งให้ clear cache ได้ที่หน้า Admin -> Site Config -> Performance ครับ

Comments No Comments »