Ruby 简明教程
Ruby - Overview
Ruby 是一种纯粹的面向对象的编程语言。它于 1993 年由日本的 Yukihiro Matsumoto 创建。
您可以在 www.ruby-lang.org 上的 Ruby 邮件列表上找到 Yukihiro Matsumoto 的名字。Matsumoto 在 Ruby 社区中也被称为 Matz。
Ruby is "A Programmer’s Best Friend".
Ruby 具有类似于 Smalltalk、Perl 和 Python 的特性。Perl、Python 和 Smalltalk 是脚本语言。Smalltalk 是一种真正的面向对象语言。Ruby 和 Smalltalk 一样,也是一种完美的面向对象语言。使用 Ruby 语法比使用 Smalltalk 语法更容易。
Features of Ruby
-
Ruby 是开源的,可以在网上免费获得,但它受许可证约束。
-
Ruby 是一种通用解释型编程语言。
-
Ruby 是一种真正的面向对象编程语言。
-
Ruby 是一种类似于 Python 和 PERL 的服务器端脚本语言。
-
Ruby 可用于编写通用网关接口 (CGI) 脚本。
-
Ruby 可嵌入到超文本标记语言 (HTML) 中。
-
Ruby 具有简洁易用的语法,允许新开发者非常快速、轻松地进行学习。
-
Ruby 具有与 C++ 和 Perl 等许多编程语言类似的语法。
-
Ruby 具有很强的可扩展性,用 Ruby 编写的庞大程序易于维护。
-
Ruby 可用于开发互联网和内部网应用。
-
Ruby 可安装在 Windows 和 POSIX 环境中。
-
Ruby 支持许多 GUI 工具,例如 Tcl/Tk、GTK 和 OpenGL。
-
Ruby 可以轻松连接到 DB2、MySQL、Oracle 和 Sybase。
-
Ruby 有一组丰富的内置函数,可以直接用于 Ruby 脚本。
Ruby - Environment Setup
Local Environment Setup
如果您仍然希望为 Ruby 编程语言设置环境,那就让我们继续吧。本教程将教你有关环境设置的所有重要主题。我们建议你首先浏览以下主题,然后再进一步学习 −
-
Ruby Installation on Linux/Unix − 如果你计划在 Linux/Unix 机器上进行开发环境,那么请阅读本教程。
-
Ruby Installation on Windows − 如果你计划在 Windows 机器上进行开发环境,那么请阅读本教程。
-
Ruby Command Line Options − 本章列出了所有命令行选项,您可以将其与 Ruby 解释器一起使用。
-
Ruby Environment Variables − 本章中列出了所有重要的环境变量,这些变量的设置将使 Ruby 解释器正常工作。
Interactive Ruby (IRb)
交互式 Ruby (IRb) 针对实验提供一个 shell。在 IRb shell 中,您可以立即逐行查看表达式结果。
此工具随 Ruby 安装一同提供,因此您无需执行额外操作即可使用 IRb。
只需在命令提示符下键入 irb ,就会启动一个交互式 Ruby 会话,如下所示:
$irb
irb 0.6.1(99/09/16)
irb(main):001:0> def hello
irb(main):002:1> out = "Hello World"
irb(main):003:1> puts out
irb(main):004:1> end
nil
irb(main):005:0> hello
Hello World
nil
irb(main):006:0>
不必担心我们在此处所做的事情。您将在后续章节中了解所有这些步骤。
Ruby - Syntax
让我们用 Ruby 编写一个简单的程序。所有 Ruby 文件都将具有扩展名 .rb 。因此,将以下源代码放入 test.rb 文件中。
#!/usr/bin/ruby -w
puts "Hello, Ruby!";
在这里,我们假设你的计算机在 /usr/bin 目录中有 Ruby 解释器。现在,尝试按如下所示运行此程序——
$ ruby test.rb
这会产生以下结果 −
Hello, Ruby!
你已经看到了一个简单的 Ruby 程序,现在让我们了解一些与 Ruby 语法相关的基本概念。
Whitespace in Ruby Program
在 Ruby 代码中通常忽略空格字符(如空格和制表符),但当它们出现在字符串中时除外。但是,有时它们用于解释歧义的声明。在启用 -w 选项时,这种解释会产生警告。
Ruby Identifiers
标识符是变量、常量和方法的名称。Ruby 标识符区分大小写。这意味着 Ram 和 RAM 是 Ruby 中的两个不同的标识符。
Ruby 标识符名称可能由字母数字字符和下划线字符(_)组成。
Reserved Words
以下列表显示了 Ruby 中的保留字。这些保留字不能用作常量或变量名。不过,可将它们用作方法名。
BEGIN |
do |
next |
then |
END |
else |
nil |
true |
alias |
elsif |
not |
undef |
and |
end |
or |
unless |
begin |
ensure |
redo |
until |
break |
false |
rescue |
when |
case |
for |
retry |
while |
class |
if |
return |
while |
def |
in |
self |
FILE |
defined? |
module |
super |
LINE |
Here Document in Ruby
“Here Document” 引用了如何从多行创建字符串。在 << 后,你可以指定一个字符串或一个标识符来终止该字符串字面值,并且当前行后所有行直到终止符的行都是该字符串的值。
如果终止符带有引号,则引号的类型决定了面向行的字符串字面值类型。注意,<< 和终止符之间不能有空格。
这里有一些不同的示例 −
#!/usr/bin/ruby -w
print <<EOF
This is the first way of creating
here document ie. multiple line string.
EOF
print <<"EOF"; # same as above
This is the second way of creating
here document ie. multiple line string.
EOF
print <<`EOC` # execute commands
echo hi there
echo lo there
EOC
print <<"foo", <<"bar" # you can stack them
I said foo.
foo
I said bar.
bar
这会产生以下结果 −
This is the first way of creating
her document ie. multiple line string.
This is the second way of creating
her document ie. multiple line string.
hi there
lo there
I said foo.
I said bar.
Ruby Comments
注释将一行、一行的一部分或多行从 Ruby 解释器中隐藏。你可以在一行的开头使用井号字符 (#) −
# I am a comment. Just ignore me.
或者,注释可以在语句或表达式之后在同一行上 −
name = "Madisetti" # This is again comment
您可以按如下方式注释多行 −
# This is a comment.
# This is a comment, too.
# This is a comment, too.
# I said that already.
这是另一种形式。此块注释使用 =begin/=end 将多行从解释器中隐藏 −
=begin
This is a comment.
This is a comment, too.
This is a comment, too.
I said that already.
=end
Ruby - Classes and Objects
Ruby 是一个完美的面向对象编程语言。面向对象编程语言具有以下特点 −
-
Data Encapsulation
-
Data Abstraction
-
Polymorphism
-
Inheritance
这些特点已在章节 Object Oriented Ruby 中进行讨论。
面向对象程序涉及类和对象。类是单个对象根据其创建的蓝图。在面向对象术语中,我们说你的自行车是自行车类对象的一个实例。
以任何车辆为例。它包含轮子、马力和燃油或油箱容量。这些特性形成了类 Vehicle 的数据成员。你可以借助这些特性来区分一辆车与另一辆车。
车辆还具有一些功能,例如停车、行驶和加速。即使这些功能也形成了类 Vehicle 的数据成员。因此,你可以将类定义为特性和功能的组合。
类 Vehicle 可以定义为 −
Class Vehicle {
Number no_of_wheels
Number horsepower
Characters type_of_tank
Number Capacity
Function speeding {
}
Function driving {
}
Function halting {
}
}
通过为这些数据成员指定不同的值,可以形成车辆类的多个实例。例如,飞机有三个轮子、1000 马力、燃油箱类型和 100 升容积。同样的,汽车有四个轮子、200 马力、汽油箱类型和 25 升容积。
Defining a Class in Ruby
要使用 Ruby 实现面向对象编程,首先需要学习如何在 Ruby 中创建对象和类。
Ruby 中的类总是以关键字 class 开始,后跟类的名称。名称始终应以大写字母开头。Customer 类可以显示为 −
class Customer
end
使用关键字 end 终止一个类。类中的所有数据成员都位于类定义和 end 关键字之间。
Variables in a Ruby Class
Ruby 提供四种类型的变量 −
-
Local Variables − 局部变量是在方法中定义的变量。局部变量在方法外部不可用。你将在后面的章节中看到有关方法的更多详细信息。局部变量以小写字母或 _ 开头。
-
Instance Variables − 实例变量可用于任何特定实例或对象的各种方法。这意味着实例变量因对象而异。实例变量之前加上 @ 符号,后跟变量名称。
-
Class Variables − 类变量可用于不同的对象。类变量属于类,是类的特征。它们之前加上 @@ 符号,后跟变量名称。
-
Global Variables − 类变量不能跨类使用。如果你想有一个单一变量,它可以跨类使用,你需要定义一个全局变量。全局变量始终以美元符号 ($) 开头。
Creating Objects in Ruby using new Method
对象是类的实例。你现在将学习如何在 Ruby 中创建类的对象。可以使用类的 new 方法在 Ruby 中创建对象。
new 方法是一种独特的类型方法,它在 Ruby 库中预定义。new 方法属于类方法。
以下是创建 Customer 类对象的两个对象 cust1 和 cust2 的示例 −
cust1 = Customer. new
cust2 = Customer. new
此处,cust1 和 cust2 是两个对象的名字。在等于号 (=) 之后编写对象名称,然后是类名称。然后,跟随点运算符和 new 关键字。
Custom Method to Create Ruby Objects
可以将参数传递给 new 方法,这些参数可用于初始化类变量。
当计划用参数声明 new 方法时,需要在创建类时声明 initialize 方法。
initialize 方法是一种特殊类型的方法,当使用参数调用类的 new 方法时会执行该方法。
以下是创建初始化方法的示例 −
class Customer
@@no_of_customers = 0
def initialize(id, name, addr)
@cust_id = id
@cust_name = name
@cust_addr = addr
end
end
在此示例中,使用 id, name 和 addr 作为局部变量声明 initialize 方法。此处,def 和 end 用于定义 Ruby 方法 initialize。你将在后面的章节中了解有关方法的更多信息。
在 initialize 方法中,您将这些局部变量传递到实例变量 @cust_id、@cust_name、@cust_addr 中。在此,局部变量包含传递到 new 方法的值。
现在,您可以按照如下方式来创建对象。
cust1 = Customer.new("1", "John", "Wisdom Apartments, Ludhiya")
cust2 = Customer.new("2", "Poul", "New Empire road, Khandala")
Member Functions in Ruby Class
在 Ruby 中,函数称为方法。类中的每个方法以关键字 def 开始,后跟方法名称。
方法名称始终首选 lowercase letters 。您使用关键字 end 在 Ruby 中结束一个方法。
这里是一个定义 Ruby 方法的示例:
class Sample
def function
statement 1
statement 2
end
end
在此,语句 1 和语句 2 是类 Sample 中方法函数主体的一部分。这些语句可以是任何有效的 Ruby 语句。例如,我们可以使用 puts 方法像下面这样打印 Hello Ruby:
class Sample
def hello
puts "Hello Ruby!"
end
end
现在,在以下示例中,创建一个 Sample 类的对象,并调用 hello 方法,然后查看结果:
#!/usr/bin/ruby
class Sample
def hello
puts "Hello Ruby!"
end
end
# Now using above class to create objects
object = Sample. new
object.hello
这会产生以下结果 −
Hello Ruby!
Ruby - Variables, Constants and Literals
变量是程序用来保存数据的内存位置。
Ruby 支持五种类型的变量。在上一章你也已经简要地了解了这些变量。这五个类型的变量将在本节进行讲解。
Ruby Global Variables
全局变量以 $ 开头。未初始化的全局变量具有 nil 值,并且使用 -w 选项会产生警告。
对全局变量进行赋值会改变全局状态。不建议使用全局变量,它们会让程序晦涩难懂。
这里有一个展示全局变量使用方式的示例。
#!/usr/bin/ruby
$global_variable = 10
class Class1
def print_global
puts "Global variable in Class1 is #$global_variable"
end
end
class Class2
def print_global
puts "Global variable in Class2 is #$global_variable"
end
end
class1obj = Class1.new
class1obj.print_global
class2obj = Class2.new
class2obj.print_global
这里 $global_variable 是一个全局变量。这会产生以下结果:
NOTE - 在 Ruby 中,你可以在变量或常量的前面加上井号字符(#)来访问该变量或常量的值。
Global variable in Class1 is 10
Global variable in Class2 is 10
Ruby Instance Variables
实例变量以 @ 开头。未初始化的实例变量具有 nil 值,并且使用 -w 选项会产生警告。
这里有一个展示实例变量使用方式的示例。
#!/usr/bin/ruby
class Customer
def initialize(id, name, addr)
@cust_id = id
@cust_name = name
@cust_addr = addr
end
def display_details()
puts "Customer id #@cust_id"
puts "Customer name #@cust_name"
puts "Customer address #@cust_addr"
end
end
# Create Objects
cust1 = Customer.new("1", "John", "Wisdom Apartments, Ludhiya")
cust2 = Customer.new("2", "Poul", "New Empire road, Khandala")
# Call Methods
cust1.display_details()
cust2.display_details()
这里,@cust_id、@cust_name 和 @cust_addr 是实例变量。这会产生以下结果:
Customer id 1
Customer name John
Customer address Wisdom Apartments, Ludhiya
Customer id 2
Customer name Poul
Customer address New Empire road, Khandala
Ruby Class Variables
类变量以 @@ 开头,并且必须在可以在方法定义中使用它们之前对其进行初始化。
引用未初始化的类变量会产生错误。类变量在定义了类变量的类或模块的后代中共享。
覆盖类变量会使用 -w 选项产生警告。
这里有一个展示类变量使用方式的示例:
#!/usr/bin/ruby
class Customer
@@no_of_customers = 0
def initialize(id, name, addr)
@cust_id = id
@cust_name = name
@cust_addr = addr
end
def display_details()
puts "Customer id #@cust_id"
puts "Customer name #@cust_name"
puts "Customer address #@cust_addr"
end
def total_no_of_customers()
@@no_of_customers += 1
puts "Total number of customers: #@@no_of_customers"
end
end
# Create Objects
cust1 = Customer.new("1", "John", "Wisdom Apartments, Ludhiya")
cust2 = Customer.new("2", "Poul", "New Empire road, Khandala")
# Call Methods
cust1.total_no_of_customers()
cust2.total_no_of_customers()
此处 @@no_of_customers 是一个类变量。该类变量将生成以下结果:
Total number of customers: 1
Total number of customers: 2
Ruby Local Variables
局部变量以小写字母或 _ 开头。局部变量作用域范围从类、模块、def 或 do 到相应的 end,或从代码块的花括号开头到结尾 {}。
引用未初始化的局部变量时,将将其解释为一种对此方法的调用,此方法无参数。
对未初始化局部变量的赋值还可用作变量声明。变量在当前作用域结束之前一直存在。在 Ruby 解析程序时确定局部变量的生命周期。
在以上示例中,局部变量为 id、name 和 addr。
Ruby Constants
常量以大写字母开头。可以在定义它们所在类的模块内访问类或模块中定义的常量,也可以全局访问定义在类或模块外部的常量。
不可在方法内定义常量。引用未初始化的常量将导致错误。对已初始化的常量进行赋值将产生警告。
#!/usr/bin/ruby
class Example
VAR1 = 100
VAR2 = 200
def show
puts "Value of first Constant is #{VAR1}"
puts "Value of second Constant is #{VAR2}"
end
end
# Create Objects
object = Example.new()
object.show
此处 VAR1 和 VAR2 为常量。该类常量将生成以下结果:
Value of first Constant is 100
Value of second Constant is 200
Ruby Pseudo-Variables
它们是既表现为局部变量、又表现为常量形式的特殊变量。你无法为这些变量赋予任何值。
-
self − 当前方法的接收者对象。
-
true − 表示 true 的值。
-
false − 表示 false 的值。
-
nil − 表示未定义的值。
-
FILE − 当前源代码文件的名称。
-
LINE − 当前源代码文件的行号。
Integer Numbers
Ruby 支持整数。整数的范围可以从 -230 到 230-1 或从 -262 到 262-1。在此范围内的整数是 Fixnum 类的对象,而超出此范围的整数则存储在 Bignum 类的对象中。
你可以使用可选的前置符号、可选的进制指示符(8 进制为 0,16 进制为 0x,2 进制为 0b),后跟用相应进制表示的一串数字来编写整数。连字符字符在数字字符串中将被忽略。
你还可以获取与 ASCII 字符对应的整数值,或通过使其前置一个问号来转义序列。
String Literals
Ruby 字符串只是 8 位字节的序列,它们是 String 类的对象。双引号字符串允许替换和反斜杠表示法,但单引号字符串不允许替换,只允许对 \\ 和 \' 使用反斜杠表示法
Backslash Notations
以下列出了 Ruby 支持的反斜杠表示法 −
Notation |
Character represented |
\n |
Newline (0x0a) |
\r |
Carriage return (0x0d) |
\f |
Formfeed (0x0c) |
\b |
Backspace (0x08) |
\a |
Bell (0x07) |
\e |
Escape (0x1b) |
\s |
Space (0x20) |
\nnn |
八进制表示法(n 为 0-7) |
\xnn |
十六进制表示法(n 为 0-9、a-f 或 A-F) |
\cx, \C-x |
Control-x |
\M-x |
Meta-x (c |
0x80) |
\M-\C-x |
Meta-Control-x |
\x |
有关 Ruby 字符串的更多详细信息,请参阅 Ruby Strings 。
Ruby Arrays
Ruby 数组的字面意思是由在方括号之间放置逗号分隔的对象引用序列创建的。忽略结尾逗号。
Example
#!/usr/bin/ruby
ary = [ "fred", 10, 3.14, "This is a string", "last element", ]
ary.each do |i|
puts i
end
这会产生以下结果 −
fred
10
3.14
This is a string
last element
有关 Ruby 数组的更多详细信息,请参阅 Ruby Arrays 。
Ruby Hashes
字面 Ruby 哈希是通过将键值对列表放置在大括号内创建的,键和值之间用逗号或序列 ⇒ 隔开。忽略结尾逗号。
Example
#!/usr/bin/ruby
hsh = colors = { "red" => 0xf00, "green" => 0x0f0, "blue" => 0x00f }
hsh.each do |key, value|
print key, " is ", value, "\n"
end
这会产生以下结果 −
red is 3840
green is 240
blue is 15
有关 Ruby 哈希的更多详细信息,请参阅 Ruby Hashes 。
Ruby Ranges
范围表示一个区间,这是一个具有一定值和结尾的间隔。可以使用 s..e 和 s…e 字面值或使用 Range.new 构建范围。
使用 .. 构建的范围从头到尾(含)。使用 … 创建的那些范围不包括结尾值。作为一个迭代器时使用范围,范围返回序列中的每个值。
范围 (1..5) 表示它包括 1、2、3、4、5 值,范围 (1…5) 表示它包括 1、2、3、4 值。
Example
#!/usr/bin/ruby
(10..15).each do |n|
print n, ' '
end
这会产生以下结果 −
10 11 12 13 14 15
有关 Ruby 范围的更多详细信息,请参阅 Ruby Ranges 。
Ruby - Operators
Ruby 支持丰富的运算符集,正如您对现代语言所期望的那样。大多数运算符实际上都是方法调用。例如,a + b 被解释为 a.+(b),其中变量 a 引用的对象中的 + 方法被调用,并且 b 作为其参数。
对于每个运算符(+ - * / % ** & | ^ << >> && ||),都有一个相应的缩写赋值运算符形式(+= -= 等)。
Ruby Arithmetic Operators
假设变量 a 等于 10,变量 b 等于 20,则 −
Operator |
Description |
Example |
+ |
加法 - 加上操作符两侧的值。 |
a + b 将得到 30 |
− |
减法 - 从左运算数中减去右运算数。 |
a - b 将得到 -10 |
* |
乘法 - 乘以操作符两侧的值。 |
a * b 将得到 200 |
/ |
除法 - 左运算数除以右运算数。 |
b / a 将得到 2 |
% |
模数 - 将左运算数组以右运算数组除,并返回余数。 |
b % a 将得到 0 |
** |
指数 - 对运算符执行指数(幂)计算。 |
a**b 将给出 20 次方的 10 |
Ruby Comparison Operators
假设变量 a 等于 10,变量 b 等于 20,则 −
Operator |
Description |
Example |
== |
检查两个操作数的值是否相等,如果相等,则条件变为真。 |
(a == b)为 false。 |
!= |
检查两个操作数的值是否相等,如果不相等,则条件变为真。 |
(a != b) 为 true。 |
> |
检查左操作数的值是否大于右操作数的值,如果大于,则条件变为真。 |
(a > b) 为 false。 |
< |
检查左操作数的值是否小于右操作数的值,如果小于,则条件变为真。 |
(a < b) 为 true。 |
>= |
检查左操作数的值是否大于或等于右操作数的值,如果大于或等于,则条件变为真。 |
(a >= b) 为 false。 |
⇐ |
检查左操作数的值是否小于或等于右操作数的值,如果小于或等于,则条件变为真。 |
(a ⇐ b) 为 true。 |
<⇒ |
组合比较运算符。如果第一个运算数等于第二个运算数,则返回 0,如果第一个运算数大于第二个运算数,则返回 1,如果第一个运算数小于第二个运算数,则返回 -1。 |
(a <⇒ b) 返回 -1。 |
=== |
用于在 case 语句的 when 子句中测试相等性。 |
(1…10) === 5 返回 true。 |
.eql? |
如果接收方和参数都具有相同的类型和相等的值,则为真。 |
1 == 1.0 返回 true,但 1.eql?(1.0) 为 false。 |
equal? |
如果接收方和参数具有相同的对象 ID,则为真。 |
如果 aObj 是 bObj 的副本,则 aObj == bObj 为 true,a.equal?bObj 为 false,但 a.equal?aObj 为 true。 |
Ruby Assignment Operators
假设变量 a 等于 10,变量 b 等于 20,则 −
Operator |
Description |
Example |
= |
简单赋值运算符,将右侧运算数的值分配给左侧运算数。 |
c = a + b 将 a + b 的值分配给 c |
+= |
添加 AND 赋值运算符,将右运算符添加到左运算符并将其结果赋值给左运算符。 |
c += a 等同于 c = c + a |
-= |
减 AND 赋值运算符,从左运算符中减去右运算符并将其结果赋值给左运算符。 |
c -= a 等同于 c = c - a |
*= |
乘 AND 赋值运算符,用右运算符乘以左运算符并将其结果赋值给左运算符。 |
c *= a 等同于 c = c * a |
/= |
除 AND 赋值运算符,用右运算符除以左运算符并将其结果赋值给左运算符。 |
c /= a 等同于 c = c / a |
%= |
模 AND 赋值运算符,使用两个运算符取模并将其结果赋值给左运算符。 |
c %= a 等同于 c = c % a |
**= |
指数 AND 赋值运算符,对运算符执行指数(幂)计算并将值赋值给左运算符。 |
c *= a 等同于 c = c * a |
Ruby Parallel Assignment
Ruby 也支持变量的并行赋值。这允许使用单行 Ruby 代码初始化多个变量。例如 −
a = 10
b = 20
c = 30
使用并行赋值可以更快地声明 −
a, b, c = 10, 20, 30
并行赋值还可用于交换两个变量中的值 −
a, b = b, c
Ruby Bitwise Operators
按位运算符处理位并执行逐位运算。
假设 a = 60;和 b = 13;现以二进制格式表示如下 −
a = 0011 1100
b = 0000 1101
------------------
a&b = 0000 1100
a|b = 0011 1101
a^b = 0011 0001
~a = 1100 0011
Ruby 语言支持以下按位运算符。
Operator |
Description |
Example |
& |
二进制与运算符将位复制到结果中(如果它存在于两个操作数中)。 |
(a & b) 将得到 12,即 0000 1100 |
二进制 OR 运算符会在任一操作数中存在的情况下复制比特。 |
||
(a |
b) 将给出 61,即 0011 1101 |
^ |
二进制 XOR 运算符会在其中一个操作数中但不在两个操作数中都设置的情况下复制比特。 |
(a ^ b) 将给出 49,即 0011 0001 |
~ |
二进制逻辑补运算符是单操作数运算符,它会“翻转”比特。 |
(~a) 将给出 -61,即 1100 0011,这是由于带有符号的二进制数字的 2 的补码形式。 |
<< |
二进制左移运算符。左操作数的值会向左移动由右操作数指定的比特数。 |
a << 2 将给出 240,即 1111 0000 |
>> |
Ruby Logical Operators
Ruby 语言支持以下逻辑运算符
假设变量 a 等于 10,变量 b 等于 20,则 −
Operator |
Description |
Example |
and |
称为逻辑与运算符。如果两个操作数都为真,则条件变为真。 |
(a and b) 为真。 |
or |
称为逻辑或运算符。如果两个操作数中的任何一个非零,则条件变为真。 |
(a or b) 为真。 |
&& |
称为逻辑与运算符。如果两个操作数都非零,则条件变为真。 |
(a && b) 为真。 |
称为逻辑或运算符。如果两个操作数中的任何一个非零,则条件变为真。 |
(a |
|
b) is true. |
! |
称为逻辑 NOT 运算符。用于反转运算数的逻辑状态。如果条件为真,逻辑 NOT 运算符将变为假。 |
!(a && b) 为假。 |
not |
称为逻辑 NOT 运算符。用于反转运算数的逻辑状态。如果条件为真,逻辑 NOT 运算符将变为假。 |
Ruby Ternary Operator
还有一个称为三元运算符的运算符。它首先对一个表达式求值以获得真或假的值,然后根据求值结果执行两个给定语句中的一个。条件运算符具有以下语法:
Operator |
Description |
Example |
? : |
Conditional Expression |
如果条件为真?然后值为 X:否则值为 Y |
Ruby Range Operators
Ruby 中的序列范围用于创建一系列连续值,包括一个起始值、一个结束值和介于两者之间的值范围。
在 Ruby 中,这些序列使用“..”和“···”范围运算符创建。两点形式创建一个包含范围,而三点形式创建一个排除指定高值的范围。
Operator |
Description |
Example |
.. |
从起点到终点(包括终点)创建一个范围。 |
1..10 从 1 到 10(包括 10)创建一个范围。 |
… |
从起点到终点(不包括终点)创建一个范围。 |
1…10 从 1 到 9 创建一个范围。 |
Ruby defined? Operators
defined? 是一个特殊运算符,它采用方法调用的形式来确定传入的表达式是否已定义。它返回表达式的描述字符串,如果表达式未定义,则返回 nil。
defined? 运算符有以下各种使用方法
Usage 1
defined? variable # True if variable is initialized
For Example
foo = 42
defined? foo # => "local-variable"
defined? $_ # => "global-variable"
defined? bar # => nil (undefined)
Usage 2
defined? method_call # True if a method is defined
For Example
defined? puts # => "method"
defined? puts(bar) # => nil (bar is not defined here)
defined? unpack # => nil (not defined here)
Ruby Dot "." and Double Colon "::" Operators
在模块方法名称前加上模块名称和一个句点来调用它,并且使用模块名称和两个冒号来引用常量。
:: 是一个一元运算符,它允许:类或模块内定义的常量、实例方法和类方法从类或模块外部的任何地方访问。
在 Ruby 中,类和方法也可以视为常量。
你只需要为 :: Const_name 添加前缀,返回适当的类或模块对象。
如果没有使用前缀表达式,则默认使用主 Object 类。
下面有两个示例 −
MR_COUNT = 0 # constant defined on main Object class
module Foo
MR_COUNT = 0
::MR_COUNT = 1 # set global count to 1
MR_COUNT = 2 # set local count to 2
end
puts MR_COUNT # this is the global constant
puts Foo::MR_COUNT # this is the local "Foo" constant
Second Example
CONST = ' out there'
class Inside_one
CONST = proc {' in there'}
def where_is_my_CONST
::CONST + ' inside one'
end
end
class Inside_two
CONST = ' inside two'
def where_is_my_CONST
CONST
end
end
puts Inside_one.new.where_is_my_CONST
puts Inside_two.new.where_is_my_CONST
puts Object::CONST + Inside_two::CONST
puts Inside_two::CONST + CONST
puts Inside_one::CONST
puts Inside_one::CONST.call + Inside_two::CONST
Ruby Operators Precedence
下表列出了从高到低优先级的运算符。
Method |
Operator |
Description |
Yes |
:: |
Constant resolution operator |
Yes |
[ ] [ ]= |
Element reference, element set |
Yes |
** |
指数(乘方) |
Yes |
! ~ + - |
非、补码、一元加和一元减(最后两个的方法名为 +@ 和 -@) |
Yes |
* / % |
Multiply, divide, and modulo |
Yes |
+ - |
Addition and subtraction |
Yes |
>> << |
右位移和左位移 |
Yes |
& |
Bitwise 'AND' |
Yes |
^ |
|
异或和或 |
Yes |
⇐ < > >= |
Comparison operators |
Yes |
⇒ == === != =~ !~ |
相等和模式匹配运算符(!= 和 !~ 可能未定义为方法) |
&& |
|
Logical 'AND' |
||
Logical 'OR' |
||
.. … |
Range (inclusive and exclusive) |
|
? : |
Ternary if-then-else |
|
= %= { /= -= += |
= &= >>= <⇐ *= &&= |
|
= **= |
Assignment |
|
defined? |
检查指定的符号是否已定义 |
|
not |
Logical negation |
|
or and |
Logical composition |
NOTE − 方法列包含“是”的运算符实际上是方法,因此可以被覆盖。
Ruby - Comments
注释是 Ruby 代码中在运行时被忽略的注释行。单行注释以 # 字符开头,并且从 # 扩展到行的末尾,如下所示 −
#!/usr/bin/ruby -w
# This is a single line comment.
puts "Hello, Ruby!"
执行后,以上程序产生以下结果 −
Hello, Ruby!
Ruby Multiline Comments
你可以使用 =begin 和 =end 语法对多行进行注释,如下所示 −
#!/usr/bin/ruby -w
puts "Hello, Ruby!"
=begin
This is a multiline comment and con spwan as many lines as you
like. But =begin and =end should come in the first line only.
=end
执行后,以上程序产生以下结果 −
Hello, Ruby!
确保结尾注释足够远离代码,并且它们容易区分。如果一个块中存在多个结尾注释,请对齐它们。例如 −
@counter # keeps track times page has been hit
@siteCounter # keeps track of times all pages have been hit
Ruby - if…else, case, unless
Ruby 提供条件结构,这是现代语言中非常普遍的。在此,我们将解释 Ruby 中提供的所有条件语句和修饰符。
Ruby if…else Statement
Ruby case Statement
Syntax
case expression
[when expression [, expression ...] [then]
code ]...
[else
code ]
end
比较用例指定的表达式和使用 === 运算符指定的表达式,并执行与之匹配的 when 子句的代码。
用例指定的表达式作为左操作数进行评估。如果没有与之匹配的 when 子句,case 将执行 else 子句的代码。
when 语句的表达式由保留字 then、换行符或分号与代码分隔。因此:
case expr0
when expr1, expr2
stmt1
when expr3, expr4
stmt2
else
stmt3
end
基本上类似于以下内容:
_tmp = expr0
if expr1 === _tmp || expr2 === _tmp
stmt1
elsif expr3 === _tmp || expr4 === _tmp
stmt2
else
stmt3
end
Ruby - Loops
Ruby 中的循环用于按指定次数执行相同的代码块。本教程详细说明 Ruby 支持的所有循环语句。
Ruby while modifier
Ruby until modifier
Ruby for Statement
Example
#!/usr/bin/ruby
for i in 0..5
puts "Value of local variable is #{i}"
end
在这里,我们定义了范围 0..5。语句 for i in 0..5 将允许 i 取 0 到 5(包括 5)范围内的值。这将产生以下结果 −
Value of local variable is 0
Value of local variable is 1
Value of local variable is 2
Value of local variable is 3
Value of local variable is 4
Value of local variable is 5
for…in 循环与以下内容几乎完全等效 −
(expression).each do |variable[, variable...]| code end
但 for 循环不会为局部变量创建新作用域。for 循环的表达式由保留字 do、换行符或分号与代码分开。
Ruby retry Statement
Syntax
retry
如果重试出现在 begin 表达式的救援子句中,则从 begin 体的开头重新开始。
begin
do_something # exception raised
rescue
# handles error
retry # restart from beginning
end
如果重试出现在迭代器、块或 for 表达式的体中,则会重新开始迭代器调用的调用。对迭代器的参数会重新求值。
for i in 1..5
retry if some_condition # restart from i == 1
end
Example
#!/usr/bin/ruby
for i in 0..5
retry if i > 2
puts "Value of local variable is #{i}"
end
这将产生以下结果,并进入无限循环 −
Value of local variable is 1
Value of local variable is 2
Value of local variable is 1
Value of local variable is 2
Value of local variable is 1
Value of local variable is 2
............................
Ruby - Methods
Ruby 方法与任何其他编程语言中的函数非常类似。Ruby 方法用于将一个或多个可重复的语句捆绑到一个单元中。
方法名应以小写字母开头。如果您以大写字母开始方法名,Ruby 可能认为它是一个常量,因而可能会错误地解析该调用。
方法应该在调用之前定义,否则 Ruby 将引发一个有关未定义的方法调用的异常。
Syntax
def method_name [( [arg [= default]]...[, * arg [, &expr ]])]
expr..
end
因此,您可以按照如下方式定义一个简单的方法−
def method_name
expr..
end
您可以按如下方式表示一个接受参数的方法−
def method_name (var1, var2)
expr..
end
您可以为参数设置默认值,如果在调用方法时没有传递所需的参数,则将使用该默认值−
def method_name (var1 = value1, var2 = value2)
expr..
end
无论何时调用简单方法,都只需按照如下方式编写方法名−
method_name
然而,在使用参数调用方法时,请按照如下方式编写方法名和参数−
method_name 25, 30
使用带有参数的方法最重要的问题在于你无论何时调用这样的方法时,都需要记住参数的数目。例如,如果一个方法接收三个参数,而你只传递了两个,那么 Ruby 会显示一条错误信息。
Example
#!/usr/bin/ruby
def test(a1 = "Ruby", a2 = "Perl")
puts "The programming language is #{a1}"
puts "The programming language is #{a2}"
end
test "C", "C++"
test
这会产生以下结果 −
The programming language is C
The programming language is C++
The programming language is Ruby
The programming language is Perl
Return Values from Methods
Ruby 中的每一个方法默认返回一个值。这个返回值将是最后一条语句的值。例如 −
def test
i = 100
j = 10
k = 0
end
这个方法在被调用时,将返回最后声明的变量 k。
Variable Number of Parameters
假设你声明了一个需要两个参数的方法,无论何时你调用这个方法时,都需要传递两个参数。
然而,Ruby 允许你声明可处理不定数量的参数的方法。让我们检查一个关于该内容的示例 −
#!/usr/bin/ruby
def sample (*test)
puts "The number of parameters is #{test.length}"
for i in 0...test.length
puts "The parameters are #{test[i]}"
end
end
sample "Zara", "6", "F"
sample "Mac", "36", "M", "MCA"
在这个代码中,你已经声明了一个方法 sample,它接收一个参数 test。然而,这个参数是一个可变参数。这意味着这个参数可以带入任意数量的变量。因此,上述代码将产生以下结果 −
The number of parameters is 3
The parameters are Zara
The parameters are 6
The parameters are F
The number of parameters is 4
The parameters are Mac
The parameters are 36
The parameters are M
The parameters are MCA
Class Methods
当一个方法在类定义之外被定义时,这个方法默认被标记为 private。另一方面,在类定义中定义的方法默认被标记为 public。可以通过 Module 的 public 或 private 来更改默认可见性和方法的 private 标记。
无论何时你想访问类的某个方法时,首先需要实例化这个类。然后,你可以使用该对象来访问类的任何成员。
Ruby 为你提供了一种无需实例化类即可访问方法的方式。让我们看看一个类方法是如何声明和访问的 −
class Accounts
def reading_charge
end
def Accounts.return_date
end
end
看看 return_date 方法是如何声明的,它在类名后接一个点,后面再接方法名。你可以直接按以下方式访问这个类方法 −
Accounts.return_date
要访问这个方法,不需要创建 Accounts 类的对象。
Ruby - Blocks
您已经了解 Ruby 如何定义方法,在该方法中您可以放置多条语句,然后调用该方法。与此类似,Ruby 有一个块的概念。
-
块由代码块组成。
-
您可以为块指定一个名称。
-
块中的代码始终用大括号({})括起来。
-
块总是从与块名称相同的函数调用。这意味着,如果您有一个名为 test 的块,则使用 test 函数调用此块。
-
使用 yield 语句调用块。
Syntax
block_name {
statement1
statement2
..........
}
在此,您将学习使用简单的 yield 语句调用块。您还将学习如何使用带有参数的 yield 语句调用块。您将使用两种类型的 yield 语句检查示例代码。
The yield Statement
我们来看一个 yield 语句的示例 −
#!/usr/bin/ruby
def test
puts "You are in the method"
yield
puts "You are again back to the method"
yield
end
test {puts "You are in the block"}
这会产生以下结果 −
You are in the method
You are in the block
You are again back to the method
You are in the block
您还可以使用 yield 语句传递参数。这里有一个示例 −
#!/usr/bin/ruby
def test
yield 5
puts "You are in the method test"
yield 100
end
test {|i| puts "You are in the block #{i}"}
这会产生以下结果 −
You are in the block 5
You are in the method test
You are in the block 100
在此,yield 语句后面写入了参数。您甚至可以传递多个参数。在块中,您将变量放在两个竖线(||)之间以接受参数。因此,在前面的代码中,yield 5 语句将值 5 作为参数传递给 test 块。
现在,看以下语句 −
test {|i| puts "You are in the block #{i}"}
在此,值 5 在变量 i 中接收。现在,观察以下 puts 语句 −
puts "You are in the block #{i}"
此 puts 语句的输出为 −
You are in the block 5
如果您想要传递多个参数,则 yield 语句变为 −
yield a, b
并且该块为 −
test {|a, b| statement}
参数将用逗号分隔。
Blocks and Methods
您已经了解块和方法如何相互关联。您通常使用 yield 语句从与块名称相同的调用块。 因此,您编写 −
#!/usr/bin/ruby
def test
yield
end
test{ puts "Hello world"}
这个示例是实现块的最简单方法。您使用 yield 语句来调用测试块。
但如果一个方法的最后一个参数之前带有 &,则可以将一个块传递给此方法,并且该块将被分配给最后一个参数。如果 * 和 & 都存在于参数列表中,则 & 应出现在后面。
#!/usr/bin/ruby
def test(&block)
block.call
end
test { puts "Hello World!"}
这会产生以下结果 −
Hello World!
BEGIN and END Blocks
每个 Ruby 源文件都可以声明代码块以在文件加载时运行(BEGIN 块)并在程序执行结束后运行(END 块)。
#!/usr/bin/ruby
BEGIN {
# BEGIN block code
puts "BEGIN code block"
}
END {
# END block code
puts "END code block"
}
# MAIN block code
puts "MAIN code block"
一个程序可能包含多个 BEGIN 和 END 块。BEGIN 块按照遇到的顺序执行。END 块按照反向顺序执行。执行时,上述示例产生以下结果:
BEGIN code block
MAIN code block
END code block
Ruby - Modules and Mixins
模块是将方法、类和常量分组在一起的一种方式。模块给您带来了两大好处:
-
模块提供了一个名称空间并防止名称冲突。
-
模块实现 Mixin 机制。
模块定义了一个名称空间,一个沙盒,您可以在其中使用您的方法和常量,而不用担心被其他方法和常量踩到。
Syntax
module Identifier
statement1
statement2
...........
end
模块常量的命名方式与类常量相同,以大写字母开头。方法定义看起来也很相似:模块方法的定义方式与类方法相同。
与类方法一样,您可以通过在方法名称前面加上模块名称和一个句点来调用模块方法,并且可以使用模块名称和两个冒号来引用常量。
Example
#!/usr/bin/ruby
# Module defined in trig.rb file
module Trig
PI = 3.141592654
def Trig.sin(x)
# ..
end
def Trig.cos(x)
# ..
end
end
我们可以使用相同的函数名称但不同的功能来定义另一个模块:
#!/usr/bin/ruby
# Module defined in moral.rb file
module Moral
VERY_BAD = 0
BAD = 1
def Moral.sin(badness)
# ...
end
end
与类方法一样,在模块中定义方法时,您需要指定模块名称,后跟一个点,然后再指定方法名称。
Ruby require Statement
require 语句类似于 C 和 C++ 的 include 语句,以及 Java 的 import 语句。如果第三方程序想要使用任何已定义的模块,它只需使用 Ruby require 语句加载模块文件即可:
Example
$LOAD_PATH << '.'
require 'trig.rb'
require 'moral'
y = Trig.sin(Trig::PI/4)
wrongdoing = Moral.sin(Moral::VERY_BAD)
在这里,我们使用 $LOAD_PATH << '.' 使 Ruby 了解到包含的文件必须在当前目录中搜索。如果您不想使用 $LOAD_PATH,则可以使用 require_relative 从相对目录包含文件。
IMPORTANT - 在此,两个文件都包含相同的函数名称。因此,这将在包含到调用程序时导致代码歧义,但模块避免了这种代码歧义,我们能够使用模块名称调用适当的函数。
Ruby include Statement
您可以将模块嵌入到类中。要将模块嵌入到类中,您可以在类中使用 include 语句:
Example
考虑 support.rb 文件中编写的以下模块。
module Week
FIRST_DAY = "Sunday"
def Week.weeks_in_month
puts "You have four weeks in a month"
end
def Week.weeks_in_year
puts "You have 52 weeks in a year"
end
end
现在,您可以按如下所示在课堂中包含这个模块 −
#!/usr/bin/ruby
$LOAD_PATH << '.'
require "support"
class Decade
include Week
no_of_yrs = 10
def no_of_months
puts Week::FIRST_DAY
number = 10*12
puts number
end
end
d1 = Decade.new
puts Week::FIRST_DAY
Week.weeks_in_month
Week.weeks_in_year
d1.no_of_months
这会产生以下结果 −
Sunday
You have four weeks in a month
You have 52 weeks in a year
Sunday
120
Mixins in Ruby
在通读本节之前,我们假定您了解面向对象的概念。
当一个类可以从多个父类中继承特征时,该类应当展示多重继承。
Ruby 不直接支持多重继承,但 Ruby 模块还有另一个美妙之处。总的来说,它们极大地消除了对多重继承的需求,提供了被称为混合的功能。
混合为您提供了一种受控良好的方式来对类添加功能。但是,当混合中的代码开始与使用它的类中的代码进行交互时,它们的真正威力才会显现出来。
让我们研究以下示例代码来了解混合 −
module A
def a1
end
def a2
end
end
module B
def b1
end
def b2
end
end
class Sample
include A
include B
def s1
end
end
samp = Sample.new
samp.a1
samp.a2
samp.b1
samp.b2
samp.s1
模块 A 包含方法 a1 和 a2。模块 B 包含方法 b1 和 b2。类 Sample 包含模块 A 和 B。类 Sample 可以访问所有四个方法,即 a1、a2、b1 和 b2。因此,您可以看到,类 Sample 从两个模块均继承。因此,您可以说类 Sample 展示了多重继承或混合。
Ruby - Strings
Ruby 中的 String 对象保存并处理一个或多个字节的任意序列,通常表示表示人类语言的字符。
最简单的字符串文字用单引号(撇号字符)括起来。引号中的文本是该字符串的值——
'This is a simple Ruby string literal'
如果您需要将一个撇号放在一个单引号字符串文字中,请在前面加上一个反斜杠,这样 Ruby 解释器就不会认为它是终止字符串——
'Won\'t you read O\'Reilly\'s book?'
反斜杠还可以用来转义另一个反斜杠,这样第二个反斜杠就不会被解释为转义字符。
以下是 Ruby 的与字符串相关的特性。
Expression Substitution
表达式替换是将任何 Ruby 表达式的值嵌入到一个字符串中的一种方式,使用 #{ 和 } ——
#!/usr/bin/ruby
x, y, z = 12, 36, 72
puts "The value of x is #{ x }."
puts "The sum of x and y is #{ x + y }."
puts "The average was #{ (x + y + z)/3 }."
这会产生以下结果 −
The value of x is 12.
The sum of x and y is 48.
The average was 40.
General Delimited Strings
使用常规定界字符串,您可以在一对匹配的任意定界符(例如 !、(、{、< 等)内创建字符串,前面加上一个百分号字符 (%)。Q、q 和 x 具有特殊含义。常规定界字符串可以是——
%{Ruby is fun.} equivalent to "Ruby is fun."
%Q{ Ruby is fun. } equivalent to " Ruby is fun. "
%q[Ruby is fun.] equivalent to a single-quoted string
%x!ls! equivalent to back tick command output `ls`
Character Encoding
Ruby 的默认字符集是 ASCII,其字符可以用单个字节表示。如果您使用 UTF-8 或其他现代字符集,则字符可用一个至四个字节表示。
您可以在程序的开头使用 $KCODE 来更改字符集,如下所示——
$KCODE = 'u'
String Built-in Methods
我们需要一个 String 对象的实例来调用 String 方法。以下是创建 String 对象实例的方法——
new [String.new(str = "")]
这将返回一个包含 str 副本的新字符串对象。现在,使用 str 对象,我们可以使用任何可用的实例方法。例如——
#!/usr/bin/ruby
myStr = String.new("THIS IS TEST")
foo = myStr.downcase
puts "#{foo}"
这会产生以下结果 −
this is test
String unpack Directives
Example
尝试以下示例来解包各种数据。
"abc \0\0abc \0\0".unpack('A6Z6') #=> ["abc", "abc "]
"abc \0\0".unpack('a3a3') #=> ["abc", " \000\000"]
"abc \0abc \0".unpack('Z*Z*') #=> ["abc ", "abc "]
"aa".unpack('b8B8') #=> ["10000110", "01100001"]
"aaa".unpack('h2H2c') #=> ["16", "61", 97]
"\xfe\xff\xfe\xff".unpack('sS') #=> [-2, 65534]
"now = 20is".unpack('M*') #=> ["now is"]
"whole".unpack('xax2aX2aX1aX2a') #=> ["h", "e", "l", "l", "o"]
Ruby - Arrays
Ruby 数组是有序、整数索引的任何对象的集合。数组中的每个元素都与一个索引关联并通过索引引用。
数组索引从 0 开始,就像在 C 或 Java 中一样。负索引假定相对于数组的末尾——也就是说,索引 -1 表示数组的最后一个元素,-2 是数组中的倒数第二个元素,依此类推。
Ruby 数组可以包含对象,例如字符串、整数、Fixnum、哈希、符号,甚至是其他数组对象。Ruby 数组不像其他语言中的数组那样严格。在向 Ruby 数组中添加元素时,它们会自动增长。
Creating Arrays
创建或初始化数组有很多方法。一种方法是使用 new 类方法:
names = Array.new
您能在创建数组时设置数组的大小:
names = Array.new(20)
数组名称现在的尺寸或长度是 20 个元素。您可以使用 size 或 length 方法返回数组的大小:
#!/usr/bin/ruby
names = Array.new(20)
puts names.size # This returns 20
puts names.length # This also returns 20
这会产生以下结果 −
20
20
您可以按照如下方式给数组中的每个元素赋一个值:
#!/usr/bin/ruby
names = Array.new(4, "mac")
puts "#{names}"
这会产生以下结果 −
["mac", "mac", "mac", "mac"]
您还可以在 new 中使用一个块,为每个元素填充块求值的内容:
#!/usr/bin/ruby
nums = Array.new(10) { |e| e = e * 2 }
puts "#{nums}"
这会产生以下结果 −
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Array 还有另一种方法 []。它的工作原理如下:
nums = Array.[](1, 2, 3, 4,5)
数组创建的另一种形式如下:
nums = Array[1, 2, 3, 4,5]
核心 Ruby 中提供的 Kernel 模块具有一个 Array 方法,该方法只接受一个参数。在此,该方法将一个范围作为参数来创建一个由数字组成的数组——
#!/usr/bin/ruby
digits = Array(0..9)
puts "#{digits}"
这会产生以下结果 −
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Ruby - Hashes
哈希是键值对的集合,如下所示:“员工”⇒“工资”。它类似于数组,不同之处在于,索引通过任意对象类型的键完成,而不是整数索引。
通过键或值遍历哈希时的顺序可能看起来是任意的,并且通常不会按插入顺序排列。如果尝试通过不存在的键访问哈希,则该方法将返回 nil。
Creating Hashes
与数组一样,有许多方法可以创建哈希。你可以使用 new 类方法创建空哈希 −
months = Hash.new
还可以使用 new 创建具有默认值的哈希,而默认值只是 nil −
months = Hash.new( "month" )
or
months = Hash.new "month"
当使用具有默认值的哈希访问任何键时,如果键或值不存在,访问哈希将返回默认值 −
#!/usr/bin/ruby
months = Hash.new( "month" )
puts "#{months[0]}"
puts "#{months[72]}"
这会产生以下结果 −
month
month
#!/usr/bin/ruby
H = Hash["a" => 100, "b" => 200]
puts "#{H['a']}"
puts "#{H['b']}"
这会产生以下结果 −
100
200
可以使用任何 Ruby 对象作为键或值,甚至是数组,所以以下示例是有效的 −
[1,"jan"] => "January"
Hash Built-in Methods
我们需要一个 Hash 对象的实例来调用 Hash 方法。正如我们所看到的,以下是创建 Hash 对象实例的方法 −
Hash[[key =>|, value]* ] or
Hash.new [or] Hash.new(obj) [or]
Hash.new { |hash, key| block }
这将返回一个填充给定对象的新哈希。现在使用创建的对象,我们可以调用任何可用的实例方法。例如 −
#!/usr/bin/ruby
$, = ", "
months = Hash.new( "month" )
months = {"1" => "January", "2" => "February"}
keys = months.keys
puts "#{keys}"
这会产生以下结果 −
["1", "2"]
以下是公共 hash 方法(假设 hash 是数组对象) −
Sr.No. |
Methods & Description |
1 |
hash == other_hash 根据哈希是否具有相同数量的键值对以及键值对是否与每个哈希中的对应对匹配,测试两个哈希是否相等。 |
2 |
hash.[key] 使用一个键从哈希中引用一个值。如果找不到该键,则返回一个默认值。 |
3 |
hash.[key] = value 关联键给定的值 key 和值给定的值 value。 |
4 |
hash.clear 从哈希中删除所有键值对。 |
5 |
hash.default(key = nil) 返回哈希的默认值,如果未设置则为 nil by default=([]) 如果键不在哈希中,则会返回一个默认值。 |
6 |
hash.default = obj 为哈希设置一个默认值。 |
7 |
hash.default_proc 如果哈希是由块创建的,则返回该块。 |
8 |
hash.delete(key) [or] *array.delete(key) { |
key |
block }*通过键从哈希中删除键值对。如果使用块,则在找不到配对的情况下返回块的结果,比较 delete_if。 |
9 |
*hash.delete_if { |
key,value |
block }*为每个块求值为 true 的配对从哈希中删除键值对。 |
10 |
*hash.each { |
key,value |
block }*迭代哈希,为每个键调用块一次,将键值作为两个元素的数组传递。 |
11 |
*hash.each_key { |
key |
block }*迭代哈希,为每个键调用块一次,将键作为参数传递。 |
12 |
*hash.each_key { |
key_value_array |
block }*迭代哈希,为每个键调用块一次,将键和值作为参数传递。 |
13 |
*hash.each_key { |
value |
block }*迭代哈希,为每个键调用块一次,将值作为参数传递。 |
14 |
hash.empty? 测试哈希是否为空(不包含任何键值对),返回 true 或 false。 |
15 |
hash.fetch(key [, default] ) [or] *hash.fetch(key) { |
key |
block }*为给定的键从哈希中返回一个值。如果找不到该键,并且没有其他参数,它会引发 IndexError 异常;如果给定了 default,则返回该值;如果指定了可选块,则返回其结果。 |
16 |
hash.has_key?(key) [or] hash.include?(key) [or] hash.key?(key) [or] hash.member?(key) 测试给定的键是否存在于哈希中,返回 true 或 false。 |
17 |
hash.has_value?(value) 测试哈希是否包含给定的值。 |
18 |
hash.index(value) 为哈希中给定的值返回键,如果没有找到匹配的值,则返回 nil。 |
19 |
hash.indexes(keys) 返回一个由给定键的值组成的新数组。将为找不到的键插入默认值。此方法已被弃用。使用 select。 |
20 |
hash.indices(keys) 返回包含给定键之一的值的新数组。将会插入没有找到的键的默认值。该方法已被弃用,请使用选择。 |
21 |
hash.inspect 返回哈希的漂亮打印字符串版本。 |
22 |
hash.invert 创建新哈希,反转哈希中的键和值;也就是说,在新哈希中,哈希中的键变为值,值变为键。 |
23 |
hash.keys 使用哈希中的键创建新数组。 |
24 |
hash.length 返回哈希的大小或长度,形式为整数。 |
25 |
hash.merge(other_hash) [or] *hash.merge(other_hash) { |
key, oldval, newval |
block }*返回包含哈希和 other_hash 内容的新哈希,使用 other_hash 中的内容覆盖哈希中具有重复键的键值对。 |
26 |
hash.merge!(other_hash) [or] *hash.merge!(other_hash) { |
key, oldval, newval |
block }*与合并相同,但在原处进行更改。 |
27 |
hash.rehash 根据每个键的当前值重新构建哈希。如果值自插入后发生了变化,则此方法将重新索引哈希。 |
28 |
*hash.reject { |
key, value |
block }*为该块评估为 true 的每一对创建新哈希。 |
29 |
*hash.reject! { |
key, value |
block }*与拒绝相同,但在原处进行了更改。 |
30 |
hash.replace(other_hash) 使用 other_hash 的内容替换哈希的内容。 |
31 |
*hash.select { |
key, value |
block }*返回包含哈希中的键值对的新数组,对于这些键值对,此块会返回 true。 |
32 |
hash.shift 从哈希中删除键值对,并将其作为包含两个元素的数组返回。 |
33 |
hash.size 返回哈希的大小或长度,形式为整数。 |
34 |
hash.sort 将哈希转换为包含键值对数组的二维数组,然后作为数组对其进行排序。 |
35 |
hash.store(key, value) 在哈希中存储键值对。 |
36 |
hash.to_a 从哈希中创建二维数组。每个键值对都转换为数组,然后所有这些数组都存储在包含数组中。 |
37 |
hash.to_hash Returns hash (self). |
38 |
hash.to_s 将哈希转换为数组,然后将该数组转换为字符串。 |
39 |
hash.update(other_hash) [or] *hash.update(other_hash) { |
key, oldval, newval |
block}*返回包含哈希和 other_hash 内容的新哈希,使用 other_hash 中的内容覆盖哈希中具有重复键的键值对。 |
40 |
hash.value?(value) 测试哈希表是否包含给定值。 |
41 |
hash.values 返回一个包含哈希表所有值的数组。 |
42 |
hash.values_at(obj, …​) 返回一个包含哈希表中与给定键关联的值的数组。 |
Ruby - Date & Time
Time 类在 Ruby 中表示日期和时间。它是操作系统提供的时间和日期功能之上的一层薄应用。在您的系统上,此类可能无法表示 1970 年之前或 2038 年之后的日期。
本章使您熟悉所有最需要的日期和时间概念。
Getting Current Date and Time
以下是获取当前日期和时间的简单示例 -
#!/usr/bin/ruby -w
time1 = Time.new
puts "Current Time : " + time1.inspect
# Time.now is a synonym:
time2 = Time.now
puts "Current Time : " + time2.inspect
这会产生以下结果 −
Current Time : Mon Jun 02 12:02:39 -0700 2008
Current Time : Mon Jun 02 12:02:39 -0700 2008
Getting Components of a Date & Time
我们可以使用 Time 对象获取日期和时间的各种组成部分。以下是显示相同的示例 -
#!/usr/bin/ruby -w
time = Time.new
# Components of a Time
puts "Current Time : " + time.inspect
puts time.year # => Year of the date
puts time.month # => Month of the date (1 to 12)
puts time.day # => Day of the date (1 to 31 )
puts time.wday # => 0: Day of week: 0 is Sunday
puts time.yday # => 365: Day of year
puts time.hour # => 23: 24-hour clock
puts time.min # => 59
puts time.sec # => 59
puts time.usec # => 999999: microseconds
puts time.zone # => "UTC": timezone name
这会产生以下结果 −
Current Time : Mon Jun 02 12:03:08 -0700 2008
2008
6
2
1
154
12
3
8
247476
UTC
Time.utc, Time.gm and Time.local Functions
可以使用这两个函数以标准格式设置日期,如下所示 -
# July 8, 2008
Time.local(2008, 7, 8)
# July 8, 2008, 09:10am, local time
Time.local(2008, 7, 8, 9, 10)
# July 8, 2008, 09:10 UTC
Time.utc(2008, 7, 8, 9, 10)
# July 8, 2008, 09:10:11 GMT (same as UTC)
Time.gm(2008, 7, 8, 9, 10, 11)
以下是将所有组件获取为以下格式的数组的示例 -
[sec,min,hour,day,month,year,wday,yday,isdst,zone]
尝试以下操作 -
#!/usr/bin/ruby -w
time = Time.new
values = time.to_a
p values
这将生成以下结果 -
[26, 10, 12, 2, 6, 2008, 1, 154, false, "MST"]
此数组可以传递给 Time.utc 或 Time.local 函数以获取不同格式的日期,如下所示 -
#!/usr/bin/ruby -w
time = Time.new
values = time.to_a
puts Time.utc(*values)
这将生成以下结果 -
Mon Jun 02 12:15:36 UTC 2008
以下是获取时间的方式:表示为自平台相关历元起的秒数
# Returns number of seconds since epoch
time = Time.now.to_i
# Convert number of seconds into Time object.
Time.at(time)
# Returns second since epoch which includes microseconds
time = Time.now.to_f
Timezones and Daylight Savings Time
可以使用 Time 对象来获取所有与时区和夏令时相关的信息,如下所示 -
time = Time.new
# Here is the interpretation
time.zone # => "UTC": return the timezone
time.utc_offset # => 0: UTC is 0 seconds offset from UTC
time.zone # => "PST" (or whatever your timezone is)
time.isdst # => false: If UTC does not have DST.
time.utc? # => true: if t is in UTC time zone
time.localtime # Convert to local timezone.
time.gmtime # Convert back to UTC.
time.getlocal # Return a new Time object in local zone
time.getutc # Return a new Time object in UTC
Formatting Times and Dates
有多种格式化日期和时间的方法。这里是一个显示一些示例的示例 -
#!/usr/bin/ruby -w
time = Time.new
puts time.to_s
puts time.ctime
puts time.localtime
puts time.strftime("%Y-%m-%d %H:%M:%S")
这会产生以下结果 −
Mon Jun 02 12:35:19 -0700 2008
Mon Jun 2 12:35:19 2008
Mon Jun 02 12:35:19 -0700 2008
2008-06-02 12:35:19
Time Formatting Directives
以下表格中的这些指令与方法 Time.strftime 一起使用。
Sr.No. |
Directive & Description |
1 |
%a 星期几的缩写名称(星期日)。 |
2 |
%A 星期几的全名(星期日)。 |
3 |
%b 月份的缩写名称(1 月)。 |
4 |
%B 月份的全名(1 月)。 |
5 |
%c 首选的当地日期和时间表示形式。 |
6 |
%d 这个月的几天(01 到 31)。 |
7 |
%H 一天中的小时数,24 小时制(00 到 23)。 |
8 |
%I 一天中的小时数,12 小时制(01 到 12)。 |
9 |
%j 一年中的第几天(001 至 366)。 |
10 |
%m 一年中的月份(01 至 12)。 |
11 |
%M 一小时中的分钟数(00 至 59)。 |
12 |
%p 经线指示符(AM 或 PM)。 |
13 |
%S 一分钟中的秒数(00 至 60)。 |
14 |
%U 当前年份的周数,从第一周的第一天(星期日)开始(00 至 53)。 |
15 |
%W 当前年份的周数,从第一周的第一天(星期一)开始(00 至 53)。 |
16 |
%w 一周中的某一天(星期日是 0,0 到 6)。 |
17 |
%x 仅表示日期,没有时间的首选表示形式。 |
18 |
%X 仅表示时间的首选表示形式,没有日期。 |
19 |
%y 没有世纪的年(00 到 99)。 |
20 |
%Y Year with century. |
21 |
%Z Time zone name. |
22 |
%% Literal % character. |
Time Arithmetic
你可以使用以下方法对时间执行简单的算术运算:
now = Time.now # Current time
puts now
past = now - 10 # 10 seconds ago. Time - number => Time
puts past
future = now + 10 # 10 seconds from now Time + number => Time
puts future
diff = future - past # => 10 Time - Time => number of seconds
puts diff
这会产生以下结果 −
Thu Aug 01 20:57:05 -0700 2013
Thu Aug 01 20:56:55 -0700 2013
Thu Aug 01 20:57:15 -0700 2013
20.0
Ruby - Ranges
范围无所不在:从 1 月到 12 月、从 0 到 9、从第 50 行到第 67 行,等等。Ruby 支持范围,并允许我们以多种方式使用范围 −
-
Ranges as Sequences
-
Ranges as Conditions
-
Ranges as Intervals
Ranges as Sequences
第一个也是最自然的范围用法是用来表述序列。序列有起点、终点和生成序列中连续值的方法。
Ruby 使用 ''..'' 和 ''…'' 范围运算符创建这些序列。两点形式创建一个包括范围,而三点形式创建一个不包括指定高值的范围。
(1..5) #==> 1, 2, 3, 4, 5
(1...5) #==> 1, 2, 3, 4
('a'..'d') #==> 'a', 'b', 'c', 'd'
1..100 序列作为一个范围对象保存,该对象包含对两个 Fixnum 对象的引用。如果需要,您可以使用 to_a 方法将范围转换成列表。尝试以下示例 −
#!/usr/bin/ruby
$, =", " # Array value separator
range1 = (1..10).to_a
range2 = ('bar'..'bat').to_a
puts "#{range1}"
puts "#{range2}"
这会产生以下结果 −
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
["bar", "bas", "bat"]
范围实现的方法让您以多种方式对其进行迭代并测试其内容 −
#!/usr/bin/ruby
# Assume a range
digits = 0..9
puts digits.include?(5)
ret = digits.min
puts "Min value is #{ret}"
ret = digits.max
puts "Max value is #{ret}"
ret = digits.reject {|i| i < 5 }
puts "Rejected values are #{ret}"
digits.each do |digit|
puts "In Loop #{digit}"
end
这会产生以下结果 −
true
Min value is 0
Max value is 9
Rejected values are 5, 6, 7, 8, 9
In Loop 0
In Loop 1
In Loop 2
In Loop 3
In Loop 4
In Loop 5
In Loop 6
In Loop 7
In Loop 8
In Loop 9
Ranges as Conditions
范围也可以用作条件表达式。例如,以下代码片段从标准输入打印出一组行,其中每组中的第一行包含单词 start,最后一行包含单词 ends −
while gets
print if /start/../end/
end
范围可以用于 case 语句中 −
#!/usr/bin/ruby
score = 70
result = case score
when 0..40 then "Fail"
when 41..60 then "Pass"
when 61..70 then "Pass with Merit"
when 71..100 then "Pass with Distinction"
else "Invalid Score"
end
puts result
这会产生以下结果 −
Pass with Merit
Ranges as Intervals
多功能范围的最后一种用途是作为区间测试:查看某个值是否落在范围表示的区间内。这是使用 === 即 case 相等运算符来完成的。
#!/usr/bin/ruby
if ((1..10) === 5)
puts "5 lies in (1..10)"
end
if (('a'..'j') === 'c')
puts "c lies in ('a'..'j')"
end
if (('a'..'j') === 'z')
puts "z lies in ('a'..'j')"
end
这会产生以下结果 −
5 lies in (1..10)
c lies in ('a'..'j')
Ruby - Iterators
迭代器只不过是集合支持的方法。存储一组数据成员的对象称为集合。在 Ruby 中,数组和哈希可以称为集合。
迭代器依次返回集合的所有元素。这里我们将讨论两个迭代器,each 和 collect。让我们详细了解一下它们。
Ruby collect Iterator
collect 迭代器返回集合的所有元素。
Example
#!/usr/bin/ruby
a = [1,2,3,4,5]
b = Array.new
b = a.collect
puts b
这会产生以下结果 −
1
2
3
4
5
NOTE − collect 方法不是在数组之间进行复制的正确方法。还有另一种称为 clone 的方法,应该使用该方法将一个数组复制到另一个数组中。
通常在你希望使用每个值来获取新数组时使用 collect 方法。例如,此代码生成包含 a 中每个值的 10 倍的数组 b。
#!/usr/bin/ruby
a = [1,2,3,4,5]
b = a.collect{|x| 10*x}
puts b
这会产生以下结果 −
10
20
30
40
50
Ruby - File I/O
Ruby 提供了在内核模块中实现的整套 I/O 相关方法。所有 I/O 方法都派生自类 IO。
类 IO 提供所有基本方法,如 read、write、gets、puts、readline、getc 和 printf。
本章将介绍 Ruby 中可用的所有基本 I/O 函数。有关更多函数,请参阅 Ruby 类 IO。
The File.open Method
可使用 File.open 方法创建一个新文件对象并将该文件对象指定给一个文件。但是,在 File.open 和 File.new 方法之间有一个区别。区别在于 File.open 方法可以与一个代码块关联,但你不能使用 File.new 方法执行相同的操作。
File.open("filename", "mode") do |aFile|
# ... process the file
end
Reading and Writing Files
我们用于“简单”I/O 的相同方法可用于所有文件对象。因此, gets 从标准输入读取一行,而 aFile.gets 从文件对象 aFile 中读取一行。
不过,I/O 对象提供了另外一组访问方法,使我们的生活更轻松。
The sysread Method
可使用 sysread 方法读取文件的内容。使用 sysread 方法时,你可以在任何模式下打开文件。例如 −
以下为输入文本文件 −
This is a simple text file for testing purpose.
现在,我们尝试读取此文件 −
#!/usr/bin/ruby
aFile = File.new("input.txt", "r")
if aFile
content = aFile.sysread(20)
puts content
else
puts "Unable to open file!"
end
此语句将输出文件的前 20 个字符。文件指针现在将置于文件中的第 21 个字符。
The syswrite Method
可使用 syswrite 方法向文件中写入内容。使用 syswrite 方法时,需要以写入模式打开文件。例如 −
#!/usr/bin/ruby
aFile = File.new("input.txt", "r+")
if aFile
aFile.syswrite("ABCDEF")
else
puts "Unable to open file!"
end
此语句将“ABCDEF”写入文件。
The each_byte Method
此方法属于 File 类。each_byte 方法始终与一个代码块相关联。考虑以下代码示例 −
#!/usr/bin/ruby
aFile = File.new("input.txt", "r+")
if aFile
aFile.syswrite("ABCDEF")
aFile.each_byte {|ch| putc ch; putc ?. }
else
puts "Unable to open file!"
end
字符逐个传递给变量 ch,然后按如下方式显示在屏幕上 −
s. .a. .s.i.m.p.l.e. .t.e.x.t. .f.i.l.e. .f.o.r. .t.e.s.t.i.n.g. .p.u.r.p.o.s.e...
.
.
The IO.readlines Method
File 类是 IO 类的子类。IO 类也有一些方法可用于操作文件。
IO 类方法之一是 IO.readlines。此方法按行返回文件的内容。以下代码显示了方法 IO.readlines 的用法 −
#!/usr/bin/ruby
arr = IO.readlines("input.txt")
puts arr[0]
puts arr[1]
在此代码中,变量 arr 是一个数组。文件 input.txt 的每一行将成为数组 arr 中的一个元素。因此, arr[0] 将包含第一行,而 arr[1] 将包含文件的第二行。
The IO.foreach Method
此方法也行行返回输出。foreach 方法和 readlines 方法之间的区别在于 foreach 方法具有区块。但与 readlines 方法不同的是,foreach 方法不会返回数组。例如 −
#!/usr/bin/ruby
IO.foreach("input.txt"){|block| puts block}
此代码将文件 test 的内容逐行传递给变量 block,然后在屏幕上显示输出。
Renaming and Deleting Files
你可以使用重命名和删除方法富有编程性地用 Ruby 重命名和删除文件。
以下是重命名一个现有文件 test1.txt 的示例 −
#!/usr/bin/ruby
# Rename a file from test1.txt to test2.txt
File.rename( "test1.txt", "test2.txt" )
以下是删除一个现有文件 test2.txt 的示例 −
#!/usr/bin/ruby
# Delete file test2.txt
File.delete("test2.txt")
File Modes and Ownership
使用 chmod 方法带有掩码,来更改文件的模式或权限/访问列表 −
以下是更改一个现有文件 test.txt 的模式为掩码值的一个示例 −
#!/usr/bin/ruby
file = File.new( "test.txt", "w" )
file.chmod( 0755 )
File Inquiries
以下命令测试在打开一个文件之前它是否存在 −
#!/usr/bin/ruby
File.open("file.rb") if File::exists?( "file.rb" )
以下命令查询文件是否确实是一个文件 −
#!/usr/bin/ruby
# This returns either true or false
File.file?( "text.txt" )
以下命令找出给定文件名是否是一个目录 −
#!/usr/bin/ruby
# a directory
File::directory?( "/usr/local/bin" ) # => true
# a file
File::directory?( "file.rb" ) # => false
以下命令找出文件是否可读、可写或可执行 −
#!/usr/bin/ruby
File.readable?( "test.txt" ) # => true
File.writable?( "test.txt" ) # => true
File.executable?( "test.txt" ) # => false
以下命令找出文件大小是否为零 −
#!/usr/bin/ruby
File.zero?( "test.txt" ) # => true
以下命令返回文件的大小 −
#!/usr/bin/ruby
File.size?( "text.txt" ) # => 1002
以下命令可用于找出文件类型 −
#!/usr/bin/ruby
File::ftype( "test.txt" ) # => file
ftype 方法通过返回以下项之一来识别文件类型 − file、directory、characterSpecial、blockSpecial、fifo、link、socket 或 unknown。
以下命令可用于查找文件何时被创建、修改或最后一次访问 −
#!/usr/bin/ruby
File::ctime( "test.txt" ) # => Fri May 09 10:06:37 -0700 2008
File::mtime( "text.txt" ) # => Fri May 09 10:44:44 -0700 2008
File::atime( "text.txt" ) # => Fri May 09 10:45:01 -0700 2008
Navigating Through Directories
要在 Ruby 程序中更改目录,请按如下方式使用 Dir.chdir。此示例将当前目录更改为 /usr/bin。
Dir.chdir("/usr/bin")
你可以使用 Dir.pwd 找出当前目录 −
puts Dir.pwd # This will return something like /usr/bin
你可以使用 Dir.entries 在特定目录内获取文件和目录的列表——
puts Dir.entries("/usr/bin").join(' ')
Dir.entries 返回一个数组,其中包含指定目录内的所有条目。Dir.foreach 提供相同的功能——
Dir.foreach("/usr/bin") do |entry|
puts entry
end
获取目录列表的更简洁的方法是使用 Dir 类的数组方法——
Dir["/usr/bin/*"]
Creating a Directory
Dir.mkdir 可用于创建目录——
Dir.mkdir("mynewdir")
你还可以使用 mkdir 设置新目录(不是已存在的目录)的权限——
NOTE ——掩码 755 设置权限所有者、组、世界 [任何人] 为 rwxr-xr-x,其中 r = 读,w = 写,x = 执行。
Dir.mkdir( "mynewdir", 755 )
Deleting a Directory
Dir.delete 可用于删除目录。Dir.unlink 和 Dir.rmdir 执行完全相同的功能,并且为了方便而提供。
Dir.delete("testdir")
Creating Files & Temporary Directories
临时文件是指在程序执行期间短暂创建的文件,但不是信息的永久存储。
Dir.tmpdir 提供当前系统上临时目录的路径,虽然该方法默认不可用。要使 Dir.tmpdir 可用,需要使用 require 'tmpdir'。
你可以将 Dir.tmpdir 与 File.join 一起使用,以创建一个与平台无关的临时文件——
require 'tmpdir'
tempfilename = File.join(Dir.tmpdir, "tingtong")
tempfile = File.new(tempfilename, "w")
tempfile.puts "This is a temporary file"
tempfile.close
File.delete(tempfilename)
此代码创建一个临时文件,向其中写入数据并删除它。Ruby 的标准库还包括一个名为 Tempfile 的库,它可以为你创建临时文件——
require 'tempfile'
f = Tempfile.new('tingtong')
f.puts "Hello"
puts f.path
f.close
Ruby - Exceptions
执行和异常总是同时发生。如果您打开一个不存在的文件,则如果没有正确处理此情况,那么您的程序将被认为质量很差。
如果出现异常,程序将停止。因此,异常被用于处理执行程序期间可能发生的各种错误,并采取适当操作,而不是完全停止程序。
Ruby 提供了一个很好的机制来处理异常。我们将可能引发异常的代码放在一个 begin/end 块中,并使用 rescue 子句告诉 Ruby 我们想要处理的异常类型。
Syntax
begin
# -
rescue OneTypeOfException
# -
rescue AnotherTypeOfException
# -
else
# Other exceptions
ensure
# Always will be executed
end
从 begin 到 rescue 的所有内容都受到保护。如果在此代码块执行期间发生异常,控制将传递到 rescue 和 end 之间的块。
对于 begin 块中的每个 rescue 子句,Ruby 将引发的异常与每个参数逐个进行比较。如果 rescue 子句中命名的异常与当前抛出异常的类型相同,或者该异常的超类匹配,则匹配将成功。
万一异常与任何指定的错误类型都不匹配,我们可以在所有 rescue 子句之后使用一个 else 子句。
Using retry Statement
您可以使用 rescue 块捕获异常,然后使用 retry 语句从头开始执行 begin 块。
Syntax
begin
# Exceptions raised by this code will
# be caught by the following rescue clause
rescue
# This block will capture all types of exceptions
retry # This will move control to the beginning of begin
end
Example
#!/usr/bin/ruby
begin
file = open("/unexistant_file")
if file
puts "File opened successfully"
end
rescue
fname = "existant_file"
retry
end
以下是流程 −
-
异常发生在 open。
-
转到 rescue。fname 被重新分配。
-
通过 retry 转到 begin 的开头。
-
这一次成功打开了文件。
-
Continued the essential process.
NOTE − 请注意,如果重新替换的文件不存在,此示例代码会无限次重试。如果您对异常过程使用 retry,请小心。
Using raise Statement
您可以使用 raise 语句引发异常。以下方法在每次调用时都会引发异常。它会打印其第二个消息。
Syntax
raise
OR
raise "Error Message"
OR
raise ExceptionType, "Error Message"
OR
raise ExceptionType, "Error Message" condition
第一种形式只重新引发当前异常(如果不存在当前异常,则重新引发 RuntimeError)。这用于需要在传递异常之前拦截异常的异常处理程序中。
第二种形式创建一个新的 RuntimeError 异常,将其消息设置为给定的字符串。然后在调用堆栈中引发此异常。
第三种形式使用第一个参数创建一个异常,然后将关联消息设置为第二个参数。
第四种形式类似于第三种形式,但您可以添加任何条件语句(如 unless)以引发异常。
Example
#!/usr/bin/ruby
begin
puts 'I am before the raise.'
raise 'An error has occurred.'
puts 'I am after the raise.'
rescue
puts 'I am rescued.'
end
puts 'I am after the begin block.'
这会产生以下结果 −
I am before the raise.
I am rescued.
I am after the begin block.
另一个展示 raise 用法的示例 −
#!/usr/bin/ruby
begin
raise 'A test exception.'
rescue Exception => e
puts e.message
puts e.backtrace.inspect
end
这会产生以下结果 −
A test exception.
["main.rb:4"]
Using ensure Statement
有时候,你需要保证在代码块结束时完成一些处理,无论是否引发异常。例如,你可能在进入该代码块时打开了文件,你需要确保在该块退出时将其关闭。
ensure 子句就是用来这么做的。ensure 位于最后一个 rescue 子句之后,并且包含一个将在块终止时始终执行的代码块。无论该块正常退出,是引发和捕获了一个异常,还是被未捕获的异常终止,ensure 块都将被运行。
Using else Statement
如果存在 else 子句,则它位于 rescue 子句之后、任何 ensure 之前。
else 子句的主体只在代码主体没有引发任何异常时执行。
Syntax
begin
#.. process
#..raise exception
rescue
# .. handle error
else
#.. executes if there is no exception
ensure
#.. finally ensure execution
#.. This will always execute.
end
Example
begin
# raise 'A test exception.'
puts "I'm not raising exception"
rescue Exception => e
puts e.message
puts e.backtrace.inspect
else
puts "Congratulations-- no errors!"
ensure
puts "Ensuring execution"
end
这会产生以下结果 −
I'm not raising exception
Congratulations-- no errors!
Ensuring execution
引发的错误消息可以用 $! 变量捕获。
Catch and Throw
虽然 raise 和 rescue 的异常机制非常适合在出现问题时放弃执行,但在正常处理过程中有时也可以从一些深度嵌套的构造中跳出。这就是 catch 和 throw 派上用场的时候。
catch 定义了一个用给定名称(可以是符号或字符串)标记的块。该块被正常执行,直到遇到 throw。
Syntax
throw :lablename
#.. this will not be executed
catch :lablename do
#.. matching catch will be executed after a throw is encountered.
end
OR
throw :lablename condition
#.. this will not be executed
catch :lablename do
#.. matching catch will be executed after a throw is encountered.
end
Example
以下示例在一个请求中输入了“!”来使用 throw 终止与用户的交互。
def promptAndGet(prompt)
print prompt
res = readline.chomp
throw :quitRequested if res == "!"
return res
end
catch :quitRequested do
name = promptAndGet("Name: ")
age = promptAndGet("Age: ")
sex = promptAndGet("Sex: ")
# ..
# process information
end
promptAndGet("Name:")
你应该在你的机器上尝试上述程序,因为它需要手动交互。这将产生以下结果 −
Name: Ruby on Rails
Age: 3
Sex: !
Name:Just Ruby
Class Exception
Ruby 的标准类和模块会引发异常。所有的异常类都形成一个层次结构,其中类 Exception 位于顶部。下一级包含七种不同的类型 −
-
Interrupt
-
NoMemoryError
-
SignalException
-
ScriptError
-
StandardError
-
SystemExit
在此级别还有另一个异常 Fatal ,但 Ruby 解释器仅在内部使用它。
ScriptError 和 StandardError 都有许多子类,但我们不必在这里深入细节。重要的是,如果我们创建自己的异常类,它们需要是类 Exception 或其子代的子类。
我们来看一个示例 −
class FileSaveError < StandardError
attr_reader :reason
def initialize(reason)
@reason = reason
end
end
现在,看一看下面的示例,它将使用此异常 −
File.open(path, "w") do |file|
begin
# Write out the data ...
rescue
# Something went wrong!
raise FileSaveError.new($!)
end
end
此处的重要行是 raise FileSaveError.new($!)。我们调用 raise 来发出异常已发生信号,向其传递 FileSaveError 的一个新实例,原因是该特定异常导致数据的写入失败。
Ruby - Object Oriented
Ruby 是一种纯面向对象语言,在 Ruby 中一切都被视为对象。Ruby 中的每个值都是一个对象,即使是最原始的东西:字符串、数字甚至是 true 和 false。即使类本身也是 Object 类的一个实例的对象。本章将带你了解与面向对象 Ruby 相关的全部主要功能。
类用于指定对象的格式,它将数据表示和用于操作数据的函数组合在一个整洁的包中。类中的数据和方法称为类的成员。
Ruby Class Definition
当你定义一个类时,你就定义了一个数据类型的蓝图。这实际上并不定义任何数据,但它确实定义了类名意味着什么,即类的对象将由什么组成,以及可以在这样的对象上执行什么操作。
类定义以关键字 class 开始,随后是 class name ,并用 end 分隔。例如,我们使用关键字 class 定义 Box 类,如下所示:
class Box
code
end
名称必须以大写字母开头,并且根据惯例,包含多个单词的名称会将每个单词的首字母大写并连接在一起,并且没有分隔符(驼峰式命名法)。
Define Ruby Objects
一个类为对象提供蓝图,因此一个对象基本上是从一个类中创建的。我们使用 new 关键字声明类的对象。以下语句声明类的两个对象 −
box1 = Box.new
box2 = Box.new
The initialize Method
initialize method 是一个标准 Ruby 类方法,并且作用方式几乎与 constructor 在其他面向对象编程语言中的作用方式相同。如果你想要在创建对象的时候对一些类变量进行初始化,initialize 方法是很有用的。此方法可以接受一个参数列表,并且像任何其他 ruby 方法一样,它会以 def 关键字为前导,如下所示 −
class Box
def initialize(w,h)
@width, @height = w, h
end
end
The instance Variables
instance variables 是一种类属性,一旦使用类创建了对象,它们就会成为对象的属性。每个对象的属性都单独分配,并且不与其他对象共享值。它们在类中使用 @ 运算符进行访问,但是要在类外部访问它们,我们使用 public 方法,它们称为 accessor methods 。如果我们采用上面定义的类 Box ,那么 @width 和 @height 就是类 Box 的实例变量。
class Box
def initialize(w,h)
# assign instance variables
@width, @height = w, h
end
end
The accessor & setter Methods
要让变量在类外部可用,它们必须在 accessor methods 内进行定义,这些访问器方法也称为 getter 方法。以下示例显示了访问器方法的用法 −
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# accessor methods
def printWidth
@width
end
def printHeight
@height
end
end
# create an object
box = Box.new(10, 20)
# use accessor methods
x = box.printWidth()
y = box.printHeight()
puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"
执行上述代码后,将生成以下结果 −
Width of the box is : 10
Height of the box is : 20
与用于访问变量值的访问器方法类似,Ruby 提供了一种使用 setter methods 从类外部设置这些变量值的方法,如下定义 −
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# accessor methods
def getWidth
@width
end
def getHeight
@height
end
# setter methods
def setWidth=(value)
@width = value
end
def setHeight=(value)
@height = value
end
end
# create an object
box = Box.new(10, 20)
# use setter methods
box.setWidth = 30
box.setHeight = 50
# use accessor methods
x = box.getWidth()
y = box.getHeight()
puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"
执行上述代码后,将生成以下结果 −
Width of the box is : 30
Height of the box is : 50
The instance Methods
instance methods 的定义方式与我们使用 def 关键字定义任何其他方法的方式相同,并且它们只能使用类实例来使用,如下所示。它们的功能并不仅限于访问实例变量,还可以根据你的要求执行更多操作。
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# instance method
def getArea
@width * @height
end
end
# create an object
box = Box.new(10, 20)
# call instance methods
a = box.getArea()
puts "Area of the box is : #{a}"
执行上述代码后,将生成以下结果 −
Area of the box is : 200
The class Methods and Variables
class variables 是一个变量,它在类的所有实例中共享。换句话说,该变量只有一个实例,它由对象实例访问。类变量使用两个 @ 字符(@@)作为前缀。必须在类定义中初始化类变量,如下所示。
类方法使用 def self.methodname() 定义,它以 end 定界符结尾,并且会使用类名作为 classname.methodname 调用,如下例所示 −
#!/usr/bin/ruby -w
class Box
# Initialize our class variables
@@count = 0
def initialize(w,h)
# assign instance avriables
@width, @height = w, h
@@count += 1
end
def self.printCount()
puts "Box count is : #@@count"
end
end
# create two object
box1 = Box.new(10, 20)
box2 = Box.new(30, 100)
# call class method to print box count
Box.printCount()
执行上述代码后,将生成以下结果 −
Box count is : 2
The to_s Method
你定义的任何类都应具有 to_s 实例方法以返回对象的字符串表示形式。以下是一个简单的示例,用于表示 Box 对象的宽度和高度 −
#!/usr/bin/ruby -w
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# define to_s method
def to_s
"(w:#@width,h:#@height)" # string formatting of the object.
end
end
# create an object
box = Box.new(10, 20)
# to_s method will be called in reference of string automatically.
puts "String representation of box is : #{box}"
执行上述代码后,将生成以下结果 −
String representation of box is : (w:10,h:20)
Access Control
Ruby 在实例方法级别提供三级保护,它们可能是 public, private, or protected 。Ruby 对实例和类变量不适用任何访问控制。
-
Public Methods − 任何人都可以调用公共方法。除了 initialize 外,方法都是公有的(initialize 始终是私有的)。
-
Private Methods − 无法从类外部访问或查看私有方法。只有类方法可以访问私有成员。
-
Protected Methods − 只有定义类及其子类对象才能调用受保护的方法。访问权限仅限于家族内部。
以下是一个简单的示例,展示了所有三个访问修饰符的语法 −
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# instance method by default it is public
def getArea
getWidth() * getHeight
end
# define private accessor methods
def getWidth
@width
end
def getHeight
@height
end
# make them private
private :getWidth, :getHeight
# instance method to print area
def printArea
@area = getWidth() * getHeight
puts "Big box area is : #@area"
end
# make it protected
protected :printArea
end
# create an object
box = Box.new(10, 20)
# call instance methods
a = box.getArea()
puts "Area of the box is : #{a}"
# try to call protected or methods
box.printArea()
当执行以上代码时,它会产生以下结果。在此,第一个方法成功调用,但第二个方法出现了问题。
Area of the box is : 200
test.rb:42: protected method `printArea' called for #
<Box:0xb7f11280 @height = 20, @width = 10> (NoMethodError)
Class Inheritance
面向对象编程中最重要的概念之一是继承。继承允许我们在另一个类的基础上定义一个类,这使创建和维护应用程序变得更加容易。
继承还提供了重用代码功能和快速实现时间的机会,但不幸的是,Ruby 不支持多层次继承,但 Ruby 支持 mixins 。混合就像多重继承的一种专门实现,其中仅继承接口部分。
在创建一个类时,程序员可以指定新类应该继承现有类的成员,而不是编写全新的数据成员和成员函数。这个现有的类被称为 base class or superclass ,新类被称为 derived class or sub-class 。
Ruby 也支持子类化的概念,即继承,以下示例解释了这个概念。扩展类的语法很简单。只需添加 < 字符和超类的名称到你的类语句中即可。例如,以下定义一个类 BigBox 作为 Box 的子类 −
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# instance method
def getArea
@width * @height
end
end
# define a subclass
class BigBox < Box
# add a new instance method
def printArea
@area = @width * @height
puts "Big box area is : #@area"
end
end
# create an object
box = BigBox.new(10, 20)
# print the area
box.printArea()
执行上述代码后,将生成以下结果 −
Big box area is : 200
Methods Overriding
尽管可以在派生类中添加新功能,但有时您想要更改父类中已定义的方法的行为。您可以通过保持方法名称相同并覆盖方法的功能来做到这一点,如下例所示 -
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# instance method
def getArea
@width * @height
end
end
# define a subclass
class BigBox < Box
# change existing getArea method as follows
def getArea
@area = @width * @height
puts "Big box area is : #@area"
end
end
# create an object
box = BigBox.new(10, 20)
# print the area using overriden method.
box.getArea()
Operator Overloading
我们希望 + 运算符使用 + 执行两个 Box 对象的向量加法,* 运算符根据一个标量乘以一个 Box 的宽度和高度,一元 - 运算符对 Box 的宽度和高度求反。下面是定义了数学运算符的 Box 类的版本 -
class Box
def initialize(w,h) # Initialize the width and height
@width,@height = w, h
end
def +(other) # Define + to do vector addition
Box.new(@width + other.width, @height + other.height)
end
def -@ # Define unary minus to negate width and height
Box.new(-@width, -@height)
end
def *(scalar) # To perform scalar multiplication
Box.new(@width*scalar, @height*scalar)
end
end
Freezing Objects
有时,我们希望阻止对对象的更改。Object 中的 freeze 方法允许我们做到这一点,有效地将对象变成一个常量。可以通过调用 Object.freeze 来冻结任何对象。冻结的对象可能不会被修改:您不能更改其实例变量。
您可以使用 Object.frozen? 方法检查给定对象是否已冻结,该方法在对象被冻结的情况下返回 true,否则返回 false 值。以下示例阐明了概念 -
#!/usr/bin/ruby -w
# define a class
class Box
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# accessor methods
def getWidth
@width
end
def getHeight
@height
end
# setter methods
def setWidth=(value)
@width = value
end
def setHeight=(value)
@height = value
end
end
# create an object
box = Box.new(10, 20)
# let us freez this object
box.freeze
if( box.frozen? )
puts "Box object is frozen object"
else
puts "Box object is normal object"
end
# now try using setter methods
box.setWidth = 30
box.setHeight = 50
# use accessor methods
x = box.getWidth()
y = box.getHeight()
puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"
执行上述代码后,将生成以下结果 −
Box object is frozen object
test.rb:20:in `setWidth=': can't modify frozen object (TypeError)
from test.rb:39
Class Constants
您可以通过将直接数字或字符串值分配给一个变量来定义类中的常量,而该变量不用 @ 或 @@ 定义。约定上,我们将常量名称保持为大写。
一旦定义了常量,您就不能更改它,但是您可以直接在类中访问常量,就像访问变量一样,但是如果您想在类外访问常量,则必须像下面示例中所示使用 classname::constant 。
#!/usr/bin/ruby -w
# define a class
class Box
BOX_COMPANY = "TATA Inc"
BOXWEIGHT = 10
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# instance method
def getArea
@width * @height
end
end
# create an object
box = Box.new(10, 20)
# call instance methods
a = box.getArea()
puts "Area of the box is : #{a}"
puts Box::BOX_COMPANY
puts "Box weight is: #{Box::BOXWEIGHT}"
执行上述代码后,将生成以下结果 −
Area of the box is : 200
TATA Inc
Box weight is: 10
类常量可被继承并且可以像实例方法一样被覆盖。
Create Object Using Allocate
当您想在不调用其构造函数 initialize (即使用 new 方法)的情况下创建对象时,可能会出现这种情况,在这种情况下,您可以调用 allocate,它将为您创建一个未初始化的对象,如下例所示 -
#!/usr/bin/ruby -w
# define a class
class Box
attr_accessor :width, :height
# constructor method
def initialize(w,h)
@width, @height = w, h
end
# instance method
def getArea
@width * @height
end
end
# create an object using new
box1 = Box.new(10, 20)
# create another object using allocate
box2 = Box.allocate
# call instance method using box1
a = box1.getArea()
puts "Area of the box is : #{a}"
# call instance method using box2
a = box2.getArea()
puts "Area of the box is : #{a}"
执行上述代码后,将生成以下结果 −
Area of the box is : 200
test.rb:14: warning: instance variable @width not initialized
test.rb:14: warning: instance variable @height not initialized
test.rb:14:in `getArea': undefined method `*'
for nil:NilClass (NoMethodError) from test.rb:29
Class Information
如果类定义是可执行代码,这意味着它们在某个对象的上下文中执行:self 必须引用某些东西。让我们找出是什么。
#!/usr/bin/ruby -w
class Box
# print class information
puts "Type of self = #{self.type}"
puts "Name of self = #{self.name}"
end
执行上述代码后,将生成以下结果 −
Type of self = Class
Name of self = Box
这意味着类定义以该类为当前对象执行。这意味着在执行方法定义期间将可以使用元类及其超类中的方法。
Ruby - Regular Expressions
正则表达式是一个特殊的字符序列,它帮助您使用保存在模式中的特殊语法匹配或找到其他字符串或一组字符串。
正则表达式文字是在斜杠之间或在任意分隔符之后加 %r 的模式,如下所示 −
Syntax
/pattern/
/pattern/im # option can be specified
%r!/usr/local! # general delimited regular expression
Regular-Expression Modifiers
正则表达式文字可能包括一个可选修饰符来控制匹配的各个方面。修饰符在第二个斜杠字符之后指定,如前所示,可用以下字符中的一个表示 −
Sr.No. |
Modifier & Description |
1 |
i 在匹配文本时忽略大小写。 |
2 |
o 仅执行一次 #{} 插值,在首次评估 regexp 文字时执行。 |
3 |
x 忽略空格并允许正则表达式中的注释。 |
4 |
m 匹配多行,将换行符识别为普通字符。 |
5 |
u,e,s,n 将正则表达式解释为 Unicode(UTF-8)、EUC、SJIS 或 ASCII。如果未指定这些修饰符中的任何一个,则假定正则表达式使用源编码。 |
与使用 %Q 分隔的字符串文字类似,Ruby 允许你使用 %r 开始你的正则表达式,然后跟上你选择的限定符。当你要描述的模式包含大量你不想要转义的正斜杠字符时,此方法非常有用 −
# Following matches a single slash character, no escape required
%r|/|
# Flag characters are allowed with this syntax, too
%r[</(.*)>]i
Search and Replace
一些最重要的使用正则表达式的 String 方法是 sub 和 gsub ,以及它们的原地变体 sub! 和 gsub! 。
所有这些方法都使用 Regexp 模式执行搜索并替换操作。 sub 和 sub! 替换模式的首次出现, gsub 和 gsub! 替换所有出现。
sub 和 gsub 返回一个新字符串,保持原始字符串不变,而 sub! 和 gsub! 修改了调用它们的字符串。
以下示例 −
#!/usr/bin/ruby
phone = "2004-959-559 #This is Phone Number"
# Delete Ruby-style comments
phone = phone.sub!(/#.*$/, "")
puts "Phone Num : #{phone}"
# Remove anything other than digits
phone = phone.gsub!(/\D/, "")
puts "Phone Num : #{phone}"
这会产生以下结果 −
Phone Num : 2004-959-559
Phone Num : 2004959559
以下示例 −
#!/usr/bin/ruby
text = "rails are rails, really good Ruby on Rails"
# Change "rails" to "Rails" throughout
text.gsub!("rails", "Rails")
# Capitalize the word "Rails" throughout
text.gsub!(/\brails\b/, "Rails")
puts "#{text}"
这会产生以下结果 −
Rails are Rails, really good Ruby on Rails
Ruby/DBI Tutorial
本章教你如何使用 Ruby 访问数据库。Ruby DBI 模块为 Ruby 脚本提供了一个与 Perl DBI 模块类似的数据库独立接口。
DBI 代表 Ruby 的数据库独立接口,这意味着 DBI 在 Ruby 代码和底层数据库之间提供了一个抽象层,让你可以非常轻松地切换数据库实现。它定义了一组方法、变量和约定,它们提供了一个一致的数据库接口,与实际使用的数据库无关。
DBI 可以与以下内容交互 −
-
ADO (ActiveX Data Objects)
-
DB2
-
Frontbase
-
mSQL
-
MySQL
-
ODBC
-
Oracle
-
OCI8 (Oracle)
-
PostgreSQL
-
Proxy/Server
-
SQLite
-
SQLRelay
Architecture of a DBI Application
DBI 独立于后端可用的任何数据库。无论你使用的是 Oracle、MySQL 还是 Informix 等,都可以使用 DBI。这从以下架构图中可以看出。
Ruby DBI 的一般架构使用两层 −
-
数据库接口 (DBI) 层。该层与数据库无关,并提供了一组通用访问方法,无论使用哪种类型的数据库服务器,使用方式都相同。
-
数据库驱动程序 (DBD) 层。该层依赖于数据库;不同的驱动程序提供对不同数据库引擎的访问。有一个适用于 MySQL 的驱动程序,另一个适用于 PostgreSQL,另一个适用于 InterBase,另一个适用于 Oracle,以此类推。每个驱动程序都会解释来自 DBI 层的请求,并将其映射到适用于给定类型的数据库服务器的请求。
Prerequisites
如果你想要编写 Ruby 脚本来访问 MySQL 数据库,则需要安装 Ruby MySQL 模块。
如上所述,该模块充当 DBD,并且可以从 https://www.tmtm.org/en/mysql/ruby/ 下载
Obtaining and Installing Ruby/DBI
您可以使用 Ruby Gems 软件包管理器安装 Ruby DBI:
gem install dbi
在开始此安装之前,请确保您拥有 root 权限。现在,按照以下步骤操作:
Database Connection
假设我们要使用 MySQL 数据库,在连接到数据库之前确保以下内容:
-
您已经创建了一个 TESTDB 数据库。
-
您已经在 TESTDB 中创建了 EMPLOYEE。
-
该表具有字段 FIRST_NAME、LAST_NAME、AGE、SEX 和 INCOME。
-
设置了用户 ID“testuser”和密码“test123”来访问 TESTDB。
-
Ruby Module DBI 已正确安装在您的计算机上。
-
您已经学习了 MySQL 教程以了解 MySQL 基础知识。
以下是连接到 MySQL 数据库“TESTDB”的示例
#!/usr/bin/ruby -w
require "dbi"
begin
# connect to the MySQL server
dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123")
# get server version string and display it
row = dbh.select_one("SELECT VERSION()")
puts "Server version: " + row[0]
rescue DBI::DatabaseError => e
puts "An error occurred"
puts "Error code: #{e.err}"
puts "Error message: #{e.errstr}"
ensure
# disconnect from server
dbh.disconnect if dbh
end
在运行此脚本时,它会在我们的 Linux 计算机上产生以下结果。
Server version: 5.0.45
如果已与数据源建立连接,则返回数据库句柄并将其保存在 dbh 中以供进一步使用,否则 dbh 将设置为 nil 值,而 e.err 和 e::errstr 将分别返回错误代码和错误字符串。
最后,在退出之前,请确保已关闭数据库连接且已释放资源。
INSERT Operation
当您想要将记录创建到数据库表中时,需要 INSERT 操作。
一旦建立了数据库连接,我们就可以使用 do 方法或 prepare 和 execute 方法在数据库表中创建表或记录。
Using do Statement
可以通过调用 do 数据库句柄方法发出不返回行的语句。此方法采用语句字符串参数,并返回受语句影响的行数。
dbh.do("DROP TABLE IF EXISTS EMPLOYEE")
dbh.do("CREATE TABLE EMPLOYEE (
FIRST_NAME CHAR(20) NOT NULL,
LAST_NAME CHAR(20),
AGE INT,
SEX CHAR(1),
INCOME FLOAT )" );
同样,您可以执行 SQL INSERT 语句,以在 EMPLOYEE 表中创建记录。
#!/usr/bin/ruby -w
require "dbi"
begin
# connect to the MySQL server
dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123")
dbh.do( "INSERT INTO EMPLOYEE(FIRST_NAME, LAST_NAME, AGE, SEX, INCOME)
VALUES ('Mac', 'Mohan', 20, 'M', 2000)" )
puts "Record has been created"
dbh.commit
rescue DBI::DatabaseError => e
puts "An error occurred"
puts "Error code: #{e.err}"
puts "Error message: #{e.errstr}"
dbh.rollback
ensure
# disconnect from server
dbh.disconnect if dbh
end
Using prepare and execute
您可以使用 DBI 类的 prepare 和 execute 方法通过 Ruby 代码执行 SQL 语句。
记录创建采用以下步骤:
-
使用 INSERT 语句准备 SQL 语句。将使用 prepare 方法进行此操作。
-
执行 SQL 查询以从数据库中选择所有结果。将使用 execute 方法进行此操作。
-
释放语句句柄。此操作将使用 finish API 进行。
-
如果一切正常,则 commit 此操作,否则可以 rollback 完整的交易。
下面是使用这两种方法的语法 −
sth = dbh.prepare(statement)
sth.execute
... zero or more SQL operations ...
sth.finish
这两种方法可用于将 bind 值传递给 SQL 语句。可能出现以下情况:要输入的值未提前给出。在这种情况下,将使用绑定值。问号 ( ? ) 用于替换实际值,然后通过 execute() API 传递实际值。
以下是 EMPLOYEE 表中创建两条记录的示例 −
#!/usr/bin/ruby -w
require "dbi"
begin
# connect to the MySQL server
dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123")
sth = dbh.prepare( "INSERT INTO EMPLOYEE(FIRST_NAME, LAST_NAME, AGE, SEX, INCOME)
VALUES (?, ?, ?, ?, ?)" )
sth.execute('John', 'Poul', 25, 'M', 2300)
sth.execute('Zara', 'Ali', 17, 'F', 1000)
sth.finish
dbh.commit
puts "Record has been created"
rescue DBI::DatabaseError => e
puts "An error occurred"
puts "Error code: #{e.err}"
puts "Error message: #{e.errstr}"
dbh.rollback
ensure
# disconnect from server
dbh.disconnect if dbh
end
如果一次有多个 INSERT,那么先准备一条语句,然后在循环中多次执行它比每次通过循环来调用 do 更有效率。
READ Operation
在任何数据库上进行 READ 操作意味着从数据库中获取一些有用的信息。
一旦建立我们的数据库连接,我们就可以对该数据库进行查询。我们可以使用 do 方法或 prepare 和 execute 方法从数据库表中获取值。
记录获取需要以下步骤 −
-
基于所需条件准备 SQL 查询。将使用 prepare 方法进行此操作。
-
执行 SQL 查询以从数据库中选择所有结果。将使用 execute 方法进行此操作。
-
逐个获取所有结果并打印这些结果。将使用 fetch 方法执行此操作。
-
释放语句句柄。此操作将使用 finish 方法进行。
以下是在 EMPLOYEE 表中查询薪水超过 1000 的所有记录的过程。
#!/usr/bin/ruby -w
require "dbi"
begin
# connect to the MySQL server
dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123")
sth = dbh.prepare("SELECT * FROM EMPLOYEE WHERE INCOME > ?")
sth.execute(1000)
sth.fetch do |row|
printf "First Name: %s, Last Name : %s\n", row[0], row[1]
printf "Age: %d, Sex : %s\n", row[2], row[3]
printf "Salary :%d \n\n", row[4]
end
sth.finish
rescue DBI::DatabaseError => e
puts "An error occurred"
puts "Error code: #{e.err}"
puts "Error message: #{e.errstr}"
ensure
# disconnect from server
dbh.disconnect if dbh
end
这会产生以下结果 −
First Name: Mac, Last Name : Mohan
Age: 20, Sex : M
Salary :2000
First Name: John, Last Name : Poul
Age: 25, Sex : M
Salary :2300
还有更简单的快捷方式来从数据库中获取记录。如果感兴趣,请浏览 Fetching the Result ,否则继续下一部分。
Update Operation
对任何数据库上的 UPDATE 操作意味着更新数据库中已有的一个或多个记录。以下是在 SEX 为 'M' 的情况下更新所有记录的过程。在这里,我们将所有男性的 AGE 增加一年。这需要三个步骤 −
-
基于所需条件准备 SQL 查询。将使用 prepare 方法进行此操作。
-
执行 SQL 查询以从数据库中选择所有结果。将使用 execute 方法进行此操作。
-
释放语句句柄。此操作将使用 finish 方法进行。
-
如果一切正常,则 commit 此操作,否则可以 rollback 完整的交易。
#!/usr/bin/ruby -w
require "dbi"
begin
# connect to the MySQL server
dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123")
sth = dbh.prepare("UPDATE EMPLOYEE SET AGE = AGE + 1 WHERE SEX = ?")
sth.execute('M')
sth.finish
dbh.commit
rescue DBI::DatabaseError => e
puts "An error occurred"
puts "Error code: #{e.err}"
puts "Error message: #{e.errstr}"
dbh.rollback
ensure
# disconnect from server
dbh.disconnect if dbh
end
DELETE Operation
当您想从数据库中删除某些记录时,需要 DELETE 操作。以下是删除 EMPLOYEE 表中 AGE 大于 20 的所有记录的过程。此操作将执行以下步骤。
-
基于所需条件准备 SQL 查询。将使用 prepare 方法进行此操作。
-
执行 SQL 查询以从数据库中删除所需的记录。将使用 execute 方法进行此操作。
-
释放语句句柄。此操作将使用 finish 方法进行。
-
如果一切正常,则 commit 此操作,否则可以 rollback 完整的交易。
#!/usr/bin/ruby -w
require "dbi"
begin
# connect to the MySQL server
dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123")
sth = dbh.prepare("DELETE FROM EMPLOYEE WHERE AGE > ?")
sth.execute(20)
sth.finish
dbh.commit
rescue DBI::DatabaseError => e
puts "An error occurred"
puts "Error code: #{e.err}"
puts "Error message: #{e.errstr}"
dbh.rollback
ensure
# disconnect from server
dbh.disconnect if dbh
end
Performing Transactions
事务是一种确保数据一致性的机制。事务应当有以下四种属性 −
-
Atomicity − 事务要么完成,要么根本不会发生。
-
Consistency - 交易必须以一致的状态开始,且使系统进入一致的状态。
-
Isolation − 事务的中间结果在当前事务外不可见。
-
Durability − 一旦提交事务,其效果将持久,即使在系统故障后也是如此。
DBI 提供两种方法来提交或回滚交易。还有一个名为事务的方法,可用于实施事务。有两种简单方法来实施事务 -
Approach I
第一种方法使用 DBI 的 commit 和 rollback 方法来明确提交或取消事务 -
dbh['AutoCommit'] = false # Set auto commit to false.
begin
dbh.do("UPDATE EMPLOYEE SET AGE = AGE+1 WHERE FIRST_NAME = 'John'")
dbh.do("UPDATE EMPLOYEE SET AGE = AGE+1 WHERE FIRST_NAME = 'Zara'")
dbh.commit
rescue
puts "transaction failed"
dbh.rollback
end
dbh['AutoCommit'] = true
Approach II
第二种方法使用事务方法。这更简单,因为它采用包含构成事务的语句的代码块。事务方法执行代码块,然后根据代码块是否成功或失败,自动调用 commit 或 rollback -
dbh['AutoCommit'] = false # Set auto commit to false.
dbh.transaction do |dbh|
dbh.do("UPDATE EMPLOYEE SET AGE = AGE+1 WHERE FIRST_NAME = 'John'")
dbh.do("UPDATE EMPLOYEE SET AGE = AGE+1 WHERE FIRST_NAME = 'Zara'")
end
dbh['AutoCommit'] = true
COMMIT Operation
提交是一项操作,它向数据库发出一个绿色信号,以最终确定所做的更改,并且在此操作之后,任何更改都将无法还原。
下面是一个调用 commit 方法的简单示例。
dbh.commit
ROLLBACK Operation
如果你对一项或多项更改不满意,并且希望完全撤销这些更改,那么请使用 rollback 方法。
下面是一个调用 rollback 方法的简单示例。
dbh.rollback
Disconnecting Database
要断开数据库连接,请使用断开连接 API。
dbh.disconnect
如果用户使用断开连接方法关闭与数据库的连接,DBI 会回滚所有未完成的事务。但是,与其依赖于 DBI 的任何实施细节,你的应用程序最好明确调用 commit 或 rollback。
Handling Errors
有许多错误来源。一些示例是已执行 SQL 语句中的语法错误、连接故障或对已取消或完成的语句句柄调用获取方法。
如果 DBI 方法失败,DBI 会引发异常。DBI 方法可能会引发任何几种类型的异常,但最重要的两个异常类是 DBI::InterfaceError 和 DBI::DatabaseError。
这些类的异常对象有三个属性,分别名为 err、errstr 和 state,表示错误号、描述性错误字符串和标准错误代码。这些属性的解释如下 -
-
err - 返回发生的错误的整数表示,如果不被 DBD 支持,则返回 nil。例如,Oracle DBD 会返回 ORA-XXXX 错误消息的数字部分。
-
errstr - 返回发生的错误的字符串表示。
-
state - 返回发生的错误的 SQLSTATE 代码。SQLSTATE 是一个长度为 5 个字符的字符串。大多数 DBD 不支持此功能,而是返回 nil。
你一定在大多数示例中看到了上面的代码 -
rescue DBI::DatabaseError => e
puts "An error occurred"
puts "Error code: #{e.err}"
puts "Error message: #{e.errstr}"
dbh.rollback
ensure
# disconnect from server
dbh.disconnect if dbh
end
要获取有关你的脚本在执行时正在进行的操作的调试信息,你可以启用跟踪。要做到这一点,你必须首先加载 dbi/trace 模块,然后调用控制跟踪模式和输出目的地的 trace 方法 -
require "dbi/trace"
..............
trace(mode, destination)
模式值可以是 0(关闭)、1、2 或 3,目标应该是 IO 对象。默认值分别是 2 和 STDERR。
Code Blocks with Methods
有一些方法可以创建句柄。这些方法可以通过代码块来调用。使用代码块和方法一起的优点是,它们将句柄作为其参数传递给代码块,并在代码块终止时自动清除句柄。以下有几个理解这个概念的示例。
-
DBI.connect - 此方法生成一个数据库句柄,建议在代码块的末尾调用断开连接以断开数据库连接。
-
dbh.prepare − 此方法将生成一个语句句柄,建议在代码块结束时结束该语句句柄。在该代码块中,您必须调用执行方法才能执行此语句。
-
dbh.execute − 此方法类似,但我们不需要在该代码块中调用执行方法。系统会自动执行该语句句柄。
Example 1
DBI.connect 可以获取代码块,向数据库句柄传递代码块,然后在代码块的末尾自动断开与句柄的连接,如下所示。
dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123") do |dbh|
Driver-specific Functions and Attributes
使用 DBI,数据库驱动程序可以提供其他特定于数据库的功能,用户可以通过任何句柄对象的 func 方法调用这些函数。
支持特定于驱动程序的属性,可以使用 []= 或 [] 方法设置或获取这些属性。
Example
#!/usr/bin/ruby
require "dbi"
begin
# connect to the MySQL server
dbh = DBI.connect("DBI:Mysql:TESTDB:localhost", "testuser", "test123")
puts dbh.func(:client_info)
puts dbh.func(:client_version)
puts dbh.func(:host_info)
puts dbh.func(:proto_info)
puts dbh.func(:server_info)
puts dbh.func(:thread_id)
puts dbh.func(:stat)
rescue DBI::DatabaseError => e
puts "An error occurred"
puts "Error code: #{e.err}"
puts "Error message: #{e.errstr}"
ensure
dbh.disconnect if dbh
end
这会产生以下结果 −
5.0.45
50045
Localhost via UNIX socket
10
5.0.45
150621
Uptime: 384981 Threads: 1 Questions: 1101078 Slow queries: 4 \
Opens: 324 Flush tables: 1 Open tables: 64 \
Queries per second avg: 2.860
Ruby Web Applications - CGI Programming
Ruby 是一种通用语言;它根本不能准确地称为 Web 语言。尽管如此,Web 应用程序和通用 Web 工具仍然是 Ruby 最常见的用途之一。
您不仅可以使用 Ruby 编写自己的 SMTP 服务器、FTP 守护程序或 Web 服务器,还可以将 Ruby 用于更常用的任务,例如 CGI 编程或作为 PHP 的替代方案。
请花几分钟时间参阅 CGI Programming 教程,以更详细地了解 CGI 编程。
Writing CGI Scripts
最基本的 Ruby CGI 脚本如下所示 −
#!/usr/bin/ruby
puts "HTTP/1.0 200 OK"
puts "Content-type: text/html\n\n"
puts "<html><body>This is a test</body></html>"
如果您调用此脚本 test.cgi 并以正确的权限将其上传到基于 Unix 的 Web 托管服务提供商,您可以将其用作 CGI 脚本。
例如,如果您有一个托管在 Linux Web 托管服务提供商上的名为 https://www.example.com/ 的网站,并且您已将 test.cgi 上传到主目录并授予它执行权限,那么访问 https://www.example.com/test.cgi 时应该会返回一个显示 This is a test 的 HTML 网页。
在此,当 Web 浏览器请求 test.cgi 时,Web 服务器会在网站上查找 test.cgi,然后使用 Ruby 解释器执行它。此 Ruby 脚本会返回一个基本的 HTTP 头,然后返回一个基本的 HTML 文档。
Using cgi.rb
Ruby 自带一个名为 cgi 的特殊库,它支持比之前的 CGI 脚本更复杂的操作。
让我们创建一个使用 cgi 的基本 CGI 脚本 −
#!/usr/bin/ruby
require 'cgi'
cgi = CGI.new
puts cgi.header
puts "<html><body>This is a test</body></html>"
此处,您创建了一个 CGI 对象并使用它为您打印头行。
Form Processing
使用类 CGI 使您可以通过两种方式访问 HTML 查询参数。假设我们提供了一个 URL /cgi-bin/test.cgi?FirstName = Zara&LastName = Ali。
按照以下方法直接使用 CGI#[] 访问 FirstName 和 LastName 参数:
#!/usr/bin/ruby
require 'cgi'
cgi = CGI.new
cgi['FirstName'] # => ["Zara"]
cgi['LastName'] # => ["Ali"]
访问这些表单变量还有另一种方法。以下代码会给你一个所有键值对的哈希值:
#!/usr/bin/ruby
require 'cgi'
cgi = CGI.new
h = cgi.params # => {"FirstName"=>["Zara"],"LastName"=>["Ali"]}
h['FirstName'] # => ["Zara"]
h['LastName'] # => ["Ali"]
以下代码用于检索所有键:
#!/usr/bin/ruby
require 'cgi'
cgi = CGI.new
cgi.keys # => ["FirstName", "LastName"]
如果表单包含具有相同名称的多个字段,则相应的键值对将作为数组返回给脚本。[] 访问器仅返回这些值中的第一个。对 params 方法的结果进行索引用以获取所有键值对。
在此示例中,假设表单有三个名为“姓名”的字段,我们输入了三个姓名“Zara”、“Huma”和“Nuha”:
#!/usr/bin/ruby
require 'cgi'
cgi = CGI.new
cgi['name'] # => "Zara"
cgi.params['name'] # => ["Zara", "Huma", "Nuha"]
cgi.keys # => ["name"]
cgi.params # => {"name"=>["Zara", "Huma", "Nuha"]}
Note - Ruby 将自动处理 GET 和 POST 方法。这两个不同的方法没有单独的处理。
可以发送正确数据的相关但基本的表单具有如下 HTML 代码:
<html>
<body>
<form method = "POST" action = "http://www.example.com/test.cgi">
First Name :<input type = "text" name = "FirstName" value = "" />
<br />
Last Name :<input type = "text" name = "LastName" value = "" />
<input type = "submit" value = "Submit Data" />
</form>
</body>
</html>
Creating Forms and HTML
CGI 包含大量用于创建 HTML 的方法。每个标签都有一个方法。为了启用这些方法,你必须通过调用 CGI.new 创建一个 CGI 对象。
为了简化标签嵌套,这些方法将内容用作代码块。代码块应该返回一个字符串,该字符串将用作标签的内容。例如:
#!/usr/bin/ruby
require "cgi"
cgi = CGI.new("html4")
cgi.out {
cgi.html {
cgi.head { "\n"+cgi.title{"This Is a Test"} } +
cgi.body { "\n"+
cgi.form {"\n"+
cgi.hr +
cgi.h1 { "A Form: " } + "\n"+
cgi.textarea("get_text") +"\n"+
cgi.br +
cgi.submit
}
}
}
}
NOTE - CGI 类的 form 方法可以接受一个方法参数,该参数将设置提交表单时要使用的 HTTP 方法 (GET、POST 等)。本示例中使用的默认值为 POST。
这会产生以下结果 −
Content-Type: text/html
Content-Length: 302
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Final//EN">
<HTML>
<HEAD>
<TITLE>This Is a Test</TITLE>
</HEAD>
<BODY>
<FORM METHOD = "post" ENCTYPE = "application/x-www-form-urlencoded">
<HR>
<H1>A Form: </H1>
<TEXTAREA COLS = "70" NAME = "get_text" ROWS = "10"></TEXTAREA>
<BR>
<INPUT TYPE = "submit">
</FORM>
</BODY>
</HTML>
Quoting Strings
在处理 URL 和 HTML 代码时,你必须小心引用某些字符。例如,斜杠字符 (/) 在 URL 中具有特殊含义,因此当它不是路径名的一部分时,必须 escaped 。
例如,URL 查询部分中的任何 / 都将转换为字符串 %2F,并且你必须将其转换回 / 才能使用它。空格和与号也是特殊字符。为了解决此问题,CGI 提供了例程 CGI.escape 和 CGI.unescape 。
#!/usr/bin/ruby
require 'cgi'
puts CGI.escape(Zara Ali/A Sweet & Sour Girl")
这会产生以下结果 −
Zara+Ali%2FA Sweet+%26+Sour+Girl")
#!/usr/bin/ruby
require 'cgi'
puts CGI.escapeHTML('<h1>Zara Ali/A Sweet & Sour Girl</h1>')
这会产生以下结果 −
<h1>Zara Ali/A Sweet & Sour Girl</h1>'
Cookies and Sessions
我们在不同的部分中解释了这两个概念。请遵循以下部分:
-
Ruby CGI Cookies - 如何处理 CGI Cookie。
-
Ruby CGI Sessions - 如何管理 CGI 会话。
Sending Email using Ruby - SMTP
简单邮件传输协议 (SMTP) 是一种协议,处理在邮件服务器之间发送和路由电子邮件。
Ruby 提供 Net::SMTP 类用于简单邮件传输协议 (SMTP) 客户端连接,并提供了两个类方法 new 和 start。
-
new 采用两个参数 - 服务器名称默认为 localhost,端口号默认为众所周知的端口 25。
-
start 方法采用这些参数 - 服务器 - SMTP 服务器的 IP 名称,默认为 localhost;端口 - 端口号,默认为 25;域 - 邮件发送者的域,默认为 ENV["HOSTNAME"];帐户 - 用户名,默认为 nil;密码 - 用户密码,默认为 nil;authtype - 授权类型,默认为 cram_md5。
一个 SMTP 对象具有一个实例方法 sendmail,它通常用于发送邮件。它采用三个参数 -
-
源 - 字符串、数组或任何具有 each 迭代器的东西,一次返回一个字符串。
-
发送者 - 将出现在电子邮件的 from 字段中的字符串。
-
收件人 - 表示收件人地址的字符串或字符串数组。
Example
下面是使用 Ruby 脚本发送一封电子邮件的简单方法,试试吧 -
require 'net/smtp'
message = <<MESSAGE_END
From: Private Person <me@fromdomain.com>
To: A Test User <test@todomain.com>
Subject: SMTP e-mail test
This is a test e-mail message.
MESSAGE_END
Net::SMTP.start('localhost') do |smtp|
smtp.send_message message, 'me@fromdomain.com', 'test@todomain.com'
end
这里,你使用文档将基本电子邮件放入消息中,并注意正确设置标头格式。电子邮件需要一个 From 、 To 和 Subject 标头,并用空行与电子邮件正文分开。
要发送邮件,你可以使用 Net::SMTP 连接到本地计算机上的 SMTP 服务器,然后将 send_message 方法与邮件、发件人地址和目标地址一起用作参数(尽管发件人和收件人地址都在电子邮件本身中,但这些地址并不总是用于路由邮件)。
如果你不打算在计算机上运行 SMTP 服务器,你可以使用 Net::SMTP 与远程 SMTP 服务器进行通信。除非你正在使用 Web 邮件服务(例如 Hotmail 或 Yahoo! Mail),你的电子邮件提供商会向你提供发件服务器详细信息,你可以将其提供给 Net::SMTP,具体如下 -
Net::SMTP.start('mail.your-domain.com')
此代码行将连接到 mail.your-domain.com 的端口 25 上的 SMTP 服务器,而不会使用任何用户名或密码。不过,如果你需要,你可以指定端口号和其他详细信息。例如 -
Net::SMTP.start('mail.your-domain.com',
25,
'localhost',
'username', 'password' :plain)
此示例使用用户名和明文格式的密码连接到 mail.your-domain.com 上的 SMTP 服务器。它将客户端的主机名标识为 localhost。
Sending an HTML e-mail using Ruby
如果你使用 Ruby 发送文本消息,那么所有内容都将被视为简单文本。即使你在文本消息中包含 HTML 标签,它也会显示为简单文本,HTML 标签不会根据 HTML 语法进行格式化。但是,Ruby Net::SMTP 提供了将 HTML 消息作为实际 HTML 消息发送的选项。
发送邮件时,你可以指定一个 Mime 版本、内容类型和字符集来发送 HTML 邮件。
Example
以下是将 HTML 内容作为电子邮件发送的示例,试试吧 -
require 'net/smtp'
message = <<MESSAGE_END
From: Private Person <me@fromdomain.com>
To: A Test User <test@todomain.com>
MIME-Version: 1.0
Content-type: text/html
Subject: SMTP e-mail test
This is an e-mail message to be sent in HTML format
<b>This is HTML message.</b>
<h1>This is headline.</h1>
MESSAGE_END
Net::SMTP.start('localhost') do |smtp|
smtp.send_message message, 'me@fromdomain.com', 'test@todomain.com'
end
Sending Attachments as an e-mail
要发送包含混合内容的电子邮件,需要将 Content-type 标头设置为 multipart/mixed 。然后,可以在 boundaries 中指定文本和附件部分。
一个边界的开头是两个连字符后跟一个唯一数字,它不能出现在电子邮件的消息部分。表示电子邮件的最终部分的最终边界也必须以两个连字符结束。
附加文件应使用 pack("m") 函数进行编码,以便在传输之前进行 base64 编码。
Example
以下是发送文件 /tmp/test.txt 作为附件的示例。
require 'net/smtp'
filename = "/tmp/test.txt"
# Read a file and encode it into base64 format
filecontent = File.read(filename)
encodedcontent = [filecontent].pack("m") # base64
marker = "AUNIQUEMARKER"
body = <<EOF
This is a test email to send an attachement.
EOF
# Define the main headers.
part1 = <<EOF
From: Private Person <me@fromdomain.net>
To: A Test User <test@todmain.com>
Subject: Sending Attachement
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary = #{marker}
--#{marker}
EOF
# Define the message action
part2 = <<EOF
Content-Type: text/plain
Content-Transfer-Encoding:8bit
#{body}
--#{marker}
EOF
# Define the attachment section
part3 = <<EOF
Content-Type: multipart/mixed; name = \"#{filename}\"
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename = "#{filename}"
#{encodedcontent}
--#{marker}--
EOF
mailtext = part1 + part2 + part3
# Let's put our code in safe area
begin
Net::SMTP.start('localhost') do |smtp|
smtp.sendmail(mailtext, 'me@fromdomain.net', ['test@todmain.com'])
end
rescue Exception => e
print "Exception occured: " + e
end
NOTE − 您可以在数组内部指定多个目标,但是它们应该以逗号分隔。
Ruby - Socket Programming
Ruby 提供了两个级别的网络服务访问权限。在低级别,您可以访问底层操作系统中的基本套接字支持,这使您可以为面向连接和无连接协议实现客户端和服务器。
Ruby 还具有提供对特定应用程序级网络协议(例如 FTP、HTTP 等)的高级访问权限的库。
本章将帮助您了解网络中最著名的概念 - 套接字编程。
What are Sockets?
套接字是双向通信通道的端点。套接字可以在进程内进行通信,也可以在同一电脑上的进程之间或不同区域中的进程之间进行通信。
套接字可以通过多种不同的信道类型进行实现:Unix 域套接字、TCP、UDP 等。套接字提供用于处理常见传输的特定类,以及用于处理其余部分的通用接口。
套接字具有自己的词汇:
Sr.No. |
Term & Description |
1 |
domain 将用作传输机制的协议族。这些值是常量,例如 PF_INET、PF_UNIX、PF_X25 等。 |
2 |
type 两个端点之间的通信类型,通常对于面向连接的协议使用 SOCK_STREAM,对于无连接的协议使用 SOCK_DGRAM。 |
3 |
protocol 通常为零,这可以在域和类型内标识协议的变体。 |
4 |
hostname 网络接口标识符 − 一个字符串,可以是主机名、点分十进制地址或冒号(可能还有点)表示法的 IPV6 地址一个指定 INADDR_BROADCAST 地址的字符串“<broadcast>”。一个空字符串,它指定 INADDR_ANY,或者一个整数,解释为主机字节顺序的二进制地址。 |
5 |
port 每个服务器监听客户端调用一个或多个端口。端口可以是 Fixnum 端口号、包含端口号的字符串或服务名。 |
A Simple Client
这里我们将编写一个非常简单的客户端程序,它将打开到给定端口和给定主机的连接。Ruby 类 TCPSocket 提供 open 函数来打开这样的套接字。
TCPSocket.open(hosname, port ) 在端口上向 hostname 打开一个 TCP 连接。
一旦您打开了一个套接字,就可以像任何 IO 对象一样从中读取。完成后,请记住关闭它,就像您关闭文件一样。
以下代码是一个非常简单的客户端,它连接到给定的主机和端口,从套接字读取任何可用数据,然后退出 −
require 'socket' # Sockets are in standard library
hostname = 'localhost'
port = 2000
s = TCPSocket.open(hostname, port)
while line = s.gets # Read lines from the socket
puts line.chop # And print with platform line terminator
end
s.close # Close the socket when done
A Simple Server
要编写 Internet 服务器,我们使用 TCPServer 类。TCPServer 对象是 TCPSocket 对象的工厂。
现在,调用 TCPServer.open(hostname, port 函数来指定服务的端口,并创建一个 TCPServer 对象。
接下来,调用返回的 TCPServer 对象的 accept 方法。此方法会一直等到客户端连接至您指定的端口,然后返回一个 TCPSocket 对象,该对象表示与该客户端的连接。
require 'socket' # Get sockets from stdlib
server = TCPServer.open(2000) # Socket to listen on port 2000
loop { # Servers run forever
client = server.accept # Wait for a client to connect
client.puts(Time.now.ctime) # Send the time to the client
client.puts "Closing the connection. Bye!"
client.close # Disconnect from the client
}
现在,在后台运行此服务器,然后运行上述客户端查看结果。
Multi-Client TCP Servers
互联网上的大多数服务器都设计为一次处理大量客户端。
Ruby 的 Thread 类简单创建多线程序。该程序可以接受请求,并立即创建一个新的执行线程来处理该连接,同时允许主程序等待更多连接−
require 'socket' # Get sockets from stdlib
server = TCPServer.open(2000) # Socket to listen on port 2000
loop { # Servers run forever
Thread.start(server.accept) do |client|
client.puts(Time.now.ctime) # Send the time to the client
client.puts "Closing the connection. Bye!"
client.close # Disconnect from the client
end
}
在此示例中,您有一个永久循环,当 server.accept 响应时,会创建一个新线程,并立即启动它来处理刚刚接受的连接,方法是用连接对象传递到该线程中。然而,主程序会立即循环返回等待新连接。
按照此方式使用 Ruby 线程意味着该代码是可移植的,并且可以在 Linux、OS X 和 Windows 上以相同方式运行。
A Tiny Web Browser
我们可以使用 socket 库来实现任何因特网协议。例如,下面是一个获取网页内容的代码−
require 'socket'
host = 'www.tutorialspoint.com' # The web server
port = 80 # Default HTTP port
path = "/index.htm" # The file we want
# This is the HTTP request we send to fetch a file
request = "GET #{path} HTTP/1.0\r\n\r\n"
socket = TCPSocket.open(host,port) # Connect to server
socket.print(request) # Send request
response = socket.read # Read complete response
# Split response at first blank line into headers and body
headers,body = response.split("\r\n\r\n", 2)
print body # And display it
要实现类似的 Web 客户端,您可以使用预先构建的库(例如 Net::HTTP )来使用 HTTP。下方的代码的作用等同于之前的代码−
require 'net/http' # The library we need
host = 'www.tutorialspoint.com' # The web server
path = '/index.htm' # The file we want
http = Net::HTTP.new(host) # Create a connection
headers, body = http.get(path) # Request the file
if headers.code == "200" # Check the status code
print body
else
puts "#{headers.code} #{headers.message}"
end
请查看类似的库以配合使用 FTP、SMTP、POP 和 IMAP 协议。
Further Readings
我们为您提供了有关套接字编程的快速入门指导。这是一个大课题,因此推荐您查看 Ruby Socket Library and Class Methods 以查找更多详细信息。
Ruby - XML, XSLT and XPath Tutorial
What is XML?
可扩展标记语言 (XML) 是一种类似 HTML 或 SGML 的标记语言。它由万维网联盟推荐,并作为一个开放标准提供。
XML 是一种便携式开源语言,允许程序员开发应用程序,无论操作系统和/或开发语言是什么,其他应用程序都可以读取这些应用程序。
XML 极其适用于追踪少量至中等数量的数据,而不需要基于 SQL 的后端。
XML Parser Architectures and APIs
对于 XML 解析器,有两种不同的类型 −
-
SAX-like (Stream interfaces) − 在此,你可以为你感兴趣的事件注册回调,然后让解析器继续处理文档。当你的文档很大,或者你有内存限制时,这很有用,它在从磁盘读取文件时对其进行解析,并且整个文件永远不会存储在内存中。
-
DOM-like (Object tree interfaces) − 这是万维网联盟的建议,其中整个文件都被读入内存中,并存储在分层(基于树)的形式中来表示 XML 文档的所有特性。
在处理大型文件时,SAX 显然无法像 DOM 那样快速处理信息。另一方面,专属使用 DOM 会真正耗尽你的资源,尤其是在大量的小型文件中使用时。
SAX 是只读的,而 DOM 允许对 XML 文件进行更改。由于这两个不同的 API 实际上是互补的,因此你没有理由不能将它们都用于大型项目。
Parsing and Creating XML using Ruby
处理 XML 最常见的方法是使用 Sean Russell 的 REXML 库。自 2002 年以来,REXML 已成为标准 Ruby 发行版的一部分。
REXML 是一个符合 XML 1.0 标准的纯 Ruby XML 处理器。它是一个无需验证的处理器,通过了所有 OASIS 无需验证的一致性测试。
REXML 解析器相对于其他可用解析器具有以下优势 −
-
它 100% 用 Ruby 编写。
-
它可以用于 SAX 和 DOM 解析。
-
它很轻量级,代码少于 2000 行。
-
方法和类非常容易理解。
-
基于 SAX2 的 API 以及全部支持 XPath。
-
与 Ruby 安装一起提供,不需要单独安装。
对于所有我们的 XML 代码示例,让我们使用一个简单的 XML 文件作为输入 −
<collection shelf = "New Arrivals">
<movie title = "Enemy Behind">
<type>War, Thriller</type>
<format>DVD</format>
<year>2003</year>
<rating>PG</rating>
<stars>10</stars>
<description>Talk about a US-Japan war</description>
</movie>
<movie title = "Transformers">
<type>Anime, Science Fiction</type>
<format>DVD</format>
<year>1989</year>
<rating>R</rating>
<stars>8</stars>
<description>A schientific fiction</description>
</movie>
<movie title = "Trigun">
<type>Anime, Action</type>
<format>DVD</format>
<episodes>4</episodes>
<rating>PG</rating>
<stars>10</stars>
<description>Vash the Stampede!</description>
</movie>
<movie title = "Ishtar">
<type>Comedy</type>
<format>VHS</format>
<rating>PG</rating>
<stars>2</stars>
<description>Viewable boredom</description>
</movie>
</collection>
DOM-like Parsing
让我们首先以树状的方式解析我们的 XML 数据。我们首先需要 rexml/document 库;通常,我们引入 REXML 以方便地导入到顶级命名空间。
#!/usr/bin/ruby -w
require 'rexml/document'
include REXML
xmlfile = File.new("movies.xml")
xmldoc = Document.new(xmlfile)
# Now get the root element
root = xmldoc.root
puts "Root element : " + root.attributes["shelf"]
# This will output all the movie titles.
xmldoc.elements.each("collection/movie"){
|e| puts "Movie Title : " + e.attributes["title"]
}
# This will output all the movie types.
xmldoc.elements.each("collection/movie/type") {
|e| puts "Movie Type : " + e.text
}
# This will output all the movie description.
xmldoc.elements.each("collection/movie/description") {
|e| puts "Movie Description : " + e.text
}
这会产生以下结果 −
Root element : New Arrivals
Movie Title : Enemy Behind
Movie Title : Transformers
Movie Title : Trigun
Movie Title : Ishtar
Movie Type : War, Thriller
Movie Type : Anime, Science Fiction
Movie Type : Anime, Action
Movie Type : Comedy
Movie Description : Talk about a US-Japan war
Movie Description : A schientific fiction
Movie Description : Vash the Stampede!
Movie Description : Viewable boredom
SAX-like Parsing
要以面向流的方式处理相同的数据,movies.xml,文件,我们将定义一个侦听器类,其方法将成为解析器回调的目标。
NOTE −不建议对小文件使用类似 SAX 的解析,这仅仅是一个演示示例。
#!/usr/bin/ruby -w
require 'rexml/document'
require 'rexml/streamlistener'
include REXML
class MyListener
include REXML::StreamListener
def tag_start(*args)
puts "tag_start: #{args.map {|x| x.inspect}.join(', ')}"
end
def text(data)
return if data =~ /^\w*$/ # whitespace only
abbrev = data[0..40] + (data.length > 40 ? "..." : "")
puts " text : #{abbrev.inspect}"
end
end
list = MyListener.new
xmlfile = File.new("movies.xml")
Document.parse_stream(xmlfile, list)
这会产生以下结果 −
tag_start: "collection", {"shelf"=>"New Arrivals"}
tag_start: "movie", {"title"=>"Enemy Behind"}
tag_start: "type", {}
text : "War, Thriller"
tag_start: "format", {}
tag_start: "year", {}
tag_start: "rating", {}
tag_start: "stars", {}
tag_start: "description", {}
text : "Talk about a US-Japan war"
tag_start: "movie", {"title"=>"Transformers"}
tag_start: "type", {}
text : "Anime, Science Fiction"
tag_start: "format", {}
tag_start: "year", {}
tag_start: "rating", {}
tag_start: "stars", {}
tag_start: "description", {}
text : "A schientific fiction"
tag_start: "movie", {"title"=>"Trigun"}
tag_start: "type", {}
text : "Anime, Action"
tag_start: "format", {}
tag_start: "episodes", {}
tag_start: "rating", {}
tag_start: "stars", {}
tag_start: "description", {}
text : "Vash the Stampede!"
tag_start: "movie", {"title"=>"Ishtar"}
tag_start: "type", {}
tag_start: "format", {}
tag_start: "rating", {}
tag_start: "stars", {}
tag_start: "description", {}
text : "Viewable boredom"
XPath and Ruby
查看 XML 的另一种方式是 XPath。这是一种描述如何在 XML 文档中找到特定元素和属性的伪语言,将该文档视为逻辑有序树。
REXML 通过 XPath 类支持 XPath。它假定基于树的解析(文档对象模型),如上所述。
#!/usr/bin/ruby -w
require 'rexml/document'
include REXML
xmlfile = File.new("movies.xml")
xmldoc = Document.new(xmlfile)
# Info for the first movie found
movie = XPath.first(xmldoc, "//movie")
p movie
# Print out all the movie types
XPath.each(xmldoc, "//type") { |e| puts e.text }
# Get an array of all of the movie formats.
names = XPath.match(xmldoc, "//format").map {|x| x.text }
p names
这会产生以下结果 −
<movie title = 'Enemy Behind'> ... </>
War, Thriller
Anime, Science Fiction
Anime, Action
Comedy
["DVD", "DVD", "DVD", "VHS"]
XSLT and Ruby
Ruby 可使用两种 XSLT 解析器。这里对每种解析器做了简要说明。
Ruby-Sablotron
该解析器由 Masayoshi Takahashi 编写和维护。这主要针对 Linux 操作系统编写,需要以下库 −
-
Sablot
-
Iconv
-
Expat
你可以在 Ruby-Sablotron 找到此模块。
XSLT4R
XSLT4R 由 Michael Neumann 编写,可在 XML 下的库部分中的 RAA 中找到。XSLT4R 使用一个简单的命令行界面,不过也可以在第三方应用程序中使用它来转换 XML 文档。
XSLT4R 需要 XMLScan 才能运行,该扫描器包含在 XSLT4R 归档文件中,并且也是一个 100% Ruby 模块。可以使用标准 Ruby 安装方法安装这些模块(即,ruby install.rb)。
XSLT4R 具有以下语法 −
ruby xslt.rb stylesheet.xsl document.xml [arguments]
如果你想在应用程序中使用 XSLT4R,你可以包括 XSLT 并输入所需的 parameters。以下为示例 −
require "xslt"
stylesheet = File.readlines("stylesheet.xsl").to_s
xml_doc = File.readlines("document.xml").to_s
arguments = { 'image_dir' => '/....' }
sheet = XSLT::Stylesheet.new( stylesheet, arguments )
# output to StdOut
sheet.apply( xml_doc )
# output to 'str'
str = ""
sheet.output = [ str ]
sheet.apply( xml_doc )
Further Reading
-
有关 REXML 解析器的完整详细信息,请参阅 REXML Parser Documentation 的标准文档。
-
你可以从 RAA Repository 下载 XSLT4R。
Web Services with Ruby - SOAP4R
What is SOAP?
简单对象访问协议(SOAP)是一个基于 XML,通常(但并非必须)基于 HTTP 的跨平台、与语言无关的 RPC 协议。
它使用 XML 对进行远程过程调用的信息进行编码,并使用 HTTP 通过网络将该信息从客户端传输到服务器,反之亦然。
与 COM、CORBA 等其他技术相比,SOAP 具有若干优势:例如,它的部署和调试成本相对较低、具有可扩展性和易用性,并且为不同的语言和平台存在多种实现。
请参阅我们的简单教程 SOAP 以详细了解它。
本小节让你熟悉 Ruby 的 SOAP 实现(SOAP4R)。这是一本基础教程,因此如果你需要更深入了解,你需要参考其他资源。
Installing SOAP4R
SOAP4R 是由 Hiroshi Nakamura 开发的 Ruby 的 SOAP 实现,可从以下位置下载:
NOTE − 已经安装此组件的可能性很大。
Download SOAP
如果您知道 gem 实用的功能,可以使用以下命令安装SOAP4R及其相关包。
$ gem install soap4r --include-dependencies
如果您在Windows上工作,则需要从上述位置下载一个zip文件,并且需要使用标准安装方法通过运行ruby install.rb来安装它。
Writing SOAP4R Servers
SOAP4R支持两种不同类型的服务器−
-
CGI/FastCGI based (SOAP::RPC::CGIStub)
-
Standalone (SOAP::RPC:StandaloneServer)
本章提供了有关编写独立服务器的详细信息。在编写SOAP服务器中涉及以下步骤。
Step 1 - Inherit SOAP::RPC::StandaloneServer Class
要实施您自己的独立服务器,您需要编写一个新类,该类将成为SOAP::StandaloneServer的子类,如下所示:−
class MyServer < SOAP::RPC::StandaloneServer
...............
end
NOTE -如果您想编写基于FastCGI的服务器,则需要将SOAP::RPC::CGIStub作为父类,其余过程将保持不变。
Step 2 - Define Handler Methods
第二步是编写您希望向外界公开的Web服务方法。
它们可以作为简单的Ruby方法编写。例如,我们编写两个方法,将两个数字相加并除以两个数字−
class MyServer < SOAP::RPC::StandaloneServer
...............
# Handler methods
def add(a, b)
return a + b
end
def div(a, b)
return a / b
end
end
Step 3 - Expose Handler Methods
下一步是将我们定义的方法添加到服务器中。初始化方法用于使用以下两种方法之一公开服务方法:−
class MyServer < SOAP::RPC::StandaloneServer
def initialize(*args)
add_method(receiver, methodName, *paramArg)
end
end
以下是参数说明 −
Sr.No. |
Parameter & Description |
1 |
receiver 包含methodName方法的对象。您在与methodDef方法相同的类中定义服务方法,此参数为self。 |
2 |
methodName 由于RPC请求而调用的方法的名称。 |
3 |
paramArg 指定(如果给定),则指定参数名称和参数模式。 |
要理解inout或out参数的用法,请考虑以下服务方法,该服务方法采用两个参数(inParam和inoutParam),返回一个普通返回值(retVal)以及两个其他参数:inoutParam和outParam−
def aMeth(inParam, inoutParam)
retVal = inParam + inoutParam
outParam = inParam . inoutParam
inoutParam = inParam * inoutParam
return retVal, inoutParam, outParam
end
现在,我们可以按以下方式公开此方法:−
add_method(self, 'aMeth', [
%w(in inParam),
%w(inout inoutParam),
%w(out outParam),
%w(retval return)
])
Step 4 - Start the Server
最后一步是通过实例化一个派生类的实例并调用 start 方法来启动服务器。
myServer = MyServer.new('ServerName', 'urn:ruby:ServiceName', hostname, port)
myServer.start
以下是所需参数的说明:−
Sr.No. |
Parameter & Description |
1 |
ServerName 服务器名称,您可以自由定制。 |
2 |
urn:ruby:ServiceName 此处urn:ruby是常量,但您可以为此服务器提供唯一的ServiceName名称。 |
3 |
hostname 指定此服务器要监听的主机名。 |
4 |
port 用于 Web 服务的可用端口号。 |
Example
现在,使用上述步骤,让我们编写一个独立服务器 −
require "soap/rpc/standaloneserver"
begin
class MyServer < SOAP::RPC::StandaloneServer
# Expose our services
def initialize(*args)
add_method(self, 'add', 'a', 'b')
add_method(self, 'div', 'a', 'b')
end
# Handler methods
def add(a, b)
return a + b
end
def div(a, b)
return a / b
end
end
server = MyServer.new("MyServer",
'urn:ruby:calculation', 'localhost', 8080)
trap('INT){
server.shutdown
}
server.start
rescue => err
puts err.message
end
在执行时,此服务器应用程序在 localhost 上启动一个独立 SOAP 服务器,并侦听端口 8080 上的请求。它公开了一种服务方法,即加法和除法,该方法接受两个参数并返回结果。
现在,您可以按如下方式在后台运行此服务器 −
$ ruby MyServer.rb&
Writing SOAP4R Clients
SOAP::RPC::Driver 类提供对编写 SOAP 客户端应用程序的支持。本章介绍此类,并根据一个应用程序演示其使用。
以下是调用 SOAP 服务所需的最基本信息 −
-
SOAP 服务的 URL(SOAP 端点 URL)。
-
服务方法的命名空间(方法命名空间 URI)。
-
服务方法及其参数的名称。
现在,我们将编写一个 SOAP 客户端,它将调用在上一个示例中定义的服务方法,即加法和除法。
以下是创建 SOAP 客户端的主要步骤。
Step 1 - Create a SOAP Driver Instance
我们通过调用 new 方法按照如下方式创建 SOAP::RPC::Driver 的一个实例 −
SOAP::RPC::Driver.new(endPoint, nameSpace, soapAction)
以下是所需参数的说明:−
Sr.No. |
Parameter & Description |
1 |
endPoint 要连接的 SOAP 服务器的 URL。 |
2 |
nameSpace 用于此 SOAP::RPC::Driver 对象完成的所有 RPC 的命名空间。 |
3 |
soapAction HTTP 标头的 SOAPAction 字段的一个值。如果为 nil,此值将默认为空字符串 ""。 |
Step 2 - Add Service Methods
若要向 SOAP::RPC::Driver 添加 SOAP 服务方法,我们可以使用 SOAP::RPC::Driver 实例调用如下方法 −
driver.add_method(name, *paramArg)
以下是参数说明 −
Sr.No. |
Parameter & Description |
1 |
name 远程 Web 服务方法的名称。 |
2 |
paramArg 指定远程过程的参数名称。 |
Step 3 - Invoke SOAP service
最后一步是使用 SOAP::RPC::Driver 实例调用 SOAP 服务,如下所示 −
result = driver.serviceMethod(paramArg...)
在其中,serviceMethod 为实际的 Web 服务方法,paramArg… 是需在服务方法中传递的参数列表。
Example
根据上述步骤,我们编写 SOAP 客户端,如下所示:
#!/usr/bin/ruby -w
require 'soap/rpc/driver'
NAMESPACE = 'urn:ruby:calculation'
URL = 'http://localhost:8080/'
begin
driver = SOAP::RPC::Driver.new(URL, NAMESPACE)
# Add remote sevice methods
driver.add_method('add', 'a', 'b')
# Call remote service methods
puts driver.add(20, 30)
rescue => err
puts err.message
end
Further Readings
我刚刚解释了 Web 服务与 Ruby 的基本概念。如果您希望进一步深入了解,可以访问以下链接,以查找更多有关 Web Services with Ruby 的详细信息。
Ruby - Tk Guide
Introduction
Ruby 的标准图形用户界面 (GUI) 为 Tk。Tk 起初是 John Ousterhout 开发的 Tcl 脚本语言的 GUI。
Tk 拥有独特的识别标记,它是唯一跨平台的 GUI。Tk 可以运行在 Windows、Mac 和 Linux 上,并在每个操作系统上都提供原生观感。
基于 Tk 的应用程序的基本组件被称为窗口小部件。该组件有时也称为窗口,因为在 Tk 中,“窗口”和“窗口小部件”经常可以互换使用。
Tk 应用程序遵循一个窗口小部件层次结构,其中可以在另一个窗口小部件内放置任意数量的窗口小部件,并且这些窗口小部件又可以在另一个窗口小部件内放置,依此类推。Tk 程序中的主窗口小部件被称为根窗口小部件,可以通过制作 TkRoot 类的一个新实例来创建它。
-
大多数基于 Tk 的应用程序都遵循相同的循环:创建窗口小部件,将它们放入该界面,最后将与每个窗口小部件关联的事件绑定到一个方法上。
-
有三个几何管理器:place、grid 和 pack,这些管理器负责控制界面中每个窗口小部件的大小和位置。
Installation
Ruby Tk 绑定与 Ruby 一起分发,但 Tk 是一个单独的安装程序。Windows 用户可以从 ActiveState’s ActiveTcl 下载一个单次点击的 Tk 安装程序。
Mac 和 Linux 用户可能不需要安装它,因为存在很大几率 Tk 已经与操作系统一同安装了,但如果没有安装,你可以从 Tcl Developer Xchange 下载预构建软件包或获取源代码。
Simple Tk Application
Ruby/Tk 程序有一个典型的结构,即创建主窗口或 root 窗口(TkRoot 的一个实例),向其中添加窗口小部件以构建用户界面,然后通过调用 Tk.mainloop 启动主事件循环。
面向 Ruby/Tk 的传统 Hello, World! 示例看起来像这样 −
require 'tk'
root = TkRoot.new { title "Hello, World!" }
TkLabel.new(root) do
text 'Hello, World!'
pack { padx 15 ; pady 15; side 'left' }
end
Tk.mainloop
在这里,在加载 tk 扩展模块之后,我们使用 TkRoot.new 创建了一个根级别框架。然后我们制作了一个 TkLabel 窗口小部件作为根框架的一个子级,同时为该标签设置几个选项。最后,我们打包根框架并进入主 GUI 事件循环。
如果你运行这个脚本,将会产生以下结果 −
Ruby/Tk Widget Classes
有一系列 Ruby/Tk 类,它们可用于使用 Ruby/Tk 创建所需 GUI。
-
TkFrame 创建并操纵框架窗口小部件。
-
TkButton 创建并操纵按钮窗口小部件。
-
TkLabel 创建并操纵标签窗口小部件。
-
TkEntry 创建并操纵输入窗口小部件。
-
TkCheckButton 创建和操作复选框小工具。
-
TkRadioButton 创建和操作单选按钮小工具。
-
TkListbox 创建和操作列表框小工具。
-
TkComboBox 创建和操作列表框小工具。
-
TkMenu 创建和操作菜单小工具。
-
TkMenubutton 创建和操作菜单按钮小工具。
-
Tk.messageBox 创建和操作消息对话框。
-
TkScrollbar 创建和操作滚动条小工具。
-
TkCanvas 创建和操作画布小工具。
-
TkScale 创建和操作缩放小工具。
-
TkText 创建和操作文本小工具。
-
TkToplevel 创建和操作顶层小工具。
-
TkSpinbox 创建和操作 Spinbox 小工具。
-
TkProgressBar 创建和操作进度条小工具。
-
Dialog Box 创建和操作对话框小工具。
-
Tk::Tile::Notebook 在有限空间内使用笔记本元比喻显示多个窗口。
-
Tk::Tile::Paned 显示多个子窗口,垂直或水平堆叠。
-
Tk::Tile::Separator 显示水平或垂直分隔条。
-
Ruby/Tk Font, Colors and Images 了解 Ruby/Tk 字体、颜色和图像
Standard Configuration Options
所有控件都具有若干不同的配置选项,这些选项通常用于控制它们的显示方式或行为方式。有哪些可用选项当然取决于控件类。
以下列出了所有标准配置选项,这些选项可适用于任何 Ruby/Tk 控件。
Ruby/Tk Geometry Management
几何管理处理根据要求对不同控件进行定位。Tk 中的几何管理依赖于主控件和从控件的概念。
主控件是一个控件,通常是顶级窗口或框架,它将包含其它控件,即从控件。你可以认为几何管理器控制了主控件,并决定在其中显示什么。
几何管理器将询问每个从控件的自然大小,或理想的显示大小。然后,它获取该信息,并将其与程序在要求几何管理器管理该特定从控件时提供的任何参数结合在一起。
有三个几何管理器,即 place、grid 和 pack,它们负责控制接口中每个控件的大小和位置。
Ruby/Tk Event Handling
Ruby/Tk 支持事件循环,它会从操作系统接收事件。这些事件包括按钮按下、击键、鼠标移动、窗口大小调整等。
Ruby/Tk 可用于管理此事件循环。它会弄清楚该事件适用于哪个控件(用户是否单击了此按钮?如果按下了键,哪个文本框具有焦点?),并相应地分派事件。单个控件知道如何响应事件,因此例如,当鼠标移动到按钮上时,按钮可能会变色,而在鼠标离开时恢复原色。
在更高的层面上,Ruby/Tk 会在你的程序中调用回调,以表明控件发生了重大事件。对于这两种情况,你可以提供一个代码块或一个 Ruby Proc 对象,以指定应用程序如何响应该事件或回调。
让我们了解如何使用 bind 方法将基本窗口系统事件与处理它们的 Ruby 过程关联起来。bind 的最简单形式使其输入一个指示事件名称的字符串和一个 Tk 用于处理该事件的代码块。
例如,要捕获控件上的第一个鼠标按钮的 ButtonRelease 事件,你可以编写 −
someWidget.bind('ButtonRelease-1') {
....code block to handle this event...
}
事件名称可以包括其他修饰符和详细信息。修饰符是一个字符串,如 Shift、Control 或 Alt,指示按下了其中一个修饰键。
因此,例如,要捕获在用户按住 Ctrl 键并单击鼠标右键时生成的事件。
someWidget.bind('Control-ButtonPress-3', proc { puts "Ouch!" })
当用户激活它们时,许多 Ruby/Tk 控件都会触发回调,并且你可以使用 command 回调指定在这种情况下调用某个代码块或过程。如前所述,你可以在创建控件时指定 command 回调过程 −
helpButton = TkButton.new(buttonFrame) {
text "Help"
command proc { showHelp }
}
或者,你可以在稍后使用控件的 command 方法分配它 −
helpButton.command proc { showHelp }
由于 command 方法接受过程或代码块,因此你还可以编写上一段代码示例,如下所示 −
helpButton = TkButton.new(buttonFrame) {
text "Help"
command { showHelp }
}
The configure Method
配置方法可用于设置和检索任何小组件配置值。例如,如需更改按钮的宽度,你可以随时按如下方式调用配置方法:
require "tk"
button = TkButton.new {
text 'Hello World!'
pack
}
button.configure('activebackground', 'blue')
Tk.mainloop
如需获取当前小组件的值,只需按如下方式提供该值,而无需提供实际值:
color = button.configure('activebackground')
你还可以不提供任何选项来调用配置,这样便会提供所有选项及其值的列表。
Ruby - LDAP Tutorial
Ruby/LDAP 是 Ruby 的一个扩展库。它提供了一些 LDAP 库的接口,如 OpenLDAP、UMich LDAP、Netscape SDK、ActiveDirectory。
应用程序开发的通用 API 在 RFC1823 中描述,并由 Ruby/LDAP 支持。
Ruby/LDAP Installation
您可以从 SOURCEFORGE.NET 下载并安装完整的 Ruby/LDAP 程序包。
在安装 Ruby/LDAP 之前,请确保您具有以下组件:
-
Ruby 1.8.x(如果您想使用 ldap/control,则至少为 1.8.2)。
-
OpenLDAP、Netscape SDK、Windows 2003 或 Windows XP。
现在,您可以使用标准 Ruby 安装方法。在开始之前,如果您想查看 extconf.rb 的可用选项,请使用“--help”选项运行它。
$ ruby extconf.rb [--with-openldap1|--with-openldap2| \
--with-netscape|--with-wldap32]
$ make
$ make install
NOTE − 如果您在 Windows 上构建软件,您可能需要使用 nmake 而不是 make。
Establish LDAP Connection
这是一个两步过程:
Step 1 − Create Connection Object
以下是创建与 LDAP 目录连接的语法。
LDAP::Conn.new(host = 'localhost', port = LDAP_PORT)
-
host − 这是运行 LDAP 目录的主机 ID。我们将视其为本地主机。
-
port − 这是用于 LDAP 服务的端口。标准 LDAP 端口为 636 和 389。请确保使用的是服务器上的哪个端口,否则可以使用 LDAP::LDAP_PORT。
此调用将返回与服务器、主机在端口 port 上的新 LDAP::Conn 连接。
Step 2 − Binding
这里是我们通常指定将在会话剩余部分中使用的用户名和密码。
以下是绑定一个 LDAP 连接的语法,使用 DN, dn ,凭据, pwd ,和绑定方法, method −
conn.bind(dn = nil, password = nil, method = LDAP::LDAP_AUTH_SIMPLE)do
....
end
您可以使用相同的方法而没有代码块。在这种情况下,您需要如下显式解绑连接 −
conn.bind(dn = nil, password = nil, method = LDAP::LDAP_AUTH_SIMPLE)
....
conn.unbind
如果提供了代码块,则 self 被传递给该块。
我们现在可以在 bind 方法的块(bind 和 unbind 之间)中执行搜索、添加、修改或删除操作,前提是我们有适当的权限。
Example
假设我们正在本地服务器上工作,让我们将适当的主机、域、用户 ID 和密码等组合在一起。
#/usr/bin/ruby -w
require 'ldap'
$HOST = 'localhost'
$PORT = LDAP::LDAP_PORT
$SSLPORT = LDAP::LDAPS_PORT
conn = LDAP::Conn.new($HOST, $PORT)
conn.bind('cn = root, dc = localhost, dc = localdomain','secret')
....
conn.unbind
Adding an LDAP Entry
添加 LDPA 条目是一个两步过程 −
Step 1 − Creating LDAP::Mod object
我们需要将 LDAP::Mod 对象传递给 conn.add 方法以创建条目。以下是创建 LDAP::Mod 对象的简单语法 −
Mod.new(mod_type, attr, vals)
-
mod_type − 一个或多个选项 LDAP_MOD_ADD、LDAP_MOD_REPLACE 或 LDAP_MOD_DELETE。
-
attr − 是要对其进行操作的属性的名称。
-
vals − 是与 attr 相关的值的数组。如果 vals 包含二进制数据,应使用 LDAP_MOD_BVALUES 逻辑 OR’ed (|) 修饰 mod_type。
此调用返回 LDAP::Mod 对象,可以将其传递给 LDAP::Conn 类中的方法,例如 Conn#add、Conn#add_ext、Conn#modify 和 Conn#modify_ext。
Step 2 − Calling conn.add Method
一旦我们准备好 LDAP::Mod 对象,就可以调用 conn.add 方法来创建条目。下面是调用此方法的语法 −
conn.add(dn, attrs)
此方法添加了一个条目,带有 DN、dn 和属性 attrs。此处,attrs 应是 LDAP::Mod 对象的数组或属性/值数组对的哈希。
Example
下面是一个完整示例,它将创建两个目录条目 −
#/usr/bin/ruby -w
require 'ldap'
$HOST = 'localhost'
$PORT = LDAP::LDAP_PORT
$SSLPORT = LDAP::LDAPS_PORT
conn = LDAP::Conn.new($HOST, $PORT)
conn.bind('cn = root, dc = localhost, dc = localdomain','secret')
conn.perror("bind")
entry1 = [
LDAP.mod(LDAP::LDAP_MOD_ADD,'objectclass',['top','domain']),
LDAP.mod(LDAP::LDAP_MOD_ADD,'o',['TTSKY.NET']),
LDAP.mod(LDAP::LDAP_MOD_ADD,'dc',['localhost']),
]
entry2 = [
LDAP.mod(LDAP::LDAP_MOD_ADD,'objectclass',['top','person']),
LDAP.mod(LDAP::LDAP_MOD_ADD, 'cn', ['Zara Ali']),
LDAP.mod(LDAP::LDAP_MOD_ADD | LDAP::LDAP_MOD_BVALUES, 'sn',
['ttate','ALI', "zero\000zero"]),
]
begin
conn.add("dc = localhost, dc = localdomain", entry1)
conn.add("cn = Zara Ali, dc = localhost, dc = localdomain", entry2)
rescue LDAP::ResultError
conn.perror("add")
exit
end
conn.perror("add")
conn.unbind
Modifying an LDAP Entry
修改条目类似于添加条目。只需使用属性调用 modify 方法来修改,而不是用 add。下面是 modify 方法的简单语法。
conn.modify(dn, mods)
此方法使用 DN、dn 和属性 mods 修改条目。此处,mods 应是 LDAP::Mod 对象的数组或属性/值数组对的哈希。
Example
为了修改上一节中添加的条目的姓氏,我们可以编写 −
#/usr/bin/ruby -w
require 'ldap'
$HOST = 'localhost'
$PORT = LDAP::LDAP_PORT
$SSLPORT = LDAP::LDAPS_PORT
conn = LDAP::Conn.new($HOST, $PORT)
conn.bind('cn = root, dc = localhost, dc = localdomain','secret')
conn.perror("bind")
entry1 = [
LDAP.mod(LDAP::LDAP_MOD_REPLACE, 'sn', ['Mohtashim']),
]
begin
conn.modify("cn = Zara Ali, dc = localhost, dc = localdomain", entry1)
rescue LDAP::ResultError
conn.perror("modify")
exit
end
conn.perror("modify")
conn.unbind
Deleting an LDAP Entry
要删除条目,请使用 distinguished name 作为参数来调用 delete 方法。下面是 delete 方法的简单语法。
conn.delete(dn)
此方法使用 DN、dn 删除条目。
Example
要删除上一节中添加的 Zara Mohtashim 条目,我们可以编写 −
#/usr/bin/ruby -w
require 'ldap'
$HOST = 'localhost'
$PORT = LDAP::LDAP_PORT
$SSLPORT = LDAP::LDAPS_PORT
conn = LDAP::Conn.new($HOST, $PORT)
conn.bind('cn = root, dc = localhost, dc = localdomain','secret')
conn.perror("bind")
begin
conn.delete("cn = Zara-Mohtashim, dc = localhost, dc = localdomain")
rescue LDAP::ResultError
conn.perror("delete")
exit
end
conn.perror("delete")
conn.unbind
Modifying the Distinguished Name
无法使用 modify 方法修改条目的 distinguished name。可以使用 modrdn 方法来代替。以下是 modrdn 方法的简单语法 −
conn.modrdn(dn, new_rdn, delete_old_rdn)
此方法使用 DN、dn 修改条目的 RDN,为其提供新的 RDN、new_rdn。如果 delete_old_rdn 为 true,则旧的 RDN 值将从条目中删除。
Example
假设我们有以下条目 −
dn: cn = Zara Ali,dc = localhost,dc = localdomain
cn: Zara Ali
sn: Ali
objectclass: person
然后,我们可以使用以下代码修改它的 distinguished name −
#/usr/bin/ruby -w
require 'ldap'
$HOST = 'localhost'
$PORT = LDAP::LDAP_PORT
$SSLPORT = LDAP::LDAPS_PORT
conn = LDAP::Conn.new($HOST, $PORT)
conn.bind('cn = root, dc = localhost, dc = localdomain','secret')
conn.perror("bind")
begin
conn.modrdn("cn = Zara Ali, dc = localhost, dc = localdomain", "cn = Zara Mohtashim", true)
rescue LDAP::ResultError
conn.perror("modrdn")
exit
end
conn.perror("modrdn")
conn.unbind
Performing a Search
要在 LDAP 目录中执行搜索,请使用包含三种不同搜索模式之一的 search 方法 −
-
LDAP_SCOPE_BASEM − 仅搜索基本节点。
-
LDAP_SCOPE_ONELEVEL - 搜索根节点的所有子项。
-
LDAP_SCOPE_SUBTREE - 搜索包括根节点的整个子树。
Example
在这里,我们将搜索 cn = localhost,cn = localdomain 项的整个子树以查找人员对象 -
#/usr/bin/ruby -w
require 'ldap'
$HOST = 'localhost'
$PORT = LDAP::LDAP_PORT
$SSLPORT = LDAP::LDAPS_PORT
base = 'dc = localhost,dc = localdomain'
scope = LDAP::LDAP_SCOPE_SUBTREE
filter = '(objectclass = person)'
attrs = ['sn', 'cn']
conn = LDAP::Conn.new($HOST, $PORT)
conn.bind('cn = root, dc = localhost, dc = localdomain','secret')
conn.perror("bind")
begin
conn.search(base, scope, filter, attrs) { |entry|
# print distinguished name
p entry.dn
# print all attribute names
p entry.attrs
# print values of attribute 'sn'
p entry.vals('sn')
# print entry as Hash
p entry.to_hash
}
rescue LDAP::ResultError
conn.perror("search")
exit
end
conn.perror("search")
conn.unbind
这会对每个匹配项调用提供的代码块,其中 LDAP 项由 LDAP::Entry 类的实例表示。使用 search 的最后一个参数,您可以指定感兴趣的属性,省略所有其他属性。如果您在这里传递 nil,则会返回所有属性,与关系数据库中的“SELECT *”相同。
LDAP::Entry 类的 dn 方法(get_dn 的别名)返回项的专有名称,使用 to_hash 方法,您可以获得其属性的哈希表示形式(包括专有名称)。若要获取项属性的列表,请使用 attrs 方法(get_attributes 的别名)。此外,要获取一个特定属性值的列表,请使用 vals 方法(get_values 的别名)。
Handling Errors
Ruby/LDAP 定义了两种不同的异常类 -
-
如果发生错误,则 new、bind 或 unbind 方法会引发 LDAP::Error 异常。
-
如果添加、修改、删除或搜索 LDAP 目录会引发 LDAP::ResultError。
Further Reading
有关 LDAP 方法的完整详细信息,请参阅 LDAP Documentation 的标准文档。
Ruby - Multithreading
传统程序有一个单一执行线程,直到程序终止之前,组成程序的语句或指令将按顺序执行。
多线程程序有多个执行线程。在每个线程中,语句按顺序执行,但是这些线程本身可以并行执行,例如在多核 CPU 上。通常在单 CPU 计算机中,多个线程实际上并不会并行执行,但是可以通过交错执行线程来模拟并行性。
利用 Thread 类,Ruby 非常便于编写多线程程序。Ruby 线程是一种轻量且高效的方式,在您的代码中实现并发。
Creating Ruby Threads
若要启动一个新线程,只需将一个块与对 Thread.new 的调用关联即可。将创建一个新线程来执行块中代码,原始线程会立即从 Thread.new 返回,并使用下一条语句恢复执行 −
# Thread #1 is running here
Thread.new {
# Thread #2 runs this code
}
# Thread #1 runs this code
Example
这是一个示例,演示如何使用多线程 Ruby 程序。
#!/usr/bin/ruby
def func1
i = 0
while i<=2
puts "func1 at: #{Time.now}"
sleep(2)
i = i+1
end
end
def func2
j = 0
while j<=2
puts "func2 at: #{Time.now}"
sleep(1)
j = j+1
end
end
puts "Started At #{Time.now}"
t1 = Thread.new{func1()}
t2 = Thread.new{func2()}
t1.join
t2.join
puts "End at #{Time.now}"
这将生成以下结果:
Started At Wed May 14 08:21:54 -0700 2008
func1 at: Wed May 14 08:21:54 -0700 2008
func2 at: Wed May 14 08:21:54 -0700 2008
func2 at: Wed May 14 08:21:55 -0700 2008
func1 at: Wed May 14 08:21:56 -0700 2008
func2 at: Wed May 14 08:21:56 -0700 2008
func1 at: Wed May 14 08:21:58 -0700 2008
End at Wed May 14 08:22:00 -0700 2008
Thread Lifecycle
使用 Thread.new 创建新的线程。您还可以使用同义词 Thread.start 和 Thread.fork。
在创建线程后无需启动该线程,当 CPU 资源可用时,它将自动开始运行。
Thread 类定义了一些方法来查询和操作正在运行的线程。线程会运行与 Thread.new 的调用关联的块中的代码,然后停止运行。
该块中最后一个表达式的值为线程值,可以通过调用 Thread 对象的 value 方法获取该值。如果线程已经运行完成,则 value 会立即返回线程值。否则,value 方法会阻塞,在线程完成之前不会返回。
类方法 Thread.current 返回表示当前线程的 Thread 对象。这样,线程可以对自己进行操作。类方法 Thread.main 返回表示主线程的 Thread 对象。这是在启动 Ruby 程序时开始运行的初始执行线程。
您可以通过调用线程的 Thread.join 方法来等待特定线程完成。调用线程将阻塞,直到给定线程完成。
Threads and Exceptions
如果在主线程中引发了一个异常,并且在任何地方都没有处理该异常,则 Ruby 解释器将打印一条消息并退出。在主线程之外的线程中,未处理的异常会导致该线程停止运行。
如果一个线程由于未处理的异常而 t 退出,而另一个线程 s 调用 t.join 或 t.value,那么 t 中发生的异常将在线程 s 中引发。
如果 Thread.abort_on_exception 为 false,即默认条件,一个未处理的异常会简单地终止当前线程,而其他所有线程继续运行。
如果您希望任何线程中的任何未处理异常导致解释器退出,请将类方法 Thread.abort_on_exception 设置为 true。
t = Thread.new { ... }
t.abort_on_exception = true
Thread Variables
当创建一个线程时,该线程通常可以访问范围内的任何变量。一个线程块中的局部变量属于该线程,并且它们不受共享。
Thread 类提供一个特殊功能,它允许按名称创建和访问局部变量。您可以简单地将线程对象视为散列表,使用 []= 来写入元素,并使用 [] 来读取元素。
在此示例中,每个线程都会使用键 mycount 在一个局部变量中记录变量 count 的当前值。
#!/usr/bin/ruby
count = 0
arr = []
10.times do |i|
arr[i] = Thread.new {
sleep(rand(0)/10.0)
Thread.current["mycount"] = count
count += 1
}
end
arr.each {|t| t.join; print t["mycount"], ", " }
puts "count = #{count}"
这会产生以下结果:
8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10
主线程等待子线程执行完成,然后打印出每个线程捕获的 count 值。
Thread Priorities
影响线程调度的第一个因素是线程优先级:高优先级的线程会先于低优先级的线程被调度。更确切地说,当没有更高优先级的线程等待运行时,线程才能获得 CPU 时间。
可以使用 priority = 和 priority 设置和查询 Ruby Thread 对象的优先级。新创建的线程的优先级与创建它的线程的优先级相同。主线程的初始优先级为 0。
无法在开始运行之前设置线程的优先级。然而,线程可以在其执行的第一个操作中提升或降低其自身的优先级。
Thread Exclusion
如果两个线程共享对相同数据的访问权,并且其中至少有一个线程修改了该数据,则你必须特别注意以确保没有线程可能会看到不一致状态下的数据。这称为线程互斥。
Mutex 是一个类,它实现了针对某个共享资源进行互斥访问的简单信号量锁。也就是说,在给定的时间,只有一个线程可能持有该锁。其他线程可以选择在队列中等待锁可用,或者仅仅选择立即获取一个指示锁不可用的错误。
通过将对共享数据的全部访问权限置于互斥锁的控制之下,我们可以确保一致性和原子操作。我们尝试两个示例,第一个是没有互斥锁的,第二个是有互斥锁的 −
Example without Mutax
#!/usr/bin/ruby
require 'thread'
count1 = count2 = 0
difference = 0
counter = Thread.new do
loop do
count1 += 1
count2 += 1
end
end
spy = Thread.new do
loop do
difference += (count1 - count2).abs
end
end
sleep 1
puts "count1 : #{count1}"
puts "count2 : #{count2}"
puts "difference : #{difference}"
这会产生以下结果 −
count1 : 1583766
count2 : 1583766
difference : 0
#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new
count1 = count2 = 0
difference = 0
counter = Thread.new do
loop do
mutex.synchronize do
count1 += 1
count2 += 1
end
end
end
spy = Thread.new do
loop do
mutex.synchronize do
difference += (count1 - count2).abs
end
end
end
sleep 1
mutex.lock
puts "count1 : #{count1}"
puts "count2 : #{count2}"
puts "difference : #{difference}"
这会产生以下结果 −
count1 : 696591
count2 : 696591
difference : 0
Handling Deadlock
当我们开始使用 Mutex 对象进行线程互斥时,我们必须小心避免死锁。当所有线程都在等待获得另一个线程持有的资源时,就会发生死锁。由于所有线程都处于阻塞状态,因此它们无法释放所持有的锁。并且由于它们无法释放这些锁,因此没有其他线程可以获得这些锁。
这是条件变量发挥作用的地方。条件变量只是一个与资源关联并用于保护特定互斥锁的信号量。当你需要不可用的资源时,你会等待条件变量。该操作将释放对相应互斥锁的锁。当其他一些线程发出资源可用信号时,原始线程会退出等待并在临界区域同时重新获得锁。
Example
#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new
cv = ConditionVariable.new
a = Thread.new {
mutex.synchronize {
puts "A: I have critical section, but will wait for cv"
cv.wait(mutex)
puts "A: I have critical section again! I rule!"
}
}
puts "(Later, back at the ranch...)"
b = Thread.new {
mutex.synchronize {
puts "B: Now I am critical, but am done with cv"
cv.signal
puts "B: I am still critical, finishing up"
}
}
a.join
b.join
这会产生以下结果 −
A: I have critical section, but will wait for cv
(Later, back at the ranch...)
B: Now I am critical, but am done with cv
B: I am still critical, finishing up
A: I have critical section again! I rule!
Thread States
有 5 个可能的返回值与下表所示的 5 种可能的状态相对应。status 方法返回线程的状态。
Thread state |
Return value |
Runnable |
run |
Sleeping |
Sleeping |
Aborting |
aborting |
Terminated normally |
false |
Terminated with exception |
nil |