用Ruby on Rails来编写数据库管理系统是非常快速的。Ruby on Rails之所以有如此高的生产率,不光是Ruby的语法灵活,而这一切在很大程序上是拜活动记录(Active Record)所赐。那么什么是活动记录呢?如果用一句话来解释,那就是一个可以将数据映射成对象的框架(这有些类似于Hibernate)。也许你会 说:"这有什么,现在这种框架多得是,Hibernate不是也非常强大吗?",但如果你用了Ruby on Rails(以下简称为RoR),你就会发现它和其它的同类框架是多么的不同,它比其它框架更容易使用,编程效率也更高,据官方说,使用RoR比使用 Java和Hibernate的生产率高10倍(也许并没有这么夸张,但活动记录的数据映射的确比其它框架的数据映射的生产率高一些)。
无论是桌面程序还是基于Web的程序,数据处理始终是它们的核心。数据可以使用不同的技术进行描述和处理。
这些数据处理技术之一就是对象关系映射技术,或者简称为ORM技术。ORM技术将数据库中的数据表或视图映射成了面象对象的类。而ORM框架的作用就是 架起数据和对象之间的桥梁,以及为桥梁两端的数据和对象提供相应的服务。由于ORM框架自成一体,因此,开发人员的主要工作就是将ORM框架和其它的框架 (如web框架)连接起来,从而使它们可以协同工作。
在ORM框架家族中,RoR很特别。RoR的核心是ORM框架,而这个框架在 RoR中是以活动记录的形式表现的。在本文中,我们将关注活动记录的一些基本概念和她最诱人的地方:快速连接和操作数据库。本文的第一部分将讨论活动记录 的基础知识。第二部分将一步步地指导读者如何使用活动记录快速地连接和操作数据库。在第三部分中我们以一个完整的例子来讨论如何将活动记录和 ActionController以及ActionView一起使用。以上是本文所讨论的主要内容。
什么是活动记录
活动记录是一个ORM框架,也可以看做是和RoR一起发布的一个数据库映射层。活动记录是RoR的核心。活动记录是一个ORM层,它提供了以下的映射服务:
1. 将表映射成类
2. 将字段映射成类的属性
3. 将表中的主键映射成Ids属性
4. 将行映射成对象
活动记录和其它的ORM框架最大的区别是它们的映射方式不同。大多数流行的ORM框架(如Hibernate)是以XML作为映射容器。而活动记录采用的是更容易使用的"约定"方式进行映射。下面让我们来看看活动记录是如何做的。
将表映射成类
为了将表映射成类,Ruby类必须从ActiveRecord::Base继承。这个Base类在ActiveRecord包中。那么一个类从 ActiveRecord::Base继承后发生了什么呢?实际上这个子类已经和一个数据表相对应了。也许大家看到这有一些迷糊,我除了写一个从 ActiveRecord::Base继承的空类什么代码都没写,怎么就映射完了。其实这要依靠活动记录的命名约定。活动记录假设一个类名的复数形式(英 文类名)就是表名。如果类名中有多个大写字母,那么活动记录就假设表名就是这些单词中间用下划线隔开后连在一起,请看下面的例子。
类名 | 表名 |
Record | Records |
LineItem | Line_Items |
Datum | Data |
以上所描述的是活动记录的默认的映射行为,我们也可以为活动记录定义自已的行为。要改变这些默认的行为,可以按以下步骤做:
1.将全局变量ActiveRecord::Base. pluralize_table_names设为false。这个变量被定义在config目录中的environment.rb中。
2.可以通过set_table_name改变默认的表名。例如,如果将表Orders映射成类Order的代码如下:
class Order < ActiveRecord::Base end |
但如果这个类名要映射成MyOrder,那么它的实现代码如下:
class Order <ActiveRecord::Base set_table_name "MyOrder" end |
接下来让我们看看如何将字段映射成属性。
将字段转换为属性
在一个数据表被映射成类后,表中的字段就将自动映射成类的属性。这是因为活动记录是在程序运行时动态地将表中的字段映射成类的属性。事实上,在从 ActiveRecord::Base的类自动将表中的字段包装在了类中。下表将描述SQL数据类型将被映射成Ruby的哪些数据类型:
SQL数据类型 | Ruby数据类型(类) |
int, integer | Fixnum |
decimal, numeric | Float |
interval, date | Date |
clob, blob, text | String |
float, double | float |
chat, varchar, string | String |
datetime, time | Time |
下面让我们来讨论主键的映射。
将主键映射成Ids
假设有一个Orders表,它的主键是Order_Id。现在让我们看看这个Order_Id字段,这个字段由16个数字组成,其中包括Item id,User Id等。在以后如果Order_id变成20位数字,最后4位数字是一个RFID代码,那么所有依赖这个Order_id字段的列都将发生变化。这些工作 量是非常大的,即使这个应用程序并不大。然而活动记录确能自动做到这些。自动映射可按如下代码去做:
class Order < ActiveRecord::Base set_primary_key "orderId" end |
将记录映射成对象
无论什么时候,映射类的查询方法被执行时,在内部都会调用相应的SQL语句来查询数据库,并且将查询结果返回,这些返回的结果最终以对象的形式表现。列值将成为对象的属性。返回的每一行将被映射成一个对象。例如以下代码将返回Id等于100的Order对象:
an_order = Order.find(100) |
上面的代码演示了如何得到特定的记录。接下来让我们看一下使用活动记录建立相应的操作的步骤。
使用活动记录的步骤
虽然使用活动记录映射数据表不需要复杂的设置,但我们必须按着活动记录所制定的规范进行操作。这些规范将成为活动记录工作的一部分,以下就是使用活动记录的步骤。
1.建立表
2.连接数据库
3.建立ORM
4.进行增、删、改操作
第一步需要完全按着规范来做。建立表的过程一定要严格按着规范进行,否则就会覆盖活动记录的默认值。以下是建立表的详细过程:
·建立表
如果你不想在映射时做大量的工作,那么数据表必须按着活动记录所制定的规范来建立。过程如下:
1.表名应该是复制形式(如orders)。
2.主键应该被命名为Id,它的数据类型应该是整型。
如果某个表引用了另外一个表,那么这个表的外键应该按如下格式取名:
<表名的单数形式>_id
因此,按着上面两条规范建立orders表的SQL语句(MySQL数据库)如下:
create table orders ( id int not null auto_increment, name varchar(50) not null, … … primary key (id) ); |
·连接数据库
就象RoR的其它操作一样,数据库的连接也是非常快速的。这就意味着活动记录在内部做了很多的工作,如自动侦测特定的数据库适配器的细节。为了连接数据 库,必须使用相应的连接参数调用Base类的establish_connection()方法。这些参数根据数据库的不同而不同。下面的语句是使用 establish_connection通过用户名、密码、数据库等信息连接本机MySQL数据库,其中"encoding =>gb2312"是为了操作中文所需。
establish_connection( :adapter => "mysql", :host => "localhost", :username => "root", :encoding => "gb2312", :password => "1234", :database => "test" ) |
·建立ORM
在建立完数据库和表后,下一步就是将这个数据表映射成Ruby的类。我们从上述可知,RoR映射数据表是非常快的。只要一个类从 ActiveRecord::Base继承,数据表就自动映射成了Ruby类(类名为表名的单数形式)。下面是将orders表映射成Order类的代 码:
class Order < ActiveRecord::Base … … end |
·进行增、删、改操作
对一个数据表最常用的三种操作就是增、删、改。这也是一个数据库应用程序的其本的操作。使用活动记录对数据表进行这三种操作是非常容易的。下面的代码将描述如何使用活动记录对数据进行这三种操作。
·增加记录
增加记录是在建立一个空表后应该做的第一件事。在活动记录中为我们建立了new方法来向表中增加记录,并通过update_attributes方法保存新增加的记录。代码如下:
order = Order.new order.name = "computer" order.update_attributes(params[:order]) |
·删除记录
为了删除记录,活动记录提供了delete方法。这个方法支持单行删除,也支持多行删除。以下语句删除了id等于12的记录:
Order.delete(12) |
下面的语句删除了多条记录:
Order.delete([3, 54, 100]) |
·修改记录
在修改记录之前,首先要确定要修改的记录,这一过程一般使用查询来完成。因此,修改就是查询和更新记录的组合。如下面的语句将id=100的记录中的name的值改为"car"。
order = Order.find(100) order.name = "car" order.save |
上面的代码只是粗略地描述了一下活动记录的功能。我们将在下面的实例中演示如何将活动记录和ActionController以及ActionView一起使用建立一个登录程序。
实例
这个登录程序非常简单。下面将描述这个程序的基本功能:
这个程序有一个窗口允许用户输入用户名和密码。在输入用户名和密码后,系统将会验证它们的合法性。在用户登录后,系统将这个用户的信息记录在session中,直到它们注销登录。
下面将描述组成系统的模块:
User.rb - 数据表(users)的映射
login_controller.rb - 控制前端和后端的数据流
login.rhtml - 显示登录界面
下面是建立users表的SQL语句:
create table users ( id int not null auto_increment, name varchar(100) not null, password char(40) null, primary key (id); } |
接下来我们处理数据表映射类。由于我们正在使用RoR中的活动记录。因此,除了可以使用establish_connection方法连接数据库外,我 们还可以使用config目录中的database.yml文件来描述数据库连接信息。如下是在database.yml的development中的设 置情况:
development: adapter: mysql database: test encoding: gb2312 username: root password: 1234 host: localhost |
然后使用以下命令建立数据表映射类User:
ruby script/generate model User |
这个User类包含两个方法:try_to_begin(这个方法调用login方法)和login。代码如下:
class User < ActiveRecord::Base def self.login(name, password) find(:first, :conditions => ["name = ? and hashed_password = ?", name,password]) end def try_to_login User.login(self.name, self.password) end end |
Login方法使用了find方法查询users表中的记录,并进行验证。这个方法的逻辑是非常简单的:try_to_login方法通过controller被调用。然后将用户名密码传入login方法中。Login方法返回包含用户名和密码的数据对象。
接下来我们使用如下命令建立控制类:
ruby script/generate controller Login |
以下代码是控制类的实现,其中包括数据验证和处理session。
class LoginController < ApplicationController def login @user = User.new(params[:user]) logged_in_user = @user.try_to_login if logged_in_user session[:user_id] = logged_in_user.id redirect_to(:action => "index") else flash[:notice] = "不正确的用户名和密码!" end end end |
下面是login.rhtml的代码
<% @page_title = "增加一个用户" -%> <%= error_messages_for 'user' %> <%= form_tag %> <table> <tr> <td>User name:</td> <td><%= text_field("user", "name") %></td> </tr> <tr> <td>密码:</td> <td><%= password_field("user", "password") %></td> </tr> <tr> <td></td> <td><input type="submit" value=" ADD USER " /></td> </tr> </table> <%= end_form_tag %> |
到现在这个程序已经完成了。如果你将这个应用程序和使用其它语言编写的同样的应用程序相比,基于RoR的应用程序的代码是非常少的。本文只是对活动记录的主要功能进行简单的描述,如果读者对其感性趣,可以查阅相关的文档了解更详细信息。