跳到內容 跳到搜尋

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 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!

也可以在同一個 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.connection 檢索。所有繼承自 ActiveRecord::Base 的類別都將使用此連線。但您也可以設定類別特定的連線。例如,如果 Course 是 ActiveRecord::Base,但存在於不同的資料庫中,您只要說 Course.establish_connection,Course 及其所有子類別就會使用此連線。

此功能是透過在 ActiveRecord::Base 中保留連線池來實作的,連線池是一個以類別為索引的雜湊。如果要求連線,ActiveRecord::Base.retrieve_connection 方法將沿著類別層級向上,直到在連線池中找到連線為止。

例外狀況

注意:所列的屬性是類別層級屬性(可從類別和實例層級存取)。因此,可以透過 Base.logger= 將記錄器指定給類別,然後由目前物件空間中的所有實例使用。

包含的模組