Topics
主題
• What is Active Record?
什麼是Active Record?
• ActiveRecord Object Creation
ActiveRecord對象創建
• Find operation
Find方法
• Dynamic Attribute-based Finders
動態地基於屬性的Finder
• Validation
驗證
• Migration
遷移
• Callbacks
回滾
• Exception Handling
異常控制
What is Active Record?
What is Active Record?
• Active Record is a Ruby library that provides mapping between business objects and database tables
Active Record是一個提供業務對象和數據庫表之間映射關係的Ruby庫
> Accessing, saving, creating, updating operations in your Rails code are performed by Active Record
Rails代碼裏的增刪改查操作由Active Record執行
• It‘s an implementation of the object-relational mapping (ORM) pattern by the same name as described by Martin Fowler:
它實現了對象關係映射(ORM)模型,通過相同的命名由Martin Fowler:
> "An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data."
“一個對象,就是數據庫表或視圖裏的一行,封裝了數據庫鏈接,和數據的邏輯域”
• Contains domain logic
包含邏輯域
Major Features
主要特性
• Automated mapping between classes and tables, attributes and columns.
在類和表,屬性和列之間自動映射
• Validation rules that can differ for new or existing objects.
驗證規則可以區別新舊對象
• Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc).
方法或隊列的在一個完整生命週期裏可以回滾
• Observers for the entire lifecycle
生命週期的觀察者
• Transaction support on both a database and object level.
數據庫和對象層都有事務支持
• Reflections on columns, associations, and aggregations
反射
• Direct manipulation (instead of service invocation)
直接操作
• Database abstraction through simple adapters (~100 lines) with a shared connector
通過簡單的adapter實現數據抽象
• Logging support for Log4r and Logger
Log4r和Logger的日誌支持
• Associations between objects controlled by simple meta-programming macros.
通過簡單的元編程實現對象控制的聯合
• Aggregations of value objects controlled by simple meta-programming macros.
通過簡單的元編程實現對象控制的聚合
• Inheritance hierarchies
繼承的分層
Automatic Mapping between Classes and Tables
類和表之間的自動映射
Mapping between Classes and Tables
• Class definition below is automatically mapped to the table named “products”
下面的類自動映射到命名爲“products”的表
- class Product < ActiveRecord::Base
- end
數據模型的“products”表
- CREATE TABLE products (
- id int(11) NOT NULL auto_increment,
- name varchar(255),
- PRIMARY KEY (id)
- );
Active Record定義和邏輯域
• Class definition below is automatically mapped to the table named “products”
class Product < ActiveRecord::Base
end
• Active Record typically contain domain logic
典型的Active Record包括邏輯域
- class Product < ActiveRecord::Base
- def my_business_method
- # Whatever business logic
- end
- end
屬性
• Active Record objects don‘t specify their attributes directly, but rather infer them from the table definition with which they‘re linked.
Active Record對象並不直接指定他們的屬性,而是從相關的表的定義中推斷
• Adding, removing, and changing attributes and their type is done directly in the database.
添加,刪除和改變屬性和類型會直接在數據庫裏完成
• Any change is instantly reflected in the Active Record objects.
任何改變將立刻在Active Record裏產生影響
ActiveRecord Object Creation, Update, Delete
ActiveRecord對象的創建,修改和刪除
ActiveRecord Objects Can Be Created in 3 Different Ways
ActiveRecord對象能夠由三種途徑創建
• Constructor parameters in a hash
構造函數在一個hash
- user = User.new(:name => "David", :occupation => "Artist")
用block初始化
- user = User.new do |u|
- u.name = "David"
- u.occupation = "Code Artist"
- end
創建一個裸體對象,然後設置屬性
- user = User.new
- user.name = "David"
- user.occupation = "Code Artist"
ActiveRecord對象能夠被保存
• Use save instance method - a row is added to the table
用save方法 - 一行將被添加到數據庫
- user = User.new(:name => "David", :occupation => "Artist")
- user.save
ActiveRecord Find Operation
ActiveRecord的Find操作
find Method
find方法
• find is a class method
find是一個類方法
> In the same way, new and create are class methods
同理,new和create也是類方法
> You use it with a ActiveRecord class (not with an object instance)
你使用它用ActiveRecord類(而不是實例變量)
>Product.find(..)
Find Operation
Find操作
• Find operates with four different retrieval approaches:
Find操作有四個不同的實現方法:
> Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). If no record can be found for all of the listed ids, then RecordNotFound will be raised.
Find by id - 可以是一個指定的id(1),一個id列表(1, 5, 6), 或者是一個id數組([5, 6, 10]).如果一條記錄也找不到,會拋出RecordNotFound異常
> Find first - This will return the first record matched by the options used
Find first - 返回第一條匹配的記錄
> Find last - This will return the last record matched by the options used.
Find last - 返回最後一條匹配的記錄
> Find all - This will return all the records matched by the options used. If no records are found, an empty array is returned.
Find all - 返回所有匹配的記錄。如果找不到記錄,返回一個空數組
Find Criteria
Find準則
• :conditions - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]
:conditions - 一條SQL語句,比如"administrator = 1" 或者 [ "user_name = ?", username ]
• :order
• :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
:group - 一個需要被分組的屬性名。用GROUP BY SQL語句
• :limit - An integer determining the limit on the number of rows that should be returned.
:limit - 給一個整數決定需要返回的行數
• :offset - An integer determining the offset from where the rows should be fetched.
:offset - 給一個整數決定哪幾行被分離。
• :joins - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed) or named associations in the same form used for the :include option, which will perform an INNER JOIN on the associated table(s).
:joins - 附加一個SQL語句,如"LEFT JOIN comments ON comments.post_id = id"(必須的)或者由同一個格式命名
• :include - Names associations that should be loaded alongside. The symbols named refer to already defined associations.
• :select - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not include the joined columns.
• :from - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name of a database view).
• :readonly - Mark the returned records read-only so they cannot be saved or updated.
• :lock - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE". :lock => true gives connection‘s default exclusive lock, usually "FOR UPDATE".
Examples: Find by id
- Person.find(1) # returns the object for ID = 1
- Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
- Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
- Person.find([1]) # returns an array for the object with ID = 1
- Person.find(1, :conditions => "administrator = 1",
- :order => "created_on DESC")
- Person.find(:first) # returns the first object fetched
- # by SELECT * FROM people
- Person.find(:first, :conditions => [ "user_name = ?", user_name])
- Person.find(:first, :order => "created_on DESC", :offset => 5)
- Person.find(:all) # returns an array of objects for all the
- rows fetched by SELECT * FROM people
- Person.find(:all, :conditions => [ "category IN (?)",
- categories], :limit => 50)
- Person.find(:all, :conditions => { :friends => ["Bob",
- "Steve", "Fred"] }
- Person.find(:all, :offset => 10, :limit => 10)
- Person.find(:all, :include => [ :account, :friends ])
- Person.find(:all, :group => "category")
• Imagine two concurrent transactions: each will read person.visits == 2, add 1 to it, and save, resulting in two saves of person.visits = 3. By locking the row, the second transaction has to wait until the first is finished; we get the expected person.visits == 4.
- Person.transaction do
- person = Person.find(1, :lock => true)
- person.visits += 1
- person.save!
- end
• Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
條件能夠被賦予字符串,數組,或者hash
> The array form is to be used when the condition input is tainted and requires sanitization.
> The string form can be used for statements that don‘t involve tainted data.
> The hash form works much like the array form, except only equality and range is possible. Examples:
Examples: Conditions
• Array form
> User.find(:all, :conditions=>["hobby=? AND name=?", 'swimming', 'Tom']
• String form
> User.find(:all, :conditions=>"hobby='swimming'", :order=>"hobby DESC, age")
• Hash form
> User.find(:all, :conditions=>{:hobby=>'swimming', :name=>'Tom'}, :order=>"hobby DESC, age")
Dynamic Attributebased Finders
Dynamic Attribute-based Finders
• Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL.
• They work by appending the name of an attribute to find_by_ or find_all_by_, so you get finders like
Person.find_by_user_name, Person.find_all_by_last_name, and Payment.find_by_transaction_id.
Dynamic Find Operation
• So instead of writing Person.find(:first, :conditions => ["user_name = ?", user_name]), you just do Person.find_by_user_name(user_name).
• And instead of writing Person.find(:all, :conditions => ["last_name = ?", last_name]), you just do Person.find_all_by_last_name(last_name).
ActiveRecord Validation
ActiveRecord::Validations
• Validation methods
- class User < ActiveRecord::Base
- validates_presence_of :username, :level
- validates_uniqueness_of :username
- validates_oak_id :username
- validates_length_of :username, :maximum => 3, :allow_nil
- validates_numericality_of :value, :on => :create
- end
ActiveRecord::Validations
• Active Records implement validation by overwriting Base#validate (or the variations, validate_on_create and validate_on_update).
Active Records實現了驗證通過重寫Base#validate
- class Person < ActiveRecord::Base
- protected
- def validate
- errors.add_on_empty %w( first_name last_name )
- errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/
- end
- def validate_on_create # is only run the first time a new object is saved
- unless valid_discount?(membership_discount)
- errors.add("membership_discount", "has expired")
- end
- end
- end
ActiveRecord Migration
ActiveRecord Migration
• Manage the evolution of a schema used
> It’s a solution to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to push that change to other developers and to the production server.
這是個解決添加成員在本地數據庫的方法
• You can describe the transformations in self-contained classes that can be checked into version control systems and executed against another database that might be one, two, or five versions behind.
Example: Migration
• Add a boolean flag to the accounts table and remove it again, if you’re backing out of the migration.
- class AddSsl < ActiveRecord::Migration
- def self.up
- add_column :accounts, :ssl_enabled, :boolean, :default => 1
- end
- def self.down
- remove_column :accounts, :ssl_enabled
- end
- end
Example: Migration
• First adds the system_settings table, then creates the very first row in it using the Active Record model that relies on the table.
- class AddSystemSettings < ActiveRecord::Migration
- def self.up
- create_table :system_settings do |t|
- t.column :name, :string
- t.column :label, :string
- t.column :value, :text
- end
- SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
- end
- def self.down
- drop_table :system_settings
- end
- end
• create_table(name, options)
• drop_table(name)
• rename_table(old_name, new_name)
• add_column(table_name, column_name, type, options)
• rename_column(table_name, column_name, new_column_name)
• change_column(table_name, column_name, type, options)
• remove_column(table_name, column_name)
• add_index(table_name, column_names, index_type, index_name)
• remove_index(table_name, index_name)
Callbacks
回滾
What is Callback?
• Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic before or after an alteration of the object state.
回滾就是一個生命週期的一個點,可以在發生改變後混滾到該點
• This can be used to make sure that associated and dependent objects are deleted when destroy is called (by overwriting before_destroy) or to massage attributes before they‘re validated (by overwriting before_validation).
Lifecycle of an ActiveRecord Object
• (-) save
• (-) valid
• (1) before_validation
• (2) before_validation_on_create
• (-) validate
• (-) validate_on_create
• (3) after_validation
• (4) after_validation_on_create
• (5) before_save
• (6) before_create
• (-) create
• (7) after_create
• (8) after_save
Example: Callbacks in a Model
• The callback before_validation_on_create gets called on create.
- class User < ActiveRecord::Base
- # Strip everything but alphabets
- def before_validation_on_create
- self.name = name.gsub(/[^A-Za-z]/, "") if attribute_present?("name")
- end
- # More code
- end
Exception Handling
異常處理
Exception Handling
• Handle RecordNotFound Exception
- begin
- User.find(2345)
- rescue ActiveRecord::RecordNotFound
- outs “Not found!”
- end