跳至內容 跳至搜尋

Active Record

Active Record 物件不會直接指定其屬性,而是從與其連結的資料表定義推論出來。新增、移除和變更屬性及其類型會直接在資料庫中完成。任何變更會立即反映在 Active Record 物件中。將給定的 Active Record 物件與特定資料表連結的對應,在最常見的情況下會自動進行,但比較不常見的情況則可以改寫。

請參閱 table_name 中的對應規則,以及 files/activerecord/README_rdoc.html 中的完整範例,以獲得更多說明。

建立

Active Record 接受建構函數參數,方式包括雜湊或區塊。接收資料從其他地方(例如: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 使用者有一個 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

在類型轉換之前存取屬性

有時您希望能夠在不先執行由欄位決定的類型轉換的情況下讀取原始屬性資料。這可以使用所有屬性都具有的 <屬性>_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!

也有可能在同一個 find_by_ 中使用多個屬性,透過「and」將它們分開。

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 允許透過將類別名稱儲存在預設稱為「類型」的欄位中,來進行繼承。更多詳細資訊,請參閱 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= 將記錄器指派給類別,然後會由目前物件空間中的所有實例使用。

包含的模組