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_name
和 password
參數直接從 HTTP 要求而來時,容易受到 SQL 注入攻擊。authenticate_safely
和 authenticate_safely_simply
兩者會在將 user_name
和 password
插入查詢前先清除,這將確保攻擊者無法跳脫查詢並偽造登入(或更糟)。
在條件中使用多個參數時,要正確讀取第四或第五個問號所代表的內容會變得非常困難。在這種情況下,您可以改用命名繫結變數。這是藉由將問號替換為符號,並提供一個雜湊,其中包含與符號鍵相符的值
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_cast
或 account.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 方法將沿著類別層級往上,直到在連線池中找到連線。
例外狀況
-
ActiveRecordError
- 泛用錯誤類別,並且為所有 Active Record 所引發的其他錯誤的超類別。 -
AdapterNotSpecified
- 在 ActiveRecord::Base.establish_connection 中使用的組態 hash 中沒有包含:adapter
金鑰。 -
AdapterNotFound
- 在 ActiveRecord::Base.establish_connection 中使用的:adapter
金鑰指定了一個不存在的轉接器(或拼錯現有的轉接器)。 -
AssociationTypeMismatch
- 指派給關聯的物件並非在關聯定義中指定的類型。 -
AttributeAssignmentError
- 透過 ActiveRecord::Base#attributes= 方法進行大量指派時發生錯誤。您可以檢查例外狀況物件的attribute
屬性,以確定引發錯誤的屬性。 -
ConnectionNotEstablished
- 尚未建立連線。請在查詢前使用 ActiveRecord::Base.establish_connection。 -
MultiparameterAssignmentErrors
- 透過 ActiveRecord::Base#attributes= 方法進行大量指派期間發生的錯誤集合。這個例外狀況的errors
屬性包含AttributeAssignmentError
物件陣列,應檢查此陣列以確定引發錯誤的屬性。 -
RecordInvalid
- 當記錄無效時,會由 ActiveRecord::Base#save! 和 ActiveRecord::Base.create! 引發。 -
RecordNotFound
- 沒有記錄回應 ActiveRecord::Base.find 方法。具有指定 ID 的列不存在,或者列不符合其他限制。有些 ActiveRecord::Base.find 呼叫不會引發此例外狀況來表示找不到任何資料,請檢查其文件以取得進一步的詳細資料。 -
SerializationTypeMismatch
- 序列化的物件並非指定為第二個參數的類別。 -
StatementInvalid
- 資料庫伺服器拒絕 SQL 陳述式。明確的錯誤會加入訊息中。
注意:列出的屬性是類別層級的屬性(同時可從類別和實例層級存取)。因此,可以使用 Base.logger=
將記錄器指派給類別,然後會由目前物件空間中的所有實例使用。
- ActiveModel::API
- ActiveRecord::Core
- ActiveRecord::Persistence
- ActiveRecord::ReadonlyAttributes
- ActiveRecord::ModelSchema
- ActiveRecord::Inheritance
- ActiveRecord::Scoping
- ActiveRecord::Sanitization
- ActiveRecord::AttributeAssignment
- ActiveRecord::Integration
- ActiveRecord::Validations
- ActiveRecord::CounterCache
- ActiveRecord::Attributes
- ActiveRecord::Locking::Optimistic
- ActiveRecord::Locking::Pessimistic
- ActiveRecord::Encryption::EncryptableRecord
- ActiveRecord::AttributeMethods
- ActiveRecord::Callbacks
- ActiveRecord::Timestamp
- ActiveRecord::Associations
- ActiveRecord::SecurePassword
- ActiveRecord::AutosaveAssociation
- ActiveRecord::NestedAttributes
- ActiveRecord::Transactions
- ActiveRecord::NoTouching
- ActiveRecord::Reflection
- ActiveRecord::AttributeMethods::Serialization
- ActiveRecord::Store
- ActiveRecord::SecureToken
- ActiveRecord::TokenFor
- ActiveRecord::SignedId
- ActiveRecord::Suppressor
- ActiveRecord::Normalization
- ActiveRecord::Marshalling::Methods