一、心得体会
今天完成了什么
- 看了20页的镐头书
- 学了bag的10个controller
收获什么?
- 新增、编辑都放在show视图里
- 活动平台
- 订单活动
- 地址、地址快照、品牌
二、读书笔记
回顾昨天学到内容
- 肯定匹配 =~
举个栗子:
name = "ni shi da sha gua"
name =~ /a/ -> 8
匹配操作符返回匹配发生的字符位置,它们也有副作用,会设置一些Ruby变量???
$`得到匹配之前的那部分字符串,$'得到匹配之后的那部分字符串。
所以,可以这么写show_regexp方法,以说明具体的模式在何处发生匹配。
举个栗子:
def show_regexp(a, re)
if a =~ re
"#{$` } <<#{$&}>>#{$' }"
else
"no match"
end
end
show_regexp('very interesting', /t/) -> very in <<t>>eresting
这个匹配也设置了线程局部变量(thread-local variables)???,$~与$1直到$9。
$~变量是MatchData对象,它持有你想知道的有关匹配的所有信息。???
$1等持有匹配各个部分的值,我们在后面会谈到这些。对那些看到类似Perl变量名就心怀畏惧的人来讲,请稍安勿躁得啦!
关于模式
每个正则表达式包含一种模式,用来对字符串进行正则表达式的匹配。
在模式内,除了.,|,(,),[,],{,},+,\,^,$,*和?字符之外,所有字符串匹配它们本身。
注意:在这些特殊字符之前放置一个反斜线便可以匹配它们的字面量。举个栗子:
show_regexp("yes | no", /\|/) -> yes <<|>> no
如果不加反斜线的话:
show_regexp("yes | no", /|/) -> <<>>yes | no
返回的竟然是:
<<>>yes | no
为什么会这样呢?
这是因为|是特别字符,未转义的竖线要么匹配在它之前的正则表达式,要么匹配在它之后的正则表达式。
这里的竖线(|)前后都是空,所以匹配的也是空,匹配的顺序是从左到右,所以就是上面那个样子。
但是,有些同学会疑惑,那到底是先匹配模式中竖线(|)的左边还是竖线的右边呢?让我们来做个对照试验。
举个栗子:
show_regexp(" red ball blue sky", /red|/) -> <<red>> ball blue sky
show_regexp("red ball blue sky", /|red/) -> <<>>red ball blue sky
red放在左边的时候,能匹配出字符串中的red
而red放在右边的时候,则不能匹配出字符串的red,反而匹配的是空。
这说明,前者在匹配的时候,是从模式中竖线的左边和字符串中的最左边的空开始匹配。
show_regexp("red ball blue sky", /red|blue/) -> <<red>> ball blue sky
show_regexp("red ball blue sky", /blue|red/) -> <<red>> ball blue sky
show_regexp("blue ball red sky", /red|blue/) -> <<blue>> ball red sky
show_regexp("blue ball red sky", /blue|red/) -> <<blue>> ball red sky
从上面的例子中,我们可以发现,无论匹配模式里的red和blue放在哪边,结果都是一样的,所以。
但是,如果把匹配字符串里的blue和red交换位置,我们会发现,匹配出来的不是red,竟然是blue。
所以,无论是先匹配模式中竖线的左边还是右边,都不会影响结果。
5.4.2基于模式的替换(Pattern-Based Substitution)
在字符串中能够发现模式有时就已经足够好了,如果你的朋友给出难题,让你找出顺序包含字母a,b,c,d和e的词,你可能会使用模式/a.b.cd.e/去查找词表并找出abjectedness,absconded,ambuscade和carbacidometer这些词等等,这种事情肯定有价值的。
当然,有时候待根据模式匹配去改变一些东西,让我们回到歌曲列表文件中,不管是谁,都会以小写方式输入歌曲演唱者的名字,以混合大小写方式在点唱机的屏幕上显示它们会更好看,怎么样才能把每个词的首字符改成大写呢?
String#sub和String#gsub方法超找出能够匹配第一个参数的那部分字符串,同时用第二个参数替换这它们。String#sub执行一次替换,而String#gsub在匹配每次出现都进行替换。
两个方法都返回了对含有这些替换字符串的新的拷贝。Mutator版本的String#sub!和String#gsub!会直接修改原先的字符串。
a = "the quick brown fox"
a.sub(/[aeiou]/, '*')
a.gsub(/[aeiou]/, '*')
a.sub(/\s\S+/, '')
a.gsub(/\s\S+/, '')
两个函数的第二个参数可以是String或block。如果使用block,匹配的字符串会被传递被block,同时block的结果值会被替换到原先的字符串中。
a = "the quick brown fox"
a.sub(/^./){|match| match.upcase}
a.gsub(/[aeiou]/) {|vowel| vowel.upcase}
所以,对如何转换歌曲演唱者名字的这个问题,我们给出了大难。匹配词的首字符的模式是\b\w——它寻找后面跟着词字符(word character)的词边界。把这个模式和gsub结合起来,可以改变歌曲演唱者的名字了。
def mixed_case(name)
name.gsub(/\b\w/) {|first| first.upcase}
end
mixed_case("fats waller")
mixed_case("louis armstrong")
mixed_case("strength in numbers")
替换中的反斜线序列
早些时候我们注意到序列\1和\2等在模式中可用,它们代表至今为止第n个已匹配的组,相同的序列也可以作为sub和gsub的第二个参数。
"fred:smith".sub(/(\w+):(\w+)/, '\2, \1')
"nercpyitno".gsub(/(.)(.)/, '\2\1')
其他的反斜线序列在替换字符串中起的作用:&(最后的匹配),+(最后匹配的组),'(匹配之前的字符串),'(匹配之后的字符串)和\(字面量反斜线)。
如果想在替换中包含字面量反斜线,我们会变得很困惑,显然可以写成:
str.gsub(/\\/, '\\\\')
很清楚,代码正试图用两个反斜线替换str中的每个反斜线,程序员在替换文本中用了双倍的反斜线,因为知道它们在语法分析阶段中会被转化成\。但是当替换发生时,正则表达式引擎对字符串又执行了一遍,并把\转换成\,所以最终结果是使用另外一个反斜线替换每个反斜线。需要把它写成gsub(/\/, '\\\\')!
str = 'a\b\c'
str.gsub(/\/, '\\\\')
但是,如果利用&被替换为已匹配的字符串这个事实,也可以写成:
str = 'a\b\c'
str.gsub(/\/, '&&')
如果使用gsub的block形式,用来替换的字符串会被和仅被分析一次(在语法分析阶段),这正是所希望看到的结果。
str = 'a\b\c'
str.gsub(/\/) { '\\' }
最后,下面的例子极好的表达了结合使用正则表达式和block,请参见由Wakou A编写的CGI库模块的代码片段,这段代码接受包含HTML转义序列的字符串并把它转换成普通的ASCII。
为了支持日本用户,它在正则表达式上使用了n修饰符关闭了宽字符处理,它也说明了Ruby的case表达式。
def unescapeHTML(string)
str = string.dup
str.gsub!(/&(.*?); /n) {
match = $1.dup
case match
when /\Aamp\z/ni then '&'
when /\Aquot\z/ni then '*'
when /\Agt\z/ni then '>'
when /\Alt\z/ni then '<'
when /\A#(\d+)\z\/n then Integer($1).chr
when /\A#x([0-9a-f]+)\z/ni then $1.hex.chr
end
}
str
end
puts unescapeHTML("1<2 & 4>3")
puts unescapeHTML(""A" = A = A")
输出结果:
1<2 && 4>3
"A" = A = A
dup:复制对象没有分配ID,并被视为新记录。请注意,这是一个“浅”副本,因为它仅复制对象的属性,而不是其关联。 “深”副本的范围是特定于应用程序的,因此由应用程序根据需要进行实施。 dup方法不保留时间戳(已创建|更新)_(at | on)。
5.4.3面向对象的正则表达式(Object-Oritended Regular Expressions)
必须承认,尽管所有这些古怪的变量用起来十分方便,但是它们不是面向对象的,同时它们很神秘,难道我们没有说Ruby的所有物体都是对象吗?哪里出错了?
真的没有错,在Matz设计Ruby时,他设计了一个完全面向对象的正则表达处理系统,然后在它之上包装(wrap)所有这些$变量,使它们看起来对Perl程序员很熟悉。对象和类还在这里,只不过就在表面下,让我们花点时间把它们挖掘出来吧。
实际上已经遇到一个类:正则表达式字面量创建了Regexp类的实例。
re = /cat/
re.class -> Regexp
Regexp#match方法对字符串匹配正则表达式。如果失败了,它返回nil。如果成功,则返回MatchData类的一个实例。MatchData对象让你访问关于这次匹配的所有可用信息,所有这些好东西通过$变量得到的,它们绑定在一个小巧方便的对象。
re = /(\d+):(\d+)/ # match a time hh:mm
md = re.match("Time: 12:34am")
md.class
md[0] # == $&
md[1] # == $1
md[2] # == $2
md.pre_match # == $`
md.post_match # == $'
匹配数据存储在它自己的对象里,这样可以同时保留两个或多个模式匹配的结果,这些事情使用那些$变量是做不出来的。在下面的例子中,对两个字符串匹配相同的Regexp对象,每次匹配返回唯一的MatchData对象,通过检验两个子模式字段来验证它。
re = /(\d+):(\d+)/
md1 = re.match("Time: 12:34am")
md2 = re.match("Time: 10:30pm")
md1[1, 2]
第6章
关于方法的更多细节(more about Methods)
6.1 定义一个方法
- 以小写字母开头
如果你使用大写字符,并不会立即得到一个错误,但是当Ruby看到你调用这个方法时,它首先猜测这是一个常量,而不是一个方法调用,且结果是Ruby可能错误地解析这个调用。
- 表示查询的方法名通常以?结尾,例如instance_of?
- “危险的”或者会修改接收对象(receiver)的方法,可以用!结尾,例如String提供了chop和chop!方法,第一个返回一个修改后的字符串;第二个则就地修改对象。
举个栗子:
我们已经新方法指定了一个名字,可能还需要声明某些参数(parameter,形参),它们就是括号中列出的局部变量。(方法参数两边的括号是可选的;我们的惯例是,当方法有参数时则使用括号,否则忽略它们。)
def my_new_method(arg1, arg2, arg3)
#Code for the method would go there
end
Ruby可以让你指定方法参数(argument,实参)的默认值——如果调用者在传入参数时没有明确指定所使用的的值。为此你可以使用赋值操作符。
def cool_dude(arg1="Miles", arg2="Coltrane", arg3="Roach")
"#{arg1}, #{arg2}, #{arg3}."
end
方法体内是普通的Ruby表达式,你不能在方法内定义非单件(nonsingleto)类或模块。如果你在一个方法内定义另一个方法,内部的方法只有在外部执行时才得到定义,方法的返回值是执行的最后一个表达式的值,或者return表达式显示返回的值。
6.1.1 可变长度的参数列表(Variable-Length Argument Lists)
但是如果你希望传入可变个数的参数(argument)、或者想用一个形参(parameter)接收多个参数,在“普通”的参数名前放置一个星号(*)即可。
def varargs(arg1, *rest)
"Got #{arg1} and #{rest.join(', ')}"
end
varargs("one")
varargs("one", "two")
在这个栗子中,和往常一样第一个参数赋值给方法的第一个形参,不过,第二个形参的前缀为星号,因此所有剩余的参数被装入到一个新的Array中,然后赋值给第二个形参。
6.1.2 方法和Block(methods and Blocks)
之前讲过“Block和迭代器“,调用一个方法是,可以用一个block与之相关联,通常,您可以使用yield从方法内部调用这个block。
def take_block(p1)
if block_given?
yield(p1)
else
p1
end
end
take_block("no block")
take_block("no block") {|s| s.sub(/no /, '')}
不过如果方法定义的最后一个参数前缀为&,那么所关联的block会被转换为一个Proc对象,然后赋值给这个参数。
class TaxCalculator
def initialize(name, &block)
@name, @block = name, block
end
def get_tax(amount)
"#@name on #{amount} = #{ @block.call(amount) }"
end
end
tc = TaxCalculator.new("Sales tax") { |amt| amt * 0.075 }
tc.get_tax(100)
调用方法(Calling a Method)
你可以通过指定接受者、方法的名称、可选的参数及block,来调用一个方法。
connection.download_MP3("jitterbug") {|p| show_progress(p)}
在这个栗子中,connection对象是接收者,download_MP3是方法的名称,”jitterbug“是参数,花括号中的内容是相关联的block。
对类方法或者模块来说,接收者是类或模块的名字。
File.size("testfile")
Math.sin(Math::PI/4)
如果你省略了接收者,其默认为self,也就是当前的对象。
self.class
self.frozen
Ruby正是用这种默认的机制实现私有方法调用的,我们无法调用某个接受对象的私有方法,它们只是在当前对象(self)中是可用的。
而在前面的栗子中,我们调用self.class时必须要指定一个接收对象,这是因为class在Ruby中是一个关键字(它引入类的定义),因此单独使用它会产生语法错误。
可选的参数跟随在方法名之后,如果没有二义性,在调用方法时,你可以省略参数列表两侧的括号,不过,除非最简单的情况,我们不推荐这样做——某些微妙的问题可能会让你犯错误,我们的规则很简单:只要你有任何疑虑,就使用括号。
a = obj.hash
a = obj.hash()
obj.some_method
6.2.1 方法返回值(Method Return Values)
每个被调用的方法都会返回一个值(尽管没有规定说你必须要使用这个值)。方法的值,是在方法执行中最后一个语句执行的结果,Ruby有一个return语句,可以从当前的执行的方法中退出,return的返回值是其参数的值,如果不需要return就省略之,这是Ruby的一个惯用技法。
def meth_one
"one"
end
meth_one
def meth_three
100.times do |num|
square = num*num
return num, square if square >1000
end
end
最后一种情况演示了,如果你给定return多个参数,方法会将它们以数组的形式返回。你可以使用并行赋值来收集返回值。
num, square = meth_three
在方法调用中的数组展开
我们最早看到,在方法定义中,如果你在一个正规参数前加一个星号,那么传入这个方法调用的多个参数将会被装入一个数组中,当然,也有某些操作是相反的方式。
当你调用一个方法时,你可以分解一个数组,这样每个成员都视为单独的参数。在数组参数(必须在所有普通参数的后面)前加一个星号完成这一点。
def five(a, b, c, d, e)
"I was passed #{a} #{b} #{c} #{d} #{e}"
end
five(1, 2, 3, 4, 5)
让block更加动态
我们已经看到如何为方法调用关联一个block。
list_bones("aardvark") do |bone|
...
end
一般来说,这已经足够好了——你可以将一个固定的block关联到方法,就如同在if或while语句之后编写的一大块代码。
但是,某些时候,你希望更加灵活一些,例如,我们希望教授一些算术技法,学生可能想要一个n次相加或相乘的表,如果学生需要一个2倍次的表,我们就输出2,4,6,8等。
print "(t)imes or (p)lus:"
times = gets
print "number: "
number = Integer(gets)
if times =~ /^t/
puts ((1..10).collect {|n| n*number}.join(", "))
else
puts ((1..10).collect {|n| n+number}.join(", "))
end
这可以工作,不过在每个if语句之后重复实质上等价的代码,颇为丑陋,如果我们可以将完成计算的block抽取出来,代码就漂亮富多了。
print "(t)imes or (p)lus:"
times = gets
print "number: "
number = Integer(gets)
if times =~ /^t/
puts (lambda{|n| n*number})
else
puts (lambda {|n| n+number})
end
如果方法的最后一个参数前有&符号,Ruby将认为它是一个Proc对象,它会将其从参数列表中删除,并将Proc对象转换为一个block,然后关联到该方法。
收集散列参数
某些语言支持关键字参数——也就是你可以任意顺序传入参数的名称与值,而非按指定顺序传入参数。
因为我们在本书上一版中提到Ruby1.8将会支持这一特性,同时,人们可以使用散列表来取得相同的效果,例如,可以考虑为我们的SongList加入更强大的按名搜索功能。
class SongList
def create_search(name, params)
# ...
end
end
list.create_search("short jazz songs", {
'genre' => "jazz",
'duration_less_than' => 270
})
第一个参数是搜索的名字,第二个是一个散列式,其中包括搜索的参数。使用散列表意味着我们可以模拟关键字:查询类型为”爵士乐“, 且时长小于4分半的歌曲。不过这种方式有点笨重,并且一组大括号容易误写为一个和方法关联的block。
因此,Ruby提供了一种快捷方式,只要在参数列表中,散列数组在正常的参数之后,并位于任何数组或block参数之前,你就可以使用键值对,所有的这些对会被集合到一个散列数组中,并作为方法的一个参数传入。无须使用大括号。
list.create_search('short jazz songs',
'genre'=>"jazz",
'duration'=>270
)
最后,Ruby的惯用技法是,你可以使用符号(Symbol)而非字符串作为参数,符号清楚地表达了你所引用的是某个事物的名字。
list.create_search('short jazz songs',
:genre => :jazz,
:duration = 270
)
一个潜心编写的Ruby程序通常包括许多方法,每个都很短小,因此熟悉定义和使用Ruby方法的各种选择是非常值得的。
第七章 表达式(Expression)
Ruby和其他语言的一个不同之处就是任何东西都可能返回一个值:几乎所有东西都是表达式。
明显的一个好处就是实现链式语句。
a = b = c = 0
[3, 1, 7, 0].sort.reverse
不太明显的好处是,C和Java中的普通语句在Ruby中也是表达式,例如,if和case语句都返回最后执行的表达式的值。
song_type = if song.mp3_type == MP3::Jazz
if song.written < Date.new(1935, 1, 1)
Song::TradJazz
else
Song::Jazz
end
else
Song::other
end
rating = case votes_cast
when 0...10 then Rating::SkipThisOne
when 10...50 then Rating::CouldDoBetter
else Rating::Rave
end
运算符表达式(Operator Expression)
Ruby提供了基本的运算符集(如+、-、*、/等等),也提供了几个独特的运算符。
实际上,Ruby中的许多运算符是由方法调用来实现的,例如,当你执行 ab+c时,实际上你是请求a对象执行方法,传入的参数是b。然后请求返回的结果对象执行+方法,传入的参数是c。这等价于:
(a.*(b).+c)
因为任何东西都是对象,而且你可以重新定义实例方法,所以你可以重新定义任何不满足你需求的基本算术方法。
class Fixnum
alias old_plus + # we can reference the original '+' as 'old_plus'
# Redefine addition of Fixnums. This
# is a BAD IDEA
def +(other)
old_plus(other).succ
end
end
更有用的是,你写的类可以像内建那样参与到运算符表达式中,比如,你可能香葱歌曲中间剪辑一段,这可以用索引操作来实现。
class Song
def [](from_time, to_time)
result = Song.new(self.title + " [extract]"),
self.artist
to_time - from_time
result.set_start_time(from_time)
result
end
end
这段代码扩展了类Song:提供了[]方法,该方法有两个参数(开始时间和结束时间)。它返回对应给定时间间隔的一个新的song对象。我们可以通过下面的代码来试听一首歌:
song[0, 15].play
7.2 表达式之杂项
除了支持明显的运算符表达式和方法调用,以及不太明显的语句表达(例如if和case)外,Ruby的表达式还支持更多的东西。
7.2.1命令展开(Command Expansion)
如果你用反索引号(`), 或者以%x为前缀的分界形式,括起一个字符串,默认情况下它会被当做底层操作系统的命令来执行。表达式的返回值就是该命令的标准输出,由于没有去处新行符,所以你获得的返回值结尾可能会有回车符或者换行符。
`date`
`ls`.split[34]
%x{echo "Hello there"}
你也可以在命令字符串中使用表达式展开和所有普通的转义序列。
for i in 0..3
status = 'dbmanager status id=#{1}'
# ...
end
命令的退出状态(exit status)保存在全局变量$?中。
重定义反引号
在前面的描述中,我们说反引号括起来的字符串”默认“被当做命令执行。实际上,字符串被传递给了名为Kernel.方法(单引号)。如果你愿意,可以重载它。
alias old_backquote
def `(cmd)
result = old_backquote(cmd)
if $? !=0
fail "Command #{cmd} failed: #$?"
end
result
end
print `data`
print `data`