ActiveRecord 是 Rails 提供的一个对象关系映射(ORM)层,从这篇开始,我们来了解 Active Record 的一些基础内容,连接数据库,映射表,访问数据等。 Active Record 使用基本的 ORM 模式:表映射成类,行映射成为对象,列映射成对象的 属性。与很多大量使用配置的 ORM 库不同,Active Record 最小化了配置。想象一下,有一 个使用 Active Record 的程序把 Mysql 数据库中的 orders 表转换到类,通过制定的 ID 查找到 order,设定 order 的名称,然后保存回数据库: require "rubygems" require "active_record" ActiveRecord::Base.establish_connection(:adapter => "mysql", :host => "localhost", :database => "railsdb") class Order < ActiveRecord::Base end order = Order.find(123) order.name = "Dave Thomas" order.save 在上面的例子里不需要任何配置,Active Record 为我们做了这些事情,下面我们来看看 ActiveRecord 是怎样工作的。 表和类 当你创建了一个 ActiveRecord::Base 类的子类, Active Record 假定表名是复数的, 而类名 是单数的,当类名包括多个单词时,表名被假定为单词间带有下划线,复数形式不规则,例 如: 类名 表名 类名 表名 Order orders LineItem line_items TaxAgency tax_agencies Person people Diagnosis diagnoses Quantity quantities Batch batches Datum data 默认的,Active Record 的表名是复数的,类名是单数的,如果你不太习惯,可以通过设 置一个全局标记来禁用它,在 config 目录的 environment.rb 文件中设置: ActiveRecord::Base.pluralize_table_names = false 单复数规则可以对付大部分情况,对于一些特殊情况,Active Record 允许我们覆盖默认 的生成的表名,使用 set_table_name 命令,例如: class Sheep < ActiveRecord::Base set_table_name "sheep" # Not "sheeps" end class Order < ActiveRecord::Base set_table_name "ord_rev99_x" # Wrap a legacy table... end ActiveRecord 中的一个对象相当于数据库中表的一行,对象的属性对应于表的列,也许你会 注意到我们的 Order 类没有提及关于 orders 表的任何东西, 这是因为 ActiveRecord 在运行时 来确定这些对应关系,Active Record 将数据库中的模式反应到类中。 我们的 orders 表可能使用下面的
sql 来创建: create table orders ( id int not null auto_increment, name varchar(100) not null,
email varchar(255) not null, address text not null, pay_type char(10) not null, shipped_at datetime null, primary key (id) ); 我们可以创建一个类来转换这个表: require 'rubygems' require_gem 'activerecord' # Connection code omitted... class Order < ActiveRecord::Base end 当我们创建了 Order 类,就可以访问它的属性来获取信息,下面的代码使用 columns() 方法,来返回一个 Columns 对象的数组,在这里,我们显示了 orders 表中的每个列,并且 显示指定字段的详细信息。 require 'pp' pp
Order.columns.map { |col| col.name } pp Order.columns_hash['shipped_at'] 运行代码,会得到下面的输出: ["id", "name", "email", "address", "pay_type", "shipped_at"] #
注意, Active Record 决定了每个列的类型, 在这个例子里, shipped_at 列作为 datetime 将 类型,该列的值被保存在一个 ruby 的 Time 类型的对象中,我们可以写些代码来验证该列的 类型及其内容: order = Order.new order.shipped_at = "2005-03-04 12:34" pp order.shipped_at.class pp order.shipped_at 输出为: Time Fri Mar 04 12:34:00 CST 2005 下面的列表展示了 sql 和 ruby 间的数据类型对应关系: SQLType Ruby Class SQLType Ruby Class int, integer Fixnum float, double Float decimal, numeric Float char, varchar, string String clob, blob, text String datetime, time Time interval, date Date Boolean 后面详细介绍 有一个潜在的可能是关于 decimal 的,在数据库里,使用 decimal 的列来存储 number 和 fix number 型,Active Record 将 decimal 映射成 Float 类的对象,尽管这样可以应用于大多 数应用,浮点数是不精确的,在对这一类型的属性进行一系列操作的时候,可能会发生舍入
的错误,你也许可以使用 integer 类型来作为替代方案,例如,存储货币型的时候可以将元, 角,分,分别存入不同的字段。做为一种选择,你可以使用聚合(aggregations) ,使用多个 分开的字段来构建货币类型。 如果在一个 model 对象中有一个名为 balance 的属性,你可以通过索引操作符来获取该属性 的值,你可以使用一个字符串或者标记,在这里我们使用标记,例如: account[:balance] #=> 获取值 account[:balance] = 0.0 #=> 设置值 但是这种常见的代码是不提倡的,更好的是这样,利用 ruby 的访问方法: account.balance #=> 获取值 account.balance = 0.0 #=>设置值 在这里,我们使用了两种方法来获取属性的值,Active Record 会进行适当的类型转换, 比如,如果数据库中的列是时间戳(TimeStamp) ,那么,我们将会得到一个 Time 对象,如 果你想得到属性的原始的值,添加_before_type_cast 到访问方法的最后,例如: account.balance_before_type_cast #=> "123.4", a string account.release_date_before_type_cast #=> "20050301" 最后, 也可以使用 Model 自己的私有方法 read_attribute 和 write_attribute, 这两个方法 使用属性名作为参数。 一些数据库支持 boolean 类型,而另一些则不支持,这使得 Active Record 要抽象 boolean 类 型变得困难。例如,如果数据库不支持 boolean 类型,有的开发者使用 char(1)来替代,而内 容使用“t”和“f”来表示 true 和 false,而另外一些开发者使用 integer 类型,0 是 false,1 是 true。即使数据库支持
boolean 类型,在内部也许还是使用 0 和 1 来存储。 在 Ruby 里,在条件判断中,数字 0 和字符 f 都被认为是 true 值,这就意味着如果你直 接使用属性的值,你的代码会被认为该列的值是 true,而不是你认为的 false,例如: # 不要这样使用 user = Users.find_by_name("Dave") if user.superuser grant_privileges end 当在查询条件中使用属性时,你必须在列名后添加一个问号: # 这样是正确的 user = Users.find_by_name("Dave") if user.superuser? grant_privileges end 当使用访问操作符来获取属性的值时,当值为数字 0,或者字符“0”“f”,“false” , ,
或 (空字符串) 或 nil, “” , 或一个常量 false 时, 都被认为是 false, 否则, 就会被认为是 true。 如果你在一个遗留系统上或者非英语系统上开发,上面对 true 的定义也许会无法工作, 在这种情况下,你可以 override 内建的谓词方法的定义,例如,荷兰语情况下,字段也许包 含 J 或者 N,这种情况下,你可以像下面这样: class User < ActiveRecord::Base def superuser? self.superuser == 'J' end #... end 一些数据库支持 boolean 类型,而另一些则不支持,这使得 Active Record 要抽象 boolean 类 型变得困难。例如,如果数据库不支持 boolean 类型,有的开发者使用 char(1)来替代,而内 容使用“t”和“f”来表示 true 和 false,而另外一些开发者使用 integer 类型,0 是 false,1 是 true。即使数据库支持 boolean 类型,在内部也许还是使用 0 和 1 来存储。 在 Ruby 里,在条件判断中,数字 0 和字符 f 都被认为是 true 值,这就意味着如果你直 接使用属性的值,你的代码会被认为该列的值是 true,而不是你认为的 false,例如: # 不要这样使用 user = Users.find_by_name("Dave") if user.superuser grant_privileges end 当在查询条件中使用属性时,你必须在列名后添加一个问号: # 这样是正确的 user = Users.find_by_name("Dave") if user.superuser? grant_privileges end 当使用访问操作符来获取属性的值时,当值为数字 0,或者字符“0”“f”,“false” , , 或 (空字符串) 或 nil, “” , 或一个常量 false 时, 都被认为是 false, 否则, 就会被认为是 true。 如果你在一个遗留系统上或者非英语系统上开发,上面对 true 的定义也许会无法工作, 在这种情况下,你可以 override 内建的谓词方法的定义,例如,荷兰语情况下,字段也许包 含 J 或者 N,这种情况下,你可以像下面这样: class User < ActiveRecord::Base def superuser? self.superuser == 'J' end #... end 也许你已经注意到了,在我们前面的代码中,数据库定义里都使用了一个 integer 型的字段 id 作为主键,这是 Active Record 的一个约定。 或许你要问,为什么不用订单编号或者某个
有意义的列来作为主键呢?使用 id 作为主 键有一个很重要的原因,就是如果使用具有内在格式的主键的话,随着时间推移,有可能其 中的规则也会变化。例如,使用 ISBN 号码来给 book 表做主键,毕竟 ISBN 号码是唯一的, 但是,有可能当一本书写完后,美国的出版业已经发展了并且在所有的 ISBN 号码后又附加
了一位数字。 如果我们使用了 ISBN 作为 book 表的主键,我们就要更新所有 book 表的记录来反映这 个变化,而且还有一个问题,还有其他表引用了 book 表的主键,我们就要更新所有的引用, 这还牵涉到要删除外键,所有的这一切都是非常痛苦的。 如果使用有意义的值作为主键,那么我们将收到外界业务规则的影响,如果使用 id,我 们可以自己完全控制,而且如果象 ISBN 等一些东西改变的话,将不会影响到数据库结构。 如果你从一个新的数据库结构开始,可能会遵循约定,给所有的表都使用 id 作为主键, 但是,当你使用的是一个既存的数据库开始的时候,Active Record 提供了简单的方法来让你 重新给表指定主键,例如: class BadBook < ActiveRecord::Base set_primary_key "isbn" end 通常,Active Record 会注意给新创建的记录生成主键值-使用自增长的整数。不管怎样, 当你 override 表的主键名字的时候,你就需要自己负责给新建记录一个唯一的主键值。也许 有些让人惊讶,你还是设置一个 id 的属性来完成这件事,因为 Active Record 所关心的是, 主键的设置永远都使用名为 id 属性,set_primary_key 的声明只是设置了使用的列名,下面 的例子,我们使用 ISBN 作为主键。 book = BadBook.new book.id = "0-12345-6789" book.title = "My Great American Novel" book.save # ... book = BadBook.find("0-12345-6789") puts book.title # => "My Great American Novel" p book.attributes #=> {"isbn" =>"0-12345-6789", "title"=>"My Great American Novel"} 也就是说,在设置主键的时候,使用 id 属性,其他时候,使用真实的列名。 Active Record 抽象了数据库连接的概念,帮助应用程序来处理底层的数据库链接的细节,作 为替代,Active Record 使用通用的调用,将细节委托给一组数据库适配器。 可以使用 establish_connection( )方法来制定连接,下面的例子创建了一个 mysql 数据库 连接,数据库的名字是 railsdb,服务器的 Host 名为 dbserver,用户名为 railsuser,密码为 railspw。
ActiveRecord::Base.establish_connection( :adapter => "mysql", :host => "dbserver", :database => "railsdb", :username => "railsuser", :password => "railspw" )
Active Record 支持 DB2,MySql,Oracle,Postgres,SqlServer,以及 SqlLite,每一种数据 库适配器在链接的参数上都有一些细小的差别,下表列出了常用的参数:
注意 Oracle 适
配器的名字为