ActiveSupport - 工具函式库
I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages — Alan Kay, creator of Smalltalk
Active Support 是 Rails 里的工具函式库,它也扩充了一些 Ruby 标准函式库。除了被用在 Rails 核心程式中,你也可以在你的程式中使用。本章介绍其中的一小部分较为常用的功能。
blank? 和 present?
在Rails中下面几种情况被定义是blank:
nil
或是false
- 只由空白组成的字串
- 空阵列或是空Hash
- 任何物件当使用
empty?
方法呼叫时回应为true
时
在Ruby1.9中的字串支援辨识Unicode字符,因此某些字符像是U2029(分隔线也会被视为是空白。
以数字来说,0或是0.0并不是blank
举例来说在ActionDispatch::Session::AbstractStore
中就使用了blank?
方法来确定session key是否存在:
def ensure_session_key!
if @key.blank?
raise ArgumentError, 'A key is required...'
end
end
present?
方法就是blank?
方法的相反,判断是否存在,因此present?
方法与!blank?
方法两者表达的意思是一样的。
try
try
是一个相当实用的功能,当我们去呼叫一个物件的方法,而该物件当时却是nil
的时候,Rails会抛出method_missing
的例外,最常见的例子像是我们想判断某些动作只有管理员可以进行操作,因此我们通常会这样写:
if current_user.is_admin?
# do something
end
但这样的写法当使用者其实是未登入时我们的current_user
便会回传nil
,而再去呼叫is_admin?
方法时便会发生错误抛出例外,try
方法便是运用在这样的情况,刚刚的例子我们可以改写成
if current_user.try(:is_admin?)
# do something
end
这样子当使用者并未登入的时候会直接回传nil
而不会再去呼叫后面的is_admin?
方法
to_param
Rails中所有的物件都支援to_param
方法,这个方法会帮我们将物件转为可用的数值并以字串表示:
7.to_param # => "7" to_param 方法默认会去呼叫物件的 to_s 方法
Rails中某些类别去复写了to_param
方法,像是nil
、true
、false
等在呼叫to_param
时会回传自己本身,而阵列会将所有的元素印出来并加上”/”:
[0, true, String].to_param # => "0/true/String"
值得注意的是,在Rails的Routing系统中,我们常使用/:id
来表示该物件的id,事实上是Rails改写了ActiveRecord::Base
中的to_param
方法,让它回传数据库的 id,当然我们也可以自己去改写他:
class User
def to_param
"#{id}-#{name.parameterize}"
end
end
那么当我们呼叫userpath(@user)
的时候,_Rails就会转换成 “#{id}-#{name.parameterize}”,这技巧常使用在改写URL的表现方式
to_query
toquery
会帮我们去呼叫物件的to_param
方法,并且帮我们整理成查询的格式并输出,例如我们去改写_User Model的to_param
方法:
class User
def to_param
"#{id}-#{name.parameterize}"
end
end
current_user.to_query('user') # => user=357-john-smith
toquery
会将输出的符号都以逸出程式码(_escape)取代,无论是键或是值,因此更方便去处理:
account.to_query('company[name]')
# => "company%5Bname%5D=Johnson+%26+Johnson"
当呼叫阵列的to_query
方法时会呼叫阵列中所有元素的to_query
方法,并且使用"[]"
做为键值,并在每个元素与元素间插入"&"
做为区隔:
[3.4, -45.6].to_query('sample')
# => "sample%5B%5D=3.4&sample%5B%5D=-45.6"
呼叫Hash的toquery
方法时,当没有给予_query的字串时默认会以Hash本身的键值做为query字串输出(to_query(key)
):
{:c => 3, :b => 2, :a => 1}.to_query # => "a=1&b=2&c=3"
换句话说你也可以自己指定做为query的字串,这个字串会变为Hash本身键值的namespace:
{:id => 89, :name => "John Smith"}.to_query('user')
# => "user%5Bid%5D=89&user%5Bname%5D=John+Smith"
扩充 Class
Class Attributes
class_attribute
class_attribute
这个方法可以宣告一个或多个类别变量,且此类别变量是可以被继承的类别所复写的:
class A
class_attribute :x
end
class B < A; end
class C < B; end
A.x = :a
B.x # => :a
C.x # => :a
B.x = :b
A.x # => :a
C.x # => :b
C.x = :c
A.x # => :a
B.x # => :b
也可以在实例变量的层级被读取或复写:
A.x = 1
a1 = A.new
a2 = A.new
a2.x = 2
a1.x # => 1, comes from A
a2.x # => 2, overridden in a2
class_attribute
同时也帮你定义了查询的方法,你可以在变量名称后面加上问号来看此变量是否已经被定义,以上面的例子来说就是x?
,结果会回传true
或false
cattr_reader
、cattr_writer
与cattr_accessor
cattrreader
、cattr_writer
与cattr_accessor
这三个方法就像是attr
*
的类别变量版本,透过这三个方法可以建立相对应的类别变量及存取方法:
class MysqlAdapter < AbstractAdapter
# Generates class methods to access @@emulate_booleans.
cattr_accessor :emulate_booleans
self.emulate_booleans = true
end
同时也会帮我们建立实例变量的方法,让我们可以在实例变量层级来存取:
module ActionView
class Base
cattr_accessor :field_error_proc
@@field_error_proc = Proc.new{ ... }
end
end
如此我们便可以在ActionView
中存取field_error_proc
。
更多关于class_attribute的部份可以参考深入Rails3: ActiveSupport 的 class_attribute
扩充 String
安全输出
当输出HTML格式的资料时需要格外注意,例如当你文章的标题存成Flanagan & Matz rules!
时,在没有格式化的情况下&
会被逸出码所取代成&
,另一方面是安全性上的问题,因为使用者可能就会在字段中写入攻击性的script造成安全性问题,因此在处理字串输出时我们都会对输出进行处理:
我们可以使用htmlsafe?
方法来判断字串是否是_html安全格式,一般字串默认是false
:
"".html_safe? # => false
你可以透过html_safe
方法来指定字串:
s = "".html_safe
s.html_safe? # => true
你必须注意htmlsafe
这个方法并不会帮你处理_html中的tag,这方法只是单纯的指定该字串是否为htmlsafe
,你必须自己去处理_tag的部份:
s = "<script>...</script>".html_safe
s.html_safe? # => true
s # => "<script>...</script>"
当你使用像是concat
、<<
或是+
的方式将一个不是html_safe
的字串与一个html_safe
的字串作结合时,会输出成一个html_safe
的字串,但将原先不是html_safe
的字串内容作逸出码的处理:
"".html_safe + "<" # => "<"
"".html_safe + "<".html_safe # => "<" 如果是 html_safe 的内容则不会作逸出码的处理
但在Rails3之后版本的View会自动帮你把不安全的部份作逸出处理,因此你大可直接在View中使用像是<%= @post.title %>
来输出,但由于这样会直接把HTML的部份都去除,如果你希望保持HTML的格式那么你可以使用raw
这个helper来帮你输出:
<%= raw @post.content %>
基于上述安全性的前提,任何可能改变原有字串的方法都会将原先的字串变为unsafe的状态,像是
downcase
、gsub
、strip
、chomp
、underscore
等,但是复制的方法像是dup
或是clone
并不会影响。
truncated
truncate
方法会将字串截断为指定的长度:
"Oh dear! Oh dear! I shall be late!".truncate(20)
# => "Oh dear! Oh dear!..."
你可以使用omission
参数将撷取后的字串的后面取代为指定的文字:
"Oh dear! Oh dear! I shall be late!".truncate(20, :omission => '…')
# => "Oh dear! Oh …"
你必须注意truncate
后的字串不是html_safe
的,因此在你没有使用raw
来作处理的时候会将html
格式直接输出:
"<p>Oh dear! Oh dear! I shall be late!</p>".truncate(20, :omission => "(blah)")
=> "<p>Oh dear! Oh(blah)"
为了避免撷取的部分会将单字直接从中撷取,你可以用:separator
参数来取代被撷取的单字部分:
"Oh dear! Oh dear! I shall be late!".truncate(18)
# => "Oh dear! Oh dea..."
"Oh dear! Oh dear! I shall be late!".truncate(18, :separator => ' ')
# => "Oh dear! Oh..."
:separator
无法使用正规表示法
inquiry
inquiry
方法会将字串转型为StringInquirer
物件,可以让我们像用一般方法的方式来比对字串是否符合,最常见的例子就是判断Rails正在使用的版本:
Rails.env.production? # 等同于 Rails.env == "production"
因此你可以用inquiry
将一般字串转型后来达到一样的效果:
"production".inquiry.production? # => true
"active".inquiry.inactive? # => false
Key-based Interpolation
Ruby1.9以后的版本支援使用%
符号做为字串中的变量键值:
"I say %{foo}" % {:foo => "wadus"} # => "I say wadus"
"I say %{woo}" % {:foo => "wadus"} # => KeyError
字串转换相关
to_date
、to_time
与to_datetime
三个方法是与转换时间相关的方法,可以帮我们将字串转型为时间物件:
"2010-07-27".to_date # => Tue, 27 Jul 2010
"2010-07-27 23:37:00".to_time # => Tue Jul 27 23:37:00 UTC 2010
"2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000
to_time
另外还接受:utc
或是:local
的参数用来指定时区,默认为:utc
:
"2010-07-27 23:42:00".to_time(:utc) # => Tue Jul 27 23:42:00 UTC 2010
"2010-07-27 23:42:00".to_time(:local) # => Tue Jul 27 23:42:00 +0200 2010
其他实用的方法
pluralize
方法可以帮我们将名词字串转为复数的名词:
"table".pluralize # => "tables"
"ruby".pluralize # => "rubies"
"equipment".pluralize # => "equipment"
而singularize
方法则是可以帮我们转为单数:
"tables".singularize # => "table"
"rubies".singularize # => "ruby"
"equipment".singularize # => "equipment"
camelize
可以帮我们将字串转为驼峰式的字串:
"product".camelize # => "Product"
"admin_user".camelize # => "AdminUser"
在Rails中也会将路径中”/”符号转为Class及Module中的命名空间符号::
"backoffice/session".camelize # => "Backoffice::Session"
而underscore
则是将原先驼峰式的字串转为路径式的字串:
"Product".underscore # => "product"
"AdminUser".underscore # => "admin_user"
"Backoffice::Session".underscore # => "backoffice/session"
titleize
方法可以将字串标题化,将单字的开头皆转为大写:
"alice in wonderland".titleize # => "Alice In Wonderland"
"fermat's enigma".titleize # => "Fermat's Enigma"
dasherize
可以将字串中的底线转为横线:
"name".dasherize # => "name"
"contact_data".dasherize # => "contact-data"
demodulize
可以将整串的namespace
去除仅留下最后的Class name或是Module name:
"Backoffice::UsersController".demodulize # => "UsersController"
"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
deconstantize
则是相反的作用,将上层的部分全部找出来:
"Backoffice::UsersController".deconstantize # => "Backoffice"
"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"
必须注意的是这是处理字串,因此若直接仅给予Class name或是Module name是无法找出上层参照的
"Product".deconstantize # => ""
parameterize
可以将字串转为适合url的方式:
"John Smith".parameterize # => "john-smith"
"Kurt Gödel".parameterize # => "kurt-godel"
tableize
除了会将单数名词转为复数之外,还会将驼峰式的名词改为底线:
"InvoiceLine".tableize # => "invoice_lines"
tableize
的作用其实在于帮助你找出Model的资料表名称
classify
则是tableize
的相反,能够帮你从资料表的名称转为Model:
"people".classify # => "Person"
"invoices".classify # => "Invoice"
"invoice_lines".classify # => "InvoiceLine"
humanize
可以帮你将Model的属性转为较容易阅读的形式:
"name".humanize # => "Name"
"author_id".humanize # => "Author"
"comments_count".humanize # => "Comments count"
扩充 Enumerable
group_by
group_by
可以将列举依照指定的字段分组出来,例如将记录依照日期排序出来:
latest_transcripts.group_by(&:day).each do |day, transcripts|
p "#{day} -> #{transcripts.map(&:class).join(', ')}"
end
"2006-03-01 -> Transcript"
"2006-02-28 -> Transcript"
"2006-02-27 -> Transcript, Transcript"
"2006-02-26 -> Transcript, Transcript"
"2006-02-25 -> Transcript"
"2006-02-24 -> Transcript, Transcript"
"2006-02-23 -> Transcript"
sum
sum
可以算出集合的加总:
[1, 2, 3].sum # => 6
(1..100).sum # => 5050
sum
的作用其实就是帮你将元素彼此用+
方法连结起来:
[[1, 2], [2, 3], [3, 4]].sum # => [1, 2, 2, 3, 3, 4]
%w(foo bar baz).sum # => "foobarbaz"
{:a => 1, :b => 2, :c => 3}.sum # => [:b, 2, :c, 3, :a, 1]
对空集合呼叫sum
默认回传0,但你也可以改写:
[].sum # => 0
[].sum(1) # => 1
如果给予一个block,那么会迭代执行集合中的元素运算后再将结果加总起来:
(1..5).sum {|n| n * 2 } # => 30
[2, 4, 6, 8, 10].sum # => 30
空集合的元素也可以这样被改写:
[].sum(1) {|n| n**3} # => 1
each_with_object
inject
方法可以为集合中的元素迭代的给予指定的元素并运算:
[2, 3, 4].inject(1) {|product, i| product*i } # => 24
如果给予inject
的参数为一个空区块,那么inject
会将结果整理成Hash,但需注意在运算的结尾必须回传运算结果:
%w{foo bar blah}.inject({}) do |hash, string|
hash[string] = "something"
hash # 需要回传运算结果
end
=> {"foo"=>"something" "bar"=>"something" "blah"=>"something"}
each_with_object
这个方法也可以达到一样的效果,差别在于你不用回传运算结果:
%w{foo bar blah}.each_with_object({}){|string, hash| hash[string] = "something"}
=> {"foo"=>"something", "bar"=>"something", "blah"=>"something"}
index_by
indexby
可以帮我们将集合元素以指定的字段做为键值整理成_Hash:
invoices.index_by(&:number)
# => {'2009-032' => <Invoice ...>, '2009-008' => <Invoice ...>, ...}
键值通常必须是唯一的,若不是唯一的话将会以最后出现的元素做为判断值。
many?
many?
是可个好用的方法可以帮助我们快速的判断集合的数量是否大于1:
<% if pages.many? %>
<%= pagination_links %>
<% end %>
如果对many?
传入区块运算时,many?
仅会回传运算结果是true
的结果:
@see_more = videos.many? {|video| video.category == params[:category]}
扩充 Array
随机挑选
shape_type = ["Circle", "Square", "Triangle"].sample
# => Square, for example
shape_types = ["Circle", "Square", "Triangle"].sample(2)
# => ["Triangle", "Circle"], for example
增加元素
prepend
会将新元素插入在整个阵列的最前方(index
为0的位置)
%w(a b c d).prepend('e') # => %w(e a b c d)
[].prepend(10) # => [10]
append
会将元素插入在阵列的最后方:
%w(a b c d).append('e') # => %w(a b c d e)
[].append([1,2]) # => [[1,2]]
options_extractions!
在Rails中我们常常会看到一个方法可以传入不定数量的参数,例如:
my_method :arg1
my_method :arg1, :arg2, :argN
my_method :arg1, :foo => 1, :bar => 2
一个方法能够接收不定数量的多个参数主要仰赖的是extractoptions!
这个方法会帮我们将传入的集合参数展开,若没有传入参数时这个方法便会回传空_Hash
def my_method(*args)
options = args.extract_options!
puts "参数: #{args.inspect}"
puts "选项: #{options.inspect}"
end
my_method(1, 2)
# 参数: [1, 2]
# 选项: {}
my_method(1, 2, :a => :b)
# 参数: [1, 2]
# 选项: {:a=>:b}
因此extract_options!
这个方法可以很方便的帮你展开一个阵列中选项元素,最主要的作用就是展开传入方法的参数。
Grouping
in_groups_of
方法可以将阵列依照我们指定的数量做分组:
[1, 2, 3].in_groups_of(2) # => [[1, 2], [3, nil]]
如果给予一个block的话可以将分组的元素做yield:
<% sample.in_groups_of(3) do |a, b, c| %>
<tr>
<td><%=h a %></td>
<td><%=h b %></td>
<td><%=h c %></td>
</tr>
<% end %>
在元素数量不够分组的时候默认在不足的元素部分补nil
,像第一个例子中最后一个元素是nil
,你也可以在呼叫in_groups_of
方法的同时传入第二个参数做为不足元素的填充值:
[1, 2, 3].in_groups_of(2, 0) # => [[1, 2], [3, 0]]
你也可以传入false
指定当元素不足的时候就不要以nil
做为填充值,也由于这层关系你无法指定false
来做为一个填充值:
[1, 2, 3].in_groups_of(2, false) # => [[1, 2], [3]]
in_groups_of
这个方法最常拿来使用在当你页面每一列想要有n个元素来呈现的时候,例如假设我们有一个待办清单的网站,我们希望页面上每一列可以有四笔清单,我们可以这样写:
<% @tasks.in_groups_of(4) do |tasks| %>
<ul>
<% tasks.each do |task| %>
<li><%= task.name %></li>
<% end %>
</ul>
<% end %>
split
这个方法会依照你给的条件来判断阵列内的元素做分割:
[1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]] 如果阵列内元素是3的话做分割
(1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]] 如果阵内元素是3的倍数就做分割
扩充 Hash
Merging 合并
Ruby本身有Hash#merge方法来合并两个Hash
{:a => 1, :b => 1}.merge(:a => 0, :c => 2)
# => {:a => 0, :b => 1, :c => 2}
reverse_merge与reverse_merge!
在合并Hash时可能会遇到有一样的key造成需要判断以哪个key值做为依据的情况:
a = {:a => 1, :b => 2}
b = {:a => 3, :c => 4}
a.merge(b) # Ruby 本身的 merge 不会改变原先呼叫的 hash,并且以后面的 hash 为优先产生一个新的 hash
=> {:a=>3, :b=>2, :c=>4}
a # => {:a=>1, :b=>2}
b # => {:a=>3, :c=>4}
a.reverse_merge(b) # reverse_merge 不会改变原先呼叫的 hash,以前面呼叫的 hash 为优先产生一个新的 hash
=> {:a=>1, :c=>4, :b=>2}
a # => {:a=>1, :b=>2}
b # => {:a=>3, :c=>4}
a.reverse_merge!(b) # reverse_merge! 会以前面呼叫的 hash 优先并直接改变原先呼叫的 hash,不会产生新的 hash
=> {:a=>1, :b=>2, :c=>4}
a # => {:a=>1, :b=>2, :c=>4}
b # {:a=>3, :c=>4}
因此reversemerge
这个方法常用在指定_hash的默认值:
options = options.reverse_merge(:length => 30, :omission => "...")
deep_merge与deep_merge!
在两个hash的键值相同,而值也是个hash的情况下,我们可以使用deepmerge
将两个_hash组合:
{:a => {:b => 1}}.deep_merge(:a => {:c => 2})
# => {:a => {:b => 1, :c => 2}}
deep_merge!
的版本则是会直接更改呼叫的hash值
Key 键值
except与except!
except
方法可以将指定的键值从hash中移除:
{:a => 1, :b => 2}.except(:a) # => {:b => 2}
except
通常用在我们更新资料时对一些不想被更改的资料字段做保护的动作:
params[:account] = params[:account].except(:plan_id) unless admin?
@account.update(params[:account])
except!
会直接更改原本呼叫的hash而不是产生一个新的hash
stringify_keys 与 stringify_keys!
stringifykeys
可以将_hash中的键值改为字串:
{nil => nil, 1 => 1, :a => :a}.stringify_keys
# => {"" => nil, "a" => :a, "1" => 1}
如果hash中有冲突发生,则以后者优先:
{"a" => 1, :a => 2}.stringify_keys
=> {"a"=>2}
这方法方便我们将传入的hash做一致性的处理,而不用去考虑使用者传入的hash是用symbol或是字串
stringifykeys!
的版本会直接更改呼叫的_hash值
symbolize_keys与symbolize_keys!
symbolizekeys
则是会把_hash中的键值都呼叫tosym
方法将之改为_symbol:
{nil => nil, 1 => 1, "a" => "a"}.symbolize_keys
# => {1 => 1, nil => nil, :a => "a"}
如果hash中有冲突发生,以后面的优先:
{"a" => 1, :a => 2}.symbolize_keys
=> {:a=>2}
symbolizekeys!
版本会直接更改呼叫的_hash值
to_options与to_options!
to_options
与to_options!
方法作用与symbolize_keys
方法是一样的
assert_valid_keys
assertvalid_keys
是用来指定_hash键值的白名单,没有在白名单里的键值出现在hash中都会抛出例外:
{:a => 1}.assert_valid_keys(:a) # => {:a=>1}
{:a => 1}.assert_valid_keys("a") # ArgumentError: Unknown key: a
分割 Hash
slice
方法可以帮我们从hash中切出指定的值:
{:a => 1, :b => 2, :c => 3}.slice(:a, :c)
# => {:c => 3, :a => 1}
{:a => 1, :b => 2, :c => 3}.slice(:b, :X)
# => {:b => 2} # 不存在的值会被忽略
这方法也常用来做为检验hash的白名单使用,将核可的值从hash中抽出
slice!
的版本会直接更改呼叫的hash值
抽取
extract!
方法会将hash中指定的值取出变为一个新的hash,并将原先的hash中减去我们抽取出来的部分:
hash = {:a => 1, :b => 2}
rest = hash.extract!(:a) # => {:a => 1}
hash # => {:b => 2}
扩充 DateTime
DateTime
本身已经写好很多实用的方法可以方便我们计算时间:
yesterday
tomorrow
beginning_of_week (at_beginning_of_week)
end_of_week (at_end_of_week)
monday
sunday
weeks_ago
prev_week
next_week
months_ago
months_since
beginning_of_month (at_beginning_of_month)
end_of_month (at_end_of_month)
prev_month
next_month
beginning_of_quarter (at_beginning_of_quarter)
end_of_quarter (at_end_of_quarter)
beginning_of_year (at_beginning_of_year)
end_of_year (at_end_of_year)
years_ago
years_since
prev_year
next_year
DateTime
并不支援日光节约时间
DateTime.current
类似于 Time.now.to_datetime
,但他的结果会依使用者本身的时区而定,如果在时区有设定的情况下,还会有些其他好用的方法像是DateTime.yesterday
、DateTime.tomorrow
,也可以使用像是past?
及future?
来与DateTime.current
做判断
seconds_since_midnight
会回传从午夜00:00:00到指定时间所经过的秒数:
now = DateTime.current # => Mon, 07 Jun 2010 20:26:36 +0000
now.seconds_since_midnight # => 73596
utc
可以把时间转为UTC格式
now = DateTime.current # => Mon, 07 Jun 2010 19:27:52 -0400
now.utc # => Mon, 07 Jun 2010 23:27:52 +0000
utc?
可以判断是否为UTC格式
now = DateTime.now # => Mon, 07 Jun 2010 19:30:47 -0400
now.utc? # => false
now.utc.utc? # => true
advance
是个非常好用的方法,当我们想要找出相对于一个时间加加减减后的另一个时间非常好用:
d = DateTime.current
# => Thu, 05 Aug 2010 11:33:31 +0000
d.advance(:years => 1, :months => 1, :days => 1, :hours => 1, :minutes => 1, :seconds => 1)
# => Tue, 06 Sep 2011 12:34:32 +0000
要注意的是你如果呼叫多次advance
去做计算,其结果可能与呼叫一次是有差异的,你可以参考下面的例子:
d = DateTime.new(2010, 2, 28, 23, 59, 59)
# => Sun, 28 Feb 2010 23:59:59 +0000
d.advance(:months => 1, :seconds => 1)
# => Mon, 29 Mar 2010 00:00:00 +0000
d.advance(:seconds => 1).advance(:months => 1)
# => Thu, 01 Apr 2010 00:00:00 +0000
change
可以传入参数给指定的时间将它改为我们想要的时间:
now = DateTime.current
# => Tue, 08 Jun 2010 01:56:22 +0000
now.change(:year => 2011, :offset => Rational(-6, 24))
# => Wed, 08 Jun 2011 01:56:22 -0600 将年份跟时区指定为我们传入的参数
如果你传入的参数只有hour
的时候并且为0的时候,分钟及秒数都会被设为0:
now.change(:hour => 0)
# => Tue, 08 Jun 2010 00:00:00 +0000
同样的,如果传入的参数只有min
并且值为0的时候,秒数就会被设为0:
now.change(:min => 0)
# => Tue, 08 Jun 2010 01:00:00 +0000
DateTime
也可以方便得用时间间隔来做加减:
now = DateTime.current
# => Mon, 09 Aug 2010 23:15:17 +0000
now + 1.year
# => Tue, 09 Aug 2011 23:15:17 +0000
now - 1.week
# => Mon, 02 Aug 2010 23:15:17 +0000
扩充 Time
Time
继承从DateTime
来很多好用的方法:
past?
today?
future?
yesterday
tomorrow
seconds_since_midnight
change
advance
ago
since (in)
beginning_of_day (midnight, at_midnight, at_beginning_of_day)
end_of_day
beginning_of_week (at_beginning_of_week)
end_of_week (at_end_of_week)
monday
sunday
weeks_ago
prev_week
next_week
months_ago
months_since
beginning_of_month (at_beginning_of_month)
end_of_month (at_end_of_month)
prev_month
next_month
beginning_of_quarter (at_beginning_of_quarter)
end_of_quarter (at_end_of_quarter)
beginning_of_year (at_beginning_of_year)
end_of_year (at_end_of_year)
years_ago
years_since
prev_year
next_year
Time
的change
方法接受一个额外的参数:usec
Time
不同于DateTime
,是能正确计算出时区间的差异,DateTime
是不支援时光节约时间的
Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
# In Barcelona, 2010/03/28 02:00 +0100 becomes 2010/03/28 03:00 +0200 due to DST.
t = Time.local_time(2010, 3, 28, 1, 59, 59)
# => Sun Mar 28 01:59:59 +0100 2010
t.advance(:seconds => 1)
# => Sun Mar 28 03:00:00 +0200 2010
使用since
或是ago
时,如果得到的时间无法用Time
来呈现时,会自动转型为DateTime
Time.current
Time.current
类似于Time.now
会回传现在时间,唯一的差别在于Time.current
会依照使用者的时区来回传,在有定义时区的情况下你也可以使用像是Time.yesterday
、Time.tomorrow
的方法,以及像是past?
、today?
、future?
等用来与Time.current
比较的方法
也因为如此,当我们在做时间的处理时尽量使用像是
Time.current
而少用Time.now
,不然很有可能会出现时区问题所造成的错误计算
all_day、all_week、all_month、all_quarter 与 all_year
上面所列的all_*
方法会回传与指定时间相较的一个区间:
now = Time.current
# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
now.all_day
# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Mon, 09 Aug 2010 23:59:59 UTC +00:00
now.all_week
# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Sun, 15 Aug 2010 23:59:59 UTC +00:00
now.all_month
# => Sat, 01 Aug 2010 00:00:00 UTC +00:00..Tue, 31 Aug 2010 23:59:59 UTC +00:00
now.all_quarter
# => Thu, 01 Jul 2010 00:00:00 UTC +00:00..Thu, 30 Sep 2010 23:59:59 UTC +00:00
now.all_year
# => Fri, 01 Jan 2010 00:00:00 UTC +00:00..Fri, 31 Dec 2010 23:59:59 UTC +00:00
Time Constructors
Active Support
定义了 Time.current
,等同于Time.zone.now
,如果使用者已经有定义时区的话,那么Time.now
也会得到一样的效果:
Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
Time.current
# => Fri, 06 Aug 2010 17:11:58 CEST +02:00
localtime
这个_class method可以帮助我们建立基于使用者时区设定的时间物件:
Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
Time.local_time(2010, 8, 15)
# => Sun Aug 15 00:00:00 +0200 2010
utctime
可以回传_UTC格式的时间物件:
Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
Time.utc_time(2010, 8, 15)
# => Sun Aug 15 00:00:00 UTC 2010
local_time
与utc_time
这两个方法都接受七个时间参数:year
、month
、day
、hour
、min
、sec
以及usec
,year
是必填参数,month
和day
默认为1,而其他参数默认为0
时间也可以使用简单的加减:
now = Time.current
# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
now + 1.year
# => Tue, 09 Aug 2011 23:21:11 UTC +00:00
now - 1.week
# => Mon, 02 Aug 2010 23:21:11 UTC +00:00
Concerns
假设我们现在有一个Module A与Module B有相依关系:
Module A
self.included(base)
include B
# 当 Module A 被 include 后便 include Module B
end
end
今天当我们想要include Module A时,由于Module A与Module B的相依关系,我们必须同时将两个Module都include进来:
class Something
include A, B
end
但我们其实没有必要我想要include的Module之间的相依关系,如此便有了ActiveSupport::Concern
的意义,就是让我们只需要include我们想要使用的Module,其他的相依关系我们不需要去考虑他,你所需要作的只是在Module A中extend ActiveSupport::Concern
:
Module A
extend ActiveSupport::Concern
included do
include B
# 当 Module A 被 include 后便 include Module B
end
end
如此一来我们只需要include A
就可以搞定了!
更多内容请请参考:深入Rails3: ActiveSupport::Concern
Benchmarks
benchmark
方法可以用来测试template的执行时间并记录起来:
<% benchmark "Process data files" do %>
<%= expensive_files_operation %>
<% end %>
这样将会在你的log记录中增加一笔像是“Process data files (345.2ms)”
的纪录,你便可用来测量并改善你的程式码。
你也可以设定log
的层级,默认是info
:
<% benchmark "Low-level files", :level => :debug do %>
<%= lowlevel_files_operation %>
<% end %>
Configurable
Configurable
这个模组是Rails本身用来作为AbstractController::Base
的设定使用,我们可以借用这个功能来为我们的类别增加设定选项:
class Employee
include ActiveSupport::Configurable
end
employee = Employee.new
employee.config.sex = male
employee.config.permission = :normal
employee.config.salary = 22000
config_accessor
方法可以帮助我们将这些设定转为方法:
class Employee
include ActiveSupport::Configurable
config_accessor :sex, :permission, :salary
# 现在你可以使用 employee.sex, employee.permission, employee.salary 来取用这些设定
end
上面的范例让每个Employee
的实例变量都能有自己的设定,但其实我们也可以有类别层级的设定让每个实例变量都能共享设定:
# 设定类别层级的设定
Employee.config.duty_hour = 8
# 新增一个employee
employee = Employee.new
employee.config.duty_hour # => 8
# 由实例变量更改设定
employee.config.duty_hour = 5
# 会更改类别层级设定
Employee.config.duty_hour # => 5