Professional Documents
Culture Documents
新特性介?
第二版 (中文版)
Ruby on Rails 2.1
新特性介?
第二版 (中文版)
Carlos Brando
Marcos Tapajós
© Copyright 2008 Carlos Brando. All Rights Reserved.
Carlos Brando
Website: www.nomedojogo.com
Marcos Tapajós
Website: www.improveit.com.br/en/company/tapajos
Ruby on Rails 2.1 - What's New
Chapter 1
簡介(Introduction)
這次的版本更新包含但不限於下面的特色:
‧ 時區
8
Chapter 1: 簡介(Introduction)
致謝
感謝Marcos Tapajós,如果沒有他,我們到現在肯定看不到這本書。感謝Daniel Lopes幫本
書製作了漂亮的封面。
還有chinaonrails.com社區中的朋友們,正是大家的辛勤工作,才使得我們能這麼短的時間內
即可完成翻譯,謝謝你們。
9
Ruby on Rails 2.1 - What's New
中文譯者
本書正是由China on Rails社區中一些朋友翻譯成中文的,我們是:
IceskYsl http://iceskysl.1sters.com/
jesse.cai http://www.caiwangqin.com/
第5章(ActionPack),第12章(Debug)
suave.su http://chinaonrails.com/u/suave
第1章(Introduction)
dongbin http://dongbin.org/
第3章(ActiveSupport)
海陽 http://rubyjin.cn/
第6章(ActionController)
10
Chapter 1: 簡介(Introduction)
404 http://chinaonrails.com/u/404
第8章(Railties)
ashchan http://blog.ashchan.com/
cash.zhao http://www.cashplk.com/
第2章(ActiveRecord)
Markdown Editor
正體中文翻譯
這本書的正體中文翻譯由 Zero, CFC 完成。
11
Ruby on Rails 2.1 - What's New
12
Chapter 2: ActiveRecord
Chapter 2
ActiveRecord
ActiveRecord是一個物件-關聯映射層,主要負責應用層與資料層之間的相互操作性(解耦)與資
料抽象‧(wikipedia)
SUM方法
sum方法中的表達式
現在我們可以在ActiveRecord方法當中使用表達式來處理諸如sum等各種計算,如:
13
Ruby on Rails 2.1 - What's New
Person.sum("2 * age")
sum方法預設返回值的改變
在之前的版本中,當我們使用ActiveRecord的sum方法來計算表中所有記錄的和的時候,如果
沒有跟所需條件匹配的記錄時,則預設的返回值是nil‧在Rails 2.1中,預設返回值(當沒有匹配
的記錄的時候)是0,如:
HAS_ONE
支援 through 選項
has_one方法現在支援through選項。他的用法與has_many :through相同,不過代表的是和單
一一個ActiveRecord物件的關連。
14
Chapter 2: ActiveRecord
Has_one :source_type 選項
上面提到的has_one :through方法還能接收一個:source_type選項,我會試著透過一些例子來
解釋。我們先來看看這個類別:
上面的程式碼是一個Client類別,has_many種聯絡人(contacts),由於ContactCard類別具有
多型的關連。
下一步將建立兩個類別來表示ContractCard:
15
Ruby on Rails 2.1 - What's New
Person和Business透過ContactCard表與Client類別做關聯,換句話說,我有兩個聯絡人,私
人的(personal)與工作上的(business)。然而,這樣做卻行不通,看看當我試著獲取一個
contact時發生了什麼事情:
>> Client.find(:first).contacts
# ArgumentError: /…/active_support/core_ext/hash/keys.rb:48:
# in `assert_valid_keys’: Unknown key(s): polymorphic
為了讓上述程式碼成功,我們需要使用:source_type。我們修改一下Client類別:
has_many :business_contacts,
:through => :contact_cards,
:source => :contacts,
:source_type => :business
end
注意到現在我們有兩種取得聯絡人的方式,我們可以選擇我們期待哪種:source_type。
Client.find(:first).people_contacts
Client.find(:first).business_contacts
16
Chapter 2: ActiveRecord
NAMED_SCOPE
has_finder gem已經添加到Rails當中了,有一個新名字:named_scope。
為了全面了解一下這為Rails帶來了什麼,我們看看下面的例子:
Article.published.paginate(:page => 1)
Article.published.containing_the_letter_a.count
Article.containing_the_letter_a.find(:first)
Article.containing_the_letter_a.find(:all, :conditions => {…})
通常我會建立一個新的叫做published的方法來取得所有已經發布的文章,不過在這裡我是用
了named_scope來做相同的事情,而且還能得到其他的效果。來看看另一個例子:
named_scope :anonymous_extension do
def one
1
end
end
17
Ruby on Rails 2.1 - What's New
named_scope :multiple_extensions,
:extend => [MultipleExtensionTwo, MultipleExtensionOne]
用PROXY_OPTIONS來測試NAMED_SCOPE
Named scopes是Rails 2.1中很有趣的新功能,不過使用一段時間以後你就會發現想建立一些
複雜的情況的測是會有點麻煩,看看例子:
該如何建立一個可以測試scope的結果的測試呢?
為了解決這個問題,proxy_options被開發出來。它允許我們來檢測named_scope使用的選
項。為了測試上面的程式碼,我們可以這樣寫測試:
18
Chapter 2: ActiveRecord
INCREMENT 和 DECREMENT
ActiveRecord的方法increment,increment!,decrement和decrement!現在支援一個新的可選參
數。之前版本的Rails中你可以透過這些方法指定的屬性值加一或減一。在Rails 2.1中,你可以
指定要增加或者減少的值,像這樣:
player1.increment!(:points, 5)
player2.decrement!(:points, 2)
上面的例子中,我向player加了5分,從player2減了2分。由於這是一個可選填的參數,所以之
前的程式碼不會受到影響。
FIND
Conditions
從現在開始,你可以向ActiveRecord的find方法中傳一個物件作為參數。看以下的例子:
這個例子中你可以向Account類別的find方法中傳入一個Money實體做為參數,像這樣:
19
Ruby on Rails 2.1 - What's New
amount = 500
currency = "USD"
Account.find(:all, :conditions => { :balance => Money.new(amount, currency) })
Last
到現在為止我們只能在ActiveRecord的find方法中使用三個運算子來搜尋資料,他們
是:first,:all和物件自己的id(這種情況下,我們除了id以外,不再傳入其他的參數)。
在Rails 2.1中,有了第四個運算子:last,幾個例子:
Person.find(:last)
Person.find(:last, :conditions => [ "user_name = ?", user_name])
Person.find(:last, :order => "created_on DESC", :offset => 5)
為了能明白這個新的運算子如何操作,來看看底下的測試:
def test_find_last
last = Developer.find :last
assert_equal last, Developer.find(:first, :order => 'id desc')
end
All
類別方法all是另外一個類別方法find(:all)的別名,如:
20
Chapter 2: ActiveRecord
First
類別方法first是另外一個類別方法find(:first)的別名,如:
Last
類別方法last是另外一個類別方法find(:last)的別名,如:
在NAMED_SCOPE中使用FIRST和LAST方法
所有上述的方法同樣適用於named_scope。比如我們建立一個叫recent的named_scope,下列
程式碼是有效的:
post.comments.recent.last
EAGER LOADING
為了解釋這個新的功能,我們看看下面的代碼:
21
Ruby on Rails 2.1 - What's New
我在查詢authors這個表的記錄,同時通過author_id包含進posts和comments表。這個查詢原
本會產生這樣的SQL查詢語句:
SELECT
authors."id" AS t0_r0,
authors."created_at" AS t0_r1,
authors."updated_at" AS t0_r2,
posts."id" AS t1_r0,
posts."author_id" AS t1_r1,
posts."created_at" AS t1_r2,
posts."updated_at" AS t1_r3,
comments."id" AS t2_r0,
comments."author_id" AS t2_r1,
comments."created_at" AS t2_r2,
comments."updated_at" AS t2_r3
FROM
authors
LEFT OUTER JOIN posts ON posts.author_id = authors.id
LEFT OUTER JOIN comments ON comments.author_id = authors.id
這個SQL可真夠長的了,在authors,posts和comments三個表之間用了joins。我們叫這個為
笛卡爾乘積(cartesian product)。
這種查詢往往效率上不高,所以Rails 2.1做了些改進。同樣對於Author表的查詢,現在使用了
一種不同的方式從三個表中取得資料。原來用了一條SQL語句獲得三個表的記錄,現在Rails用
三條不同的查詢語句,每個表一條,這比之前生成的查詢要更短。新的結果可以在執行上述程
式碼後的log中看到:
22
Chapter 2: ActiveRecord
絕大多數的情況下,三個簡單的查詢要比一個複雜的場查詢語句執行得更快。
BELONGS_TO
為了能在關連中使用:dependent => :destroy與:delete, belongs_to方法做了些變更,比如:
belongs_to :author_address
belongs_to :author_address, :dependent => :destroy
belongs_to :author_address_extra, :dependent => :delete,
:class_name => "AuthorAddress"
POLYMORPHIC URL
一些多型URL的輔助方法也被引入到新的Rails中,用來提供一種更為簡潔優雅的routes操作方
式。
這些方法在你想生成基於RESTful資源的URL,同時又不必顯示指定資源的類型的時候會顯得
十分有用。
使用方面則是非常的簡單,來看看一些例子(注釋的部份是Rails 2.1之前的做法):
23
Ruby on Rails 2.1 - What's New
record = Article.find(:first)
polymorphic_url(record) #-> article_url(record)
record = Comment.find(:first)
polymorphic_url(record) #-> comment_url(record)
注意到polymorphic_url方法是如何確認傳入參數的類型並生成正確的routes,內嵌資源
(Nested resources)和namespaces也同樣支援:
edit_polymorphic_path(@post)
#=> /posts/1/edit
formatted_polymorphic_path([@post, :pdf])
#=> /posts/1.pdf
24
Chapter 2: ActiveRecord
這樣,關聯的models就能避免在model中被更改,如果試圖更改就會得到一
個ActiveRecord::ReadOnlyRecord異常
ADD_TIMESTAMPS和REMOVE_TIMESTAMPS方法
現在我們有兩個新的方法add_timestamps與remove_timestamps,他們分別添加、刪
除timestamp列。看個例子:
def self.up
add_timestamps :feeds
add_timestamps :urls
end
def self.down
25
Ruby on Rails 2.1 - What's New
remove_timestamps :urls
remove_timestamps :feeds
end
CALCULATIONS
ActiveRecord::Calculations做了些更改已支援資料庫表名。這個功能在幾個不同表之間存在關
聯且相關列名相同時會非常有用。我們有兩個選項可用:
authors.categories.maximum(:id)
authors.categories.maximum("categories.id")
ACTIVERECORD::BASE.CREATE接受BLOCKS
我們已經習慣ActiveRecord::Base.new接受block作為參數了,現在create也同樣支援block
了:
我們也能用同樣的方法一次建立多個物件:
26
Chapter 2: ActiveRecord
同樣在關聯中也可以使用:
# or
CHANGE_TABLE
在Rails 2.0中建立的migrations要比之前的版本更為性感,不過要想用migrations修改一個表
可就不那麼性感了。
在Rails 2.1中修改表也由於新方法change_table而變得同樣性感了。來看看性感的例子:
27
Ruby on Rails 2.1 - What's New
新方法change_table的使用就如同他的表兄create_table,不過不是建立新的表,而是透過添
加或者刪除列或索引來更改現有的表。
28
Chapter 2: ActiveRecord
DIRTY OBJECTS
在新的Rails當中,我們同樣可以跟蹤對ActiveRecord所做的更改。我們能夠知道是否一個物件
被進行了修改,如果有更改就能跟蹤到最新的變更。來看看例子:
article = Article.find(:first)
可以看到,使用上非常的簡單,同時也能透過下列兩種方法的任意一種列出對一個物件的所有
更改:
29
Ruby on Rails 2.1 - What's New
注意到一個物件被儲存後,他的狀態也隨之改變:
如果不透過attr=來更改一個物件的狀態,那你需要透過呼叫attr_name_will_change!方法(用物
件的實際屬性名稱替換attr)來通知屬性已經被更改,來看最後一個例子:
article = Article.find(:first)
article.title_will_change!
article.title.upcase!
article.title_change #=> ['Title', 'TITLE']
PARTIAL UPDATES
Dirty Objects的實現讓另一個非常有趣的功能變為可能。
由於我們現在可以跟蹤一個物件的狀態是否發生改變,那麼為什麼不用它來避免那些不必要的
對資料庫的更新呢?
在之前版本中的Rails,當我們對一個已經存在的ActiveRecord物件呼叫save方法時,所有資料
庫中的欄位都會被更新,即使那些沒有做到任何更改的欄位。
30
Chapter 2: ActiveRecord
這種方式在有了Dirty Objects以後會有很大的改進,而實際情況也的確如此。看看保存一個有
一點更改的物件時,Rails 2.1產生的SQL查詢語句:
article = Article.find(:first)
article.title #=> "Title"
article.subject #=> "Edge Rails"
注意到,只有那些在應用中被更改的屬性才會被更新,如果沒有屬性被更改那ActiveRecord就
不執行任何更新語句。
為了開啟/關閉這個新功能,你得更改model的partial_updates屬性。
# To enable it
MyClass.partial_updates = true
如果希望對所有的models開啟/關閉這個功能,那你得編輯config/initializers/
new_rails_defaults.rb:
31
Ruby on Rails 2.1 - What's New
別忘了如果你不透過attr=更改欄位,同樣得通過config/initializers/new_rails_defaults.rb來通知
Rails,像這樣:
如果你不通知Rails,那麼上述的程式碼同樣會更改物件的屬性,但是卻不能被跟蹤到,也就無
法正確的更新資料庫中的對應欄位。
MYSQL中使用SMALLINT, INT還是BIGINT?
現在建立或者更改整行列的時候,ActiveRecord的MySQL介面(Adapter)會用更聰明的方式去
處理,它可根據:limit屬性確定一個欄位的類型應該是smallint、int還是bigint。我們來看個實現
上述功能的例子:
case limit
when 0..3
"smallint(#{limit})"
32
Chapter 2: ActiveRecord
when 4..8
"int(#{limit})"
when 9..20
"bigint(#{limit})"
else
'int(11)'
end
現在我們在migration中使用它,看看每個欄位應該匹配什麼類型:
# 0 - 3: smallint
t.integer :column_one, :limit => 2 # smallint(2)
# 4 - 8: int
t.integer :column_two, :limit => 6 # int(6)
# 9 - 20: bigint
t.integer :column_three, :limit => 15 # bigint(15)
PostgreSQL介面(Adapter)已經有這個功能了,現在MySQL也不甘落後了。
33
Ruby on Rails 2.1 - What's New
case limit
when 1; 'tinyint'
when 2; 'smallint'
when 3; 'mediumint'
when 4, nil; 'int(11)'
else; 'bigint'
end
注意到了嗎?現在只要limit是4以上就屬於bigint,所以跟原文有點出入,請注意。 未來會怎樣
變更不一定,不過我想可能會就此固定也說不定 更改的Commit網址是:http://github.com/
rails/rails/commit/290e1e2fc53d80165cc876491ec0cbe18be376cf 今天日期:
2008-06-24
HAS_ONE和BELONGS_TO中的:SELECT選項
已經為人熟知的has_one和belongs_to方法現在接收一個新屬性::select。
belongs_to方法不再支援:order了,不過不要擔心,因為基本上也沒什麼用處。
34
Chapter 2: ActiveRecord
使用單表繼承(STI)的時候儲存類別的全名
當我們的models有namespace,並且是單表繼承(STI)時,ActiveRecord僅僅將類別名稱而不
是包括namespace(demodulized)在內的全名存起來。這種情況僅僅當單表繼承的所有類別在一
個namespace的時候有效,看例子:
item = ComicCollection::Item.new
item.type # => 'Item’
item2 = CollectionItem.find(item.id)
# returns an error, because it can't find
# the class Item
新的Rails添加了一個屬性,從而使ActiveRecord能儲存類別的全名。 可以
在environment.rb當中添加如下程式碼來啟動/關閉這個功能:
ActiveRecord::Base.store_full_sti_class = true
預設值是true。
TABLE_EXISTS?方法
AbstractAdapter類別有個新方法table_exists,用法非常容易:
35
Ruby on Rails 2.1 - What's New
>> ActiveRecord::Base.connection.table_exists?("users")
=> true
在根據時間戳記的migrations引入之前,建立每個migration都會在其名字之前產生一個數字,
如果兩個migrations分別由兩個開發者產生,並且都沒有即時的提交到版本庫中,那麼最後就
有可能存在相同的前綴數字,但是不同內容的migrations,這時你的schema_info表就會過
期,同時在版本控制系統中出現衝突。
嘗試解決這個問題的方式有很多,人們建立了很多plugins以不同的方式解決這個問題。儘管有
些plugins可用,不過有一點是非常清楚的,舊的方式已經無法滿足我們的要求了。
如果你使用Git,那麼你可能再給自己挖一個更大的陷阱,因為你的團隊可能同時有幾個
working branches,過期的migrations則在每個branch中都存在。這樣當合併這些branches時
就會有嚴重的衝突問題。
為了解決這個大問題,Rails核心團隊已經改變了migrations的運作方式。他們捨棄了原有以當
前schema_info中version列的值作為migration前綴的依據方式,取而代之的是根據UTC時間按
照YYYYMMDDHHMMSS格式的字串表達方式作為前綴。
36
Chapter 2: ActiveRecord
同時建立了一個新的叫做schema_migrations的表,表中存著哪些migrations已經執行了,這
樣如果發現有人建立了一個有較小值的migration,Rails會回滾(rollback)migrations到之前的版
本,然後重新執行所有的migration直到當前的版本。
顯然這樣的做法解決了migrations所帶來的衝突問題。
有兩個新的和migrations相關的rake指令:
rake db:migrate:up
rake db:migrate:down
37
Ruby on Rails 2.1 - What's New
Chapter 3
ActiveSupport
ActiveSupport之中提供了許多非常實用的類別,而且對預設的函式庫提供了一些對Ruby on
Rails程式非常有用的外掛。(翻譯自 wikipedia)
ACTIVESUPPORT::COREEXTENSIONS::DATE::CALCULATIONS
Time#end_of_day
回傳當天的結束時間(11:59:59 PM)
38
Chapter 3: ActiveSupport
Time#end_of_week
Time#end_of_quarter
Time#end_of_year
Time#in_time_zone
Time.zone = 'Hawaii'
Time.utc(2000).in_time_zone
# => Fri, 31 Dec 1999 14:00:00 HST -10:00
Time.utc(2000).in_time_zone('Alaska')
# => Fri, 31 Dec 1999 15:00:00 AKST -09:00
39
Ruby on Rails 2.1 - What's New
Time#days_in_month
這個方法本來有個bug,當沒有指定年份的時候,對於二月他沒有辦法正確處理潤年。不過在
Rails 2.1 中這個問題已經被修正了。
這個修正的作法是,如果您沒有指定年份,則使用呼叫時的當年度作為預設值。如果您處於潤
年的話,參考下面的例子:
DateTime#to_f
40
Chapter 3: ActiveSupport
Date.current
FRAGMENT_EXIST?
在 cache_store 中新增加了兩個方法: fragment_exist? 和 exist? 。
read_fragment(path).nil?
41
Ruby on Rails 2.1 - What's New
UTC OR GMT?
這是一個很有趣的修正。:P 到目前為止,Rails 通常使用 UTC 這個縮寫。但是在
TimeZone.to_s 裡頭,它卻回傳 GMT,而不是熟系的 UTC。這是因為使用 GMT 對您的產品
使用者來說是最為熟悉的縮寫。
JSON ESCAPE
json_escape 方法就像是 html_escape 所做的事情一樣。當我們想在 HTML 頁面中顯示
JSON 字串的時候非常有用。例如:
42
Chapter 3: ActiveSupport
ActiveSupport.escape_html_entities_in_json = true
或者
43
Ruby on Rails 2.1 - What's New
TIME.CURRENT
Time 類別中的新方法。 current 的回傳值將視 config.time_zone 而定。如果之前有指定時
區,則傳為 Time.zone.now,否則回傳 Time.now 。
44
Chapter 3: ActiveSupport
45
Ruby on Rails 2.1 - What's New
Chapter 4
ActiveResource
ActiveResource是RESTful系統中的客戶端實作。使用類似代理或者遠端服務的物件可以呼叫
RESTful服務。
使用EMAIL當作使用者名稱
某些服務使用Email作為使用者名稱,這會要求使用如下形式的URL:
http://ernesto.jimenez@negonation.com:pass@tractis.com
46
Chapter 4: ActiveResource
這個URL中有兩個"@",這會帶來些問題:直譯器無法正確解析這個URL。因此
對ActiveResource的使用方式做了擴展,以方便使用Email進行身分驗證。可以這樣使用:
CLONE 方法
現在我們可以複製已經有的resource:
ryan = Person.find(1)
not_ryan = ryan.clone
not_ryan.new? # => true
要注意複製出來的物件並不複製任何類別屬性,僅複製resource屬性。
ryan = Person.find(1)
ryan.address = StreetAddress.find(1, :person_id => ryan.id)
ryan.hash = {:not => "an ARes instance"}
not_ryan = ryan.clone
not_ryan.new? # => true
not_ryan.address # => NoMethodError
not_ryan.hash # => {:not => "an ARes instance"}
47
Ruby on Rails 2.1 - What's New
逾時
由於ActiveResource使用HTTP來存取RESTful API,當伺服器回應緩慢或者伺服器罷工時會
出問題。在某些情況下呼叫ActiveResource會逾時失敗。現在可以用timeout屬性來設定逾時
時間了。
本例子將逾時設定為5秒。推荐的做法是將該值設得小一點使系統能快速偵測到失敗,以避免相
關錯誤引發伺服器錯誤。
ActiveResource內部使用Net::HTTP來發起HTTP請求。當timeout屬性設定時,該值同時被設
定到Net::HTTP物件實體的read_timeout屬性上。
該屬性的預設值是60秒。
48
Chapter 5: ActionPack
Chapter 5
ActionPack
包含ActionView(為End-User產生顯示,像是HTML、XML、JavaScript)和
ActionController(商業流程控制)。 (adapted from wikipedia)
49
Ruby on Rails 2.1 - What's New
TIMEZONE
定義一個預設的時區
一個新的選項被加入到time_zone_select方法,在你的用戶沒有選擇任何TimeZone或者資料庫
欄位為空時,你可以顯示一個預設值。它已經建立一個:default選項,你可以按照下面的方式使
用這個方法:
如果我們使用:default選項,它就會顯示TimeZone已被選擇的選項。
formatted_offset 方法
formatted_offset方法被包含在Time和DateTime類別中,回傳+HH:MM格式的UTC時差。例
如,在我們的時區(台北時間),這個方法回傳的時差是一個字串"+08:00"。
讓我們看看一些範例:
50
Chapter 5: ActionPack
從一個DateTime得到時差:
從Time:
注意這個方法回傳字串,可以格式化或者不依賴於一個被給予的參數。
with_env_tz 方法
with_env_tz方法允許我們以非常簡單的方式測試不同的時區:
def test_local_offset
with_env_tz 'US/Eastern' do
assert_equal Rational(-5, 24), DateTime.local_offset
end
with_env_tz 'US/Central' do
assert_equal Rational(-6, 24), DateTime.local_offset
end
end
這個Helper可以呼叫with_timezone,但是為了避免使用ENV['TZ']和Time.zone時混亂,他被
重新命名為with_env_tz。
51
Ruby on Rails 2.1 - What's New
Time.zone_reset!
該方法已經跟我們說再見了。
Time#in_current_time_zone
該方法修改為當Time.zone為空時傳回self
Time#change_time_zone_to_current
該方法修改為當Time.zone為空時傳回self
TimeZone#now
該方法修改為傳回ActiveSupport::TimeWithZone顯示當前在TimeZone#now中已經設定的時
區。例如:
52
Chapter 5: ActionPack
Compare_with_coercion
在Time和DateTime類別中新增了一個方法:compare_with_coercion(和一個alias <=>),它
使在Time、DateTime類別和ActiveSupport::TimeWithZone實體之間可以按照年代順序排列
的比較。為了容易理解,請看下面的例子(行尾是顯示的結果):
TimeWithZone#between?
在TimeWithZone類別中包含between?方法檢驗一個實體被建立在兩個日期之間。
@twz.between?(Time.utc(1999,12,31,23,59,59),
Time.utc(2000,1,1,0,0,1))
53
Ruby on Rails 2.1 - What's New
TimeZone#parse
這個方法從字串建立一個ActiveSupport::TimeWithZone實體。例如:
Time.zone = "Hawaii"
# => "Hawaii"
Time.zone.parse('1999-12-31 14:00:00')
# => Fri, 31 Dec 1999 14:00:00 HST -10:00
Time.zone.now
# => Fri, 31 Dec 1999 14:00:00 HST -10:00
Time.zone.parse('22:30:00')
# => Fri, 31 Dec 1999 22:30:00 HST -10:00
TimeZone#at
這個方法可以從一個Unix epoch數字建立一個ActiveSupport::TimeWithZone實體,例如:
Time.zone.at(946684800.0)
# => Fri, 31 Dec 1999 14:00:00 HST -10:00
54
Chapter 5: ActionPack
其他方法
TimeWithZone為了Ruby 1.9而準備
Time.now
# => Thu Nov 03 18:58:25 CET 2005
Time.now.sunday?
# => false
一週的每天都有對應的方法。
另一個新的是Time的to_s方法將會有一個不同的傳回值。現在當我們執行Time.new.to_s,將
得到:
Time.new.to_s
# => "Thu Oct 12 10:39:27 +0200 2006″
在Ruby 1.9中我們將得到:
55
Ruby on Rails 2.1 - What's New
Time.new.to_s
# => "2006-10-12 10:39:24 +0200″
Rails 2.1擁有哪些相關的東西?答案是所有東西,從Rails開始準備這些修
改。TimeWithZone類別,例如,剛收到一個第一個範例的方法實現。
AUTO LINK
為那些不知道這個方法的人,auto_link方法接收所有文字參數,如果這個文字包含一個e-mail
或者一個網址,將返回相同的文字,但是包含了超連結。
例如:
一些網站,像是Amazon,使用"="在URL中,該方法不認可這個符號,看這個方法怎樣處理這
種情況:
auto_link("http://www.amazon.com/Testing/ref=pd_bbs_sr_1")
# => http://www.amazon.com/Testing/ref
注意該方法會截斷"="後面的東西,因為它不支援這個符號。我的意思是,它通常是不被支援
的,但在Rails 2.1中根本沒有這個問題。
56
Chapter 5: ActionPack
同樣的方法將在稍後更新,允許在網址中使用括號。
這就是括號的範例:
http://en.wikipedia.org/wiki/Sprite_(computer_graphics)
LABELS
當使用scaffold產生一個新表單時,將會建立下面的程式碼:
這種方法更有意義,它包含了label方法。該做法在HTML標籤中回傳一個標題列。
57
Ruby on Rails 2.1 - What's New
有在標籤中注意到for參數嗎?"post_title"是包含了我們的post title的文字框。這個標籤實際上
是一個關連到post_title物件。當有人點了這個label(非連結)時,被關聯的元件就會接收到焦
點。
Robby Russell在他的Blog中寫了一個關於這個主題的有趣文章。你可以從這裡閱讀:
http://www.robbyonrails.com/articles/2007/12/02/that-checkbox-needs-a-label
在FormTagHelper中同樣也擁有label_tag方法。該方法的工作方式與label一樣,但更簡單:
該方法同樣接收:for選項,看一個範例:
這將回傳這樣的結果:
58
Chapter 5: ActionPack
<label for="my_for">Title</label>
一種使用PARTIALS的新方法
在Rails開發過程中使用partials避免重複程式碼是很正常的方式,例如:
Partial是一個程式碼片段(模版)。使用partial是避免不必要的程式碼重複。使用partial非常簡
單,你可以這樣開始::render :partial => "name",之後,你必須建立一個與你的partial同樣
名稱的文件,但是得先用一個底線作為開頭。
上面的程式碼是我們通常使用的方式,但在新的Rails版本中,我們使用不同的方式做相同的事
情,如:
在這個範例中我們使用了partial "users/_form",將收到一個名為"form"被FormBuilder建立的
變數。 不過以前的用法也是不被影響。
59
Ruby on Rails 2.1 - What's New
在一個index.atom.builder檔案中:
atom_feed do |feed|
feed.title("Nome do Jogo")
feed.updated((@posts.first.created_at))
entry.author do |author|
author.name("Carlos Brando")
end
end
end
end
Atom feed是什麼?Atom的名字是根據XML的樣式與meta資料。在網路中它是一個發佈經常
更新內容的協議,如:Blog、新聞。Feeds經常以XML或Atom的格式發佈標示為applicaton/
atom+xml類型。
60
Chapter 5: ActionPack
在Rails 2.0的第一個版本中,該方法允許:language、:root_url和:url參數,你可以從Rails文件
中獲得更多關於這些方法的訊息。但是這次的更新我們更可以包含新的namespace在一個Feed
的root元素中,例如:
將返回:
修改這個之前的範例,我們可以這樣寫:
feed.title("Nome do Jogo")
feed.updated((@posts.first.created_at))
feed.tag!(openSearch:totalResults, 10)
entry.author do |author|
author.name("Carlos Brando")
end
end
end
end
61
Ruby on Rails 2.1 - What's New
CACHE
現在所有的fragment_cache_key方法預設回傳'view/'前綴。
所有快取儲存已經從ActionController::Caching::Fragments::刪除,並替換
為ActiveSupport::Cache::。在這種情況下,如果你指定一個儲存位置,
像ActionController::Caching::Fragments::MemoryStore,你需要這樣
寫:ActiveSupport::Cache::MemoryStore。
ActionController::Base.fragment_cache_store已經不再使
用,ActionController::Base.cache_store取代了它的位置。
由於這個新的ActiveSupport::Cache::函式庫使得ActiveRecord::Base中的cache_key方法容
易快取一個Active Records,它是這樣運作的:
>> Product.new.cache_key
=> "products/new"
>> Product.find(5).cache_key
=> "products/5"
>> Person.find(5).cache_key
=> "people/5-20071224150000"
它包含了ActiveSupport::Gzip.decompress/compress使得用Zlib壓縮更加容易。
62
Chapter 5: ActionPack
現在你可以在environment選項中使用config.cache_store,指定一個預設的暫存位置。有價值
提起的是,如果tmp/cache目錄存在,預設的暫存位置是FileStore,否則使用MemoryStore。
你可以用下面的方式設定它:
config.cache_store = :memory_store
config.cache_store = :file_store, "/path/to/cache/directory"
config.cache_store = :drb_store, "druby://localhost:9192"
config.cache_store = :mem_cache_store, "localhost"
config.cache_store = MyOwnStore.new("parameter")
為了把事情變得更容易,在environments/production.rb檔案中包含了以下註解,提醒你記得這
個選項:
在字串中應用格式化標題
以前當你在一個包含了 's 的字串中使用String#titleize方法時有個bug,這個bug返回大寫
的'S,看看:
相同的範例,但是此Bug已經修復了:
63
Ruby on Rails 2.1 - What's New
ACTION_NAME
現在,要知道在運行時哪個View被呼叫,我們只需使用action_name方法:
回傳值將使用params[:action]一樣,但更優雅。
CACHES_ACTION 支援條件式
caches_action方法現在支援:if選項,允許使用條件式指定一個cache可以被暫存。例如:
在上面的例子中,只有當請求不是JSON的時候,action index將被暫存。
64
Chapter 5: ActionPack
FLASH.NOW現在可以在TEST中運作
誰沒有因為這個而頭痛過?這個問題在我們測試期間無法確定訊息已經儲存到了Flash中,因為
它在到你的測試代碼之前就被Rails清掉了。
在Rails 2.1中已經沒有這個問題。現在你可以包含下面的程式碼運行在你的測試中:
在VIEWS之外存取HELPERS
有多少次你建立了一個helper希望它在一個controller中使用?要做到這樣,你必須包含這
個helper module到這個controller之中,但這使你的程式碼看起來很髒。
Rails 2.1已經開發了一個方法在Views以外的地方使用Helpers。已很簡單的方式運作:
65
Ruby on Rails 2.1 - What's New
簡單而乾淨!
JSON
Rails現在支援POST一個JSON內容的請求,例如你可以像這樣丟出一個POST:
POST /posts
{"post": {"title": "Breaking News"}}
所有參數都將到params中。例如:
def create
@post = Post.create params[:post]
# …
end
為了那些不知道JSON是一個XML競爭者的人,它在JavaScript資料交換中相當廣泛,因為它
呈現為這種語言。它的名字是來自於:JavaScript Object Notation。
PATH NAMES
我的Blog(http://www.nomedojogo.com ,譯注:原作者的Blog)讀者們應該都知道我
的Custom Resource Name外掛,但我想它應該就快要死亡了... :(
66
Chapter 5: ActionPack
在Rails中你已經包含了:as選項在routes(一些我實現在外掛中保持相容性的東西)中,現在你也
將擁有:path_names選項改變你actions的名字。
map.resource :schools, :as => 'escolas', :path_names => { :new => 'nova' }
當然,我的外掛對於早期的Rails版本依然有效。
定義你的ROUTES文件位置
在Rails 2.1中你可以定義你的routes放在哪個文件中,把底下那段程式碼寫
到environment.rb中:
config.routes_configuration_file
這在你擁有兩種分開的前端共享同models、libraries與plugins時非常有用。
例如,getsatisfaction.com和api.getsatisfaction.com共用相同的models,但使用不同的
controllers、helpers和views。getsatisfaction擁有自己針對SEO優化的routes文件,但API
routes不需要任何關於SEO的優化。
SESSION(:ON)
或許你還不知道這個,Rails可以關閉sessions:
67
Ruby on Rails 2.1 - What's New
注意在我的範例中,我關閉了所有controllers中的session(ApplicationController),但我也能
單獨關閉某一個controller的Session。
但如果我只想打開一個controller的session,在Rails 2.1中,該方法允許:on選項,這樣做:
簡單的TESTING HELPERS
在早期最最最令人討厭的事情就是在Rails中測試helpers。我已經遭受到很多次100%的覆蓋,
建立一些給helpers用的測試。
透過ActionView::TestCase類別,在Rails 2.1中這變得簡單多了。看個範例:
module PeopleHelper
def title(text)
content_tag(:h1, text)
end
def homepage_path
people_path
68
Chapter 5: ActionPack
end
end
現在看我們在Rails 2.1中怎樣做同樣的事情:
def test_title
assert_equal "<h1>Ruby on Rails</h1>", title("Ruby on Rails")
end
def test_homepage_path
assert_equal "/people", homepage_path
end
end
69
Ruby on Rails 2.1 - What's New
Chapter 6
ActionController
70
Chapter 6: ActionController
ACTIONCONTROLLER::ROUTING
Map.root
之前的路由識別實作方法是一個接著一個的檢查是否符合規則,這樣做事實上是非常耗時間
的。新的路由識別功能則變的聰明了,他會依照路徑自動生成一棵路由樹。這樣只需要尋找路
徑上的路由規則即可,這方式將時間的耗損減少了將近 2.7 倍。
71
Ruby on Rails 2.1 - What's New
recognition_optimisation.rb 文件中新的方法和他?的工作??都在注?中写得很?尽。通过直接?
?源代?的方份可以获得这些实?的更多信息。
Assert_routing
Map.resources
假設您的網站並不是使用英文寫的,而你想在路徑當中也使用相同的語言。像是:
http://www.mysite.com.br/produtos/1234/comentarios
而不是:
http://www.mysite.com.br/products/1234/reviews
72
Chapter 6: ActionController
ACTIONCONTROLLER::CACHING::SWEEPING
在之前的 Rails 版本裡,當我們正在宣告 sweeper 的時候,我們必須使用 symbols:
73
Ruby on Rails 2.1 - What's New
Chapter 7
ActionView
74
Chapter 7: ActionView
ACTIONVIEW::HELPERS::FORMHELPER
以前的作法可能是:
現在的作法則是:
75
Ruby on Rails 2.1 - What's New
ACTIONVIEW::HELPERS::DATEHELPER
現在,所有與時間處理有關的樣板方法 (date_select, time_select, select_datetime......) 都接
受 HTML 選項了。請看下面這個例子:
date_helper
ACTIONVIEW::HELPERS::ASSETTAGHELPER
register_javascript_expansion
76
Chapter 7: ActionView
# In our view:
javascript_include_tag :monkey
register_stylesheet_expansion
這個方法跟剛剛提到的
ActionView::Helpers::AssetTagHelper#register_javascript_expansion 很類似,不同點只在
於它針對的是 CSS 文件而不是 JavaScript 文件。如下面的例子:
# In our view:
stylesheet_link_tag :monkey
77
Ruby on Rails 2.1 - What's New
ACTIONVIEW::HELPERS::FORMTAGHELPER
submit_tag
ACTIONVIEW::HELPERS::NUMBERHELPER
number_to_currency
78
Chapter 7: ActionView
之後,我們又更改成另外一個樣式:
當需要建立你自己的字串格式,您只需要使用下面的參數:
ACTIONVIEW::HELPERS::TEXTHELPER
excerpt
79
Ruby on Rails 2.1 - What's New
simple_format
標籤標成一個段落。
" 標記的段落上。
80
Chapter 8: Railties
Chapter 8
Railties
CONFIG.GEM
新特性config.gem使專案在執行時載入所有必須的gems成為可能。在environment.rb中可以指
定專案所依賴的gems,如下:
config.gem "bj"
81
Ruby on Rails 2.1 - What's New
要一次安裝所有的gem依賴,我們只需要執行下面那個Rake任務:
你也可以在專案執行時列出正被使用的gems:
如果有個gem含有rails/init.rb這個檔案並且想將它放到專案中,可以用:
這會把這個gem複製到vendor/gems/gem_name-x.x.x。若不指定gem的名稱,Rails將複製所
有gems到vendor/gem裡面。
一直到Rails 2.0外掛內的init.rb都是如以下方式使用:
82
Chapter 8: Railties
require 'openid'
ActionController::Base.send :include, OpenIdAuthentication
而在Rails 2.1中則是這樣:
config.after_initialize do
ActionController::Base.send :include, OpenIdAuthentication
end
那麼,當你執行該任務來安裝所需的gems時,這些gems都將被包含在內。
GEMS:BUILD
gems:build 任務可以用來編譯透過gems:unpack安裝的所有本地端gems擴充套件。以下是指
令:
83
Ruby on Rails 2.1 - What's New
Rails.public_path
RAILS 的日記記錄、根目錄、環境變數與快取(RAILS.LOGGER,
RAILS.ROOT, RAILS.ENV AND RAILS.CACHE)
在Rails 2.1內有新方式可以取代常數:RAILS_DEFAULT_LOGGER, RAILS_ROOT,
RAILS_ENV 和 RAILS_CACHE。取而代之的是:
# RAILS_DEFAULT_LOGGER
Rails.logger
# RAILS_ROOT
84
Chapter 8: Railties
Rails.root
# RAILS_ENV
Rails.env
# RAILS_CACHE
Rails.cache
Rails::VERSION::STRING
不過Rails 2.1就改成這樣了:
Rails.version
不如來玩玩看,我們要在plugin目錄裡面建立一個about.yml檔案,寫入下面的內容:
85
Ruby on Rails 2.1 - What's New
然後我們可以使用下面的方式來獲得相關訊息:
plugin = Rails::Plugin.new(plugin_directory)
plugin.about["author"] # => “Carlos Brando”
plugin.about["url"] # => “http://www.nomedojogo.com”
如果你能在這個新特性中找到一些好的用處並願意分享,也許將改變我對它的一些看法.. 若真
的有需要的話!
86
Chapter 9: Rake任務、外掛、腳本 (Rake Tasks, Plugins and Scripts)
Chapter 9
TASKS
rails:update
87
Ruby on Rails 2.1 - What's New
Database in 127.0.0.1
databases.rake以前只用於local資料庫,現在增加對IP為127.0.0.1的資料庫。其主要是用於
建立(create)和刪除(drop)任務。databases.rake採取refactored避免程式碼重複。
在Rails 2.1之前,你無法在專案內指定一個Rails版本來凍結,只能用當前版本作為參數;而在
Rails 2.1後則可以用下面的指令直接指定要凍結的Rails版本:
時區 (TIMEZONE)
rake time:zones:all
根據時區位移(offset)分組列出所有Rails所支援的時區;你也可以透過OFFSET參數來過濾回傳
的時區,例如:OFFSET=-6
88
Chapter 9: Rake任務、外掛、腳本 (Rake Tasks, Plugins and Scripts)
rake time:zones:us
顯示美國的所有時區,OFFSET參數依然有效。
rake time:zones:local
回傳本地端系統上Rails所支援且相同的時區。
範例:
89
Ruby on Rails 2.1 - What's New
SCRIPTS
plugin
dbconsole
這個腳本和script/console一樣,但是是針對你的資料庫操作,換句話說,它採用命令列型態來
連接到你的資料庫。
不過就目前為止,僅僅支援mysql, postgresql與sqlite(含第3版),如果你在database.yml中設
定其他類型的資料庫介面時,會顯示"Unknown command-line client for 你的資料庫名稱.
Submit a Rails patch to add support!"(不明的命令列模式用戶端。給我們一個Patch讓Rails
支援!)
90
Chapter 9: Rake任務、外掛、腳本 (Rake Tasks, Plugins and Scripts)
外掛 (PLUGINS)
現在,任何包含rails/init.rb文件的gem都可以以外掛的方式安裝在vendor目錄底下。
可以設定Rails使其在除了vendor/plugins以外的地方加入外掛,設定方法則是
在environment.rb中加入以下程式碼:
不過Rails 2.0在這個地方有個Bug,該Bug是只在vendor/plugins中尋找含有生成器
(Generator)的外掛,所以上面設定的路徑下的外掛的生成器並不會生效。在Rails 2.1中已經修
復此問題。
91
Ruby on Rails 2.1 - What's New
Chapter 10
Prototype 和 script.aculo.us
PROTOTYPE
92
Chapter 11: Ruby 1.9
Chapter 11
Ruby 1.9
詳細資訊 (DETAILS)
Rails的修改還是集中在對Ruby 1.9的支援程度,對於新版Ruby中的細微改變都做了相對應的
調整以求更好的整合,例如把File.exists?改為File.exist?。
另外,在Ruby 1.9中去掉了Base64模組(base64.rb),因此在Rails中所有使用這個模組的都得
修改為ActiveSupport::Base64。
93
Ruby on Rails 2.1 - What's New
>> date.utc
#=> Mon, 21 Feb 2005 16:11:12 +0000
94
Chapter 12: Debug
Chapter 12
Debug
95
Ruby on Rails 2.1 - What's New
Chapter 13
在POSTGRESQL中新增欄位
在使用PostgreSQL有個bug,透過migration新增一個已經存在的表的欄位時會出錯,來看看
範例:
檔案: db/migrate/002_add_cost.rb
96
Chapter 13: Bugs and Fixes
:scale => 2
end
def self.down
remove_column :items, :cost
end
end
看看我們剛建立的"cost"欄位。它是一個常見的數值,但是更該像是上面的欄位如"price",正確
應該是numeric(6, 2)。在Rails 2.1中這個問題就已經解決囉。
97
Ruby on Rails 2.1 - What's New
MIME TYPES
不允許定義被指派過的屬性以symbol型態給request.format的問題已經解決了,現在你可以以
以下的方式來撰寫程式碼:
request.format = :iphone
assert_equal :iphone, request.format
CHANGE_COLUMN的BUG FIXES
當change_column加上:null => true與:null => false會產生的問題都已經修正了。
98
Chapter 14: Additional Information
Chapter 14
Additional Information
CROSS-SITE SCRIPTING的防禦
在Rails 2.0的application.rb中應該曾留意過以下程式碼吧?
請注意上面這段程式碼中對protect_from_forgery的呼叫。
99
Ruby on Rails 2.1 - What's New
你聽說過跨站(XSS)嗎?最近一段時間XSS攻擊日益風行,就目前而言在大多數的網站中或多
或少都存在著XSS的漏洞;而XSS漏洞會被一些懷有惡意的人利用,可以用來修改網站內容、
釣魚,甚至通過JavaScript來控制其他用戶的瀏覽器等等。儘管攻擊方式不同,但是其主要的
目的都是使用戶在不知情的狀態下做出一些邪惡到讓我都想不出來的事情。最新的攻擊手段就
叫做"Cross-Site Request Forgery"。
protectfromforgery用來確保系統接收到的表單訊息都來自於系統本身,而不會是從第三方傳送
過來的;實做的原理是在表單中與Ajax請求中添加一個基於Session的標示(token),
Controller收到表單的資訊時會檢查這個Token是否匹配以決定如何回應這個Post請求。
不過這個方法並不保護以Get為主的請求,但是也沒關係,Get主要就是為了要求資料,而不會
修改到資料庫中。
* http://www.nomedojogo.com/2008/01/14/como-um-garoto-chamado-samy-pode-derrubar-seu-site/isc.sans.org/diary.htm
* http://www.nomedojogo.com/2008/01/14/como-um-garoto-chamado-samy-pode-derrubar-seu-site/isc.sans.org/diary.htm
* (????????????CSRF: ???????????????)
請謹記,此方法並不能夠萬無一失,就像平常我們都喜歡說的那樣:他並不是銀彈!(It's not a
silver bullet)
100
Chapter 14: Additional Information
使用METHOD_MISSING時請小心
由於Ruby是動態語言,這樣就使得respond_to?非常重要,你是否經常檢查某個物件是否擁有
某個方法?你是否經常使用is_a?來檢查某個物件是否是我們所需要的?
然而,人們常常忘記這樣做,先來看個method_missing的例子吧:
class Dog
def method_missing(method, *args, &block)
if method.to_s =~ /^bark/
puts "woofwoof!"
else
super
end
end
end
rex = Dog.new
rex.bark #=> woofwof!
rex.bark! #=> woofwoof!
rex.bark_and_run #=> woofwoof!
我想你肯定知道method_missing!在上面的例子中我建立了一個Dog類別的實體,然後呼叫三
個並不存在的方法:bark, bark!與bark_and_run,它就會去呼叫method_missing按照我用正
規表達式規定的只要是bark開頭就輸出"woofwoof!"。
看起來沒任何問題吧?那麼請繼續看看我用respond_to?來檢查:
101
Ruby on Rails 2.1 - What's New
看到了嗎?它返回false,也就是說它認為該實體並沒有bark方法,怎辦?是時候來按照我們的
規則來完善respond_to?了!
class Dog
METHOD_BARK = /^bark/
def respond_to?(method)
return true if method.to_s =~ METHOD_BARK
super
end
def method_missing(method, *args, &block)
if method.to_s =~ METHOD_BARK
puts "woofwoof!"
else
super
end
end
end
rex = Dog.new
rex.respond_to?(:bark) #=> true
rex.bark #=> woofwoof!
OK!搞定了!這樣的問題在Rails中普遍存在,你可以試試看用respond_to?來檢
查find_by_xxx就很明白了。
Ruby的擴展性能讓人稱奇,但是如果一不注意就有機會把自己搞得一頭霧水。
102
Chapter 14: Additional Information
當然你應該已經猜到我要說什麼了,在Rails 2.1中這個問題已經修復了,不信的話你一樣可以
試試看用respond_to?來檢查find_by_xxx
POSTGRESQL
在Rails 2.0中,PostgreSQL的介面(Adapter)支援的版本從8.1到8.3,而在Rails 2.1中支援
的版本範圍擴展到7.4到8.3。
103