跳至内容 跳至搜索

Active Record

Active Record 对象不直接指定其属性,而是从与其关联的表定义推断出来。添加、删除和更改属性及其类型直接在数据库中完成。任何更改都会立即反映在 Active Record 对象中。将给定 Active Record 类绑定到特定数据库表的映射在大多数常见情况下会自动发生,但在不常见情况下可以覆盖。

有关更多信息,请参见 files/activerecord/README_rdoc.html 中的 table_name 中的映射规则以及完整的示例。

创建

Active Records 接受构造函数参数,以哈希或代码块的形式。当您从其他地方(如 HTTP 请求)接收数据时,哈希方法特别有用。它这样工作

user = User.new(name: "David", occupation: "Code Artist")
user.name # => "David"

您也可以使用代码块初始化

user = User.new do |u|
  u.name = "David"
  u.occupation = "Code Artist"
end

当然,您也可以只创建一个裸对象,并在之后指定属性

user = User.new
user.name = "David"
user.occupation = "Code Artist"

条件

条件可以指定为字符串、数组或哈希,表示 SQL 语句的 WHERE 部分。当条件输入被污染并需要清理时,应使用数组形式。字符串形式可用于不涉及污染数据的语句。哈希形式的工作方式与数组形式非常相似,只是只有相等和范围是可能的。例如

class User < ActiveRecord::Base
  def self.authenticate_unsafely(user_name, password)
    where("user_name = '#{user_name}' AND password = '#{password}'").first
  end

  def self.authenticate_safely(user_name, password)
    where("user_name = ? AND password = ?", user_name, password).first
  end

  def self.authenticate_safely_simply(user_name, password)
    where(user_name: user_name, password: password).first
  end
end

authenticate_unsafely 方法将参数直接插入查询中,因此如果user_namepassword 参数直接来自 HTTP 请求,则容易受到 SQL 注入攻击。authenticate_safelyauthenticate_safely_simply 都将在插入查询之前对user_namepassword 进行清理,这将确保攻击者无法逃脱查询并伪造登录(或更糟)。

在条件中使用多个参数时,很容易难以准确地阅读第四个或第五个问号应该代表什么。在这些情况下,您可以求助于命名绑定变量。这是通过用符号替换问号并提供一个带有匹配符号键值的哈希来完成的

Company.where(
  "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
  { id: 3, name: "37signals", division: "First", accounting_date: '2005-01-01' }
).first

类似地,没有语句的简单哈希将根据 SQL AND 运算符与相等生成条件。例如

Student.where(first_name: "Harvey", status: 1)
Student.where(params[:student])

可以使用哈希中的范围使用 SQL BETWEEN 运算符

Student.where(grade: 9..12)

可以使用哈希中的数组使用 SQL IN 运算符

Student.where(grade: [9,11,12])

在联接表时,可以使用嵌套哈希或以“table_name.column_name”形式编写的键来限定特定条件的表名。例如

Student.joins(:schools).where(schools: { category: 'public' })
Student.joins(:schools).where('schools.category' => 'public' )

覆盖默认访问器

所有列值都可以通过 Active Record 对象上的基本访问器自动获得,但有时您希望专门化这种行为。这可以通过覆盖默认访问器(使用与属性相同的名称)并调用 super 来实际更改内容来完成。

class Song < ActiveRecord::Base
  # Uses an integer of seconds to hold the length of the song

  def length=(minutes)
    super(minutes.to_i * 60)
  end

  def length
    super / 60
  end
end

属性查询方法

除了基本访问器之外,查询方法也自动在 Active Record 对象上可用。查询方法允许您测试属性值是否存在。此外,在处理数值时,如果值为零,则查询方法将返回 false。

例如,具有name 属性的 Active Record User 具有一个name? 方法,您可以调用它来确定用户是否有姓名

user = User.new(name: "David")
user.name? # => true

anonymous = User.new(name: "")
anonymous.name? # => false

查询方法还将尊重对默认访问器的任何覆盖

class User
  # Has admin boolean column
  def admin
    false
  end
end

user.update(admin: true)

user.read_attribute(:admin)  # => true, gets the column value
user[:admin] # => true, also gets the column value

user.admin   # => false, due to the getter override
user.admin?  # => false, due to the getter override

在属性被类型转换之前访问属性

有时您希望能够读取原始属性数据,而无需先运行列确定的类型转换。这可以通过使用所有属性都具有的<attribute>_before_type_cast 访问器来完成。例如,如果您的 Account 模型具有balance 属性,您可以调用account.balance_before_type_castaccount.id_before_type_cast

这在验证情况下特别有用,其中用户可能为整数字段提供字符串,您希望在错误消息中显示原始字符串。正常访问属性会将字符串类型转换为 0,这不是您想要的。

基于动态属性的查找器

基于动态属性的查找器是一种稍微过时的方法,用于通过简单查询获取(和/或创建)对象,而无需转向 SQL。它们通过将属性的名称附加到find_by_ 上来工作,例如Person.find_by_user_name。您可以使用Person.find_by_user_name(user_name),而不是编写Person.find_by(user_name: user_name)

可以在动态查找器末尾添加感叹号 (!) 以使它们在没有返回任何记录时引发ActiveRecord::RecordNotFound 错误,例如Person.find_by_last_name!

还可以通过使用“and”将它们隔开,在同一个find_by_ 中使用多个属性。

Person.find_by(user_name: user_name, password: password)
Person.find_by_user_name_and_password(user_name, password) # with dynamic finder

甚至可以在关系和命名作用域上调用这些动态查找器方法。

Payment.order("created_on").find_by_amount(50)

在文本列中保存数组、哈希和其他不可映射的对象

Active Record 可以使用 YAML 在文本列中序列化任何对象。为此,您必须使用对类方法 serialize 的调用来指定这一点。这使得可以存储数组、哈希和其他不可映射的对象,而无需做任何额外的工作。

class User < ActiveRecord::Base
  serialize :preferences
end

user = User.create(preferences: { "background" => "black", "display" => large })
User.find(user.id).preferences # => { "background" => "black", "display" => large }

您还可以指定一个类选项作为第二个参数,如果以不在层次结构中的类的后代形式检索序列化的对象,则该选项会引发异常。

class User < ActiveRecord::Base
  serialize :preferences, Hash
end

user = User.create(preferences: %w( one two three ))
User.find(user.id).preferences    # raises SerializationTypeMismatch

当您指定类选项时,该属性的默认值将是该类的全新实例。

class User < ActiveRecord::Base
  serialize :preferences, OpenStruct
end

user = User.new
user.preferences.theme_color = "red"

单表继承

Active Record 允许通过将类的名称存储在一个名为“type”的列中(默认情况下)来进行继承。有关更多详细信息,请参见 ActiveRecord::Inheritance

在不同模型中连接到多个数据库

连接通常是通过 ActiveRecord::Base.establish_connection 创建的,并由 ActiveRecord::Base.lease_connection 检索。所有从 ActiveRecord::Base 继承的类都将使用此连接。但您也可以设置特定于类的连接。例如,如果 Course 是一个 ActiveRecord::Base,但位于不同的数据库中,您只需要说Course.establish_connection,Course 及其所有子类将使用此连接。

此功能是通过在 ActiveRecord::Base 中保留一个连接池来实现的,该连接池是一个以类为索引的哈希。如果请求连接,ActiveRecord::Base.retrieve_connection 方法将向上遍历类层次结构,直到在连接池中找到连接。

异常

注意:列出的属性是类级属性(从类和实例级别都可以访问)。因此,可以通过Base.logger= 向类分配一个记录器,然后所有实例都将在当前对象空间中使用它。

包含的模块