一、心得體會(huì)
1、我完成了什么?
- 今天主要看了Rails guides 5的Active Record的3、4、5、6、7章。
2、我收獲了什么?
- db/schema.rb在設(shè)計(jì)上有所取舍,不能表達(dá)數(shù)據(jù)庫的特定項(xiàng)目,如觸發(fā)器、存儲(chǔ)過程或檢查約束。
- accepts_nested_attributes_for是什么
- 數(shù)據(jù)驗(yàn)證的輔助方法:format、inclusion、length、numericality、presence、uniqueness、validates_with、validates_each
- 可以自定義驗(yàn)證的方法,比如要驗(yàn)證三個(gè)參數(shù)
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.erros[attribute] << (options[:message]|| "is not an email")
end
end
end
- 數(shù)據(jù)庫查詢的一些方法:find、take
- 條件查詢:where
- 純字符串條件:where("orders_count = '2'")
- 數(shù)組條件:where("orders_count = ?", params[:orders])
- 其他查詢
3、今天的狀態(tài)如何?
- 今天忽然接到劍爸的指示,把rails guides 5的中文版和英文版看完就可以接任務(wù)了,精神大振。
4、犯了哪些錯(cuò)誤?
5、明天還有哪些工作需要完成?
- 明天把Rails guides 5中文版看完
二、讀書筆記
Rails guides 5
直接跳到Active Record的遷移
3.5 修改現(xiàn)有的遷移
在編寫的遷移來完成或部分撤銷之前的遷移時(shí),可以使用revert方法。
3.6 數(shù)據(jù)庫模式轉(zhuǎn)儲(chǔ)
3.6.1 數(shù)據(jù)庫模式文件有什么用?
遷移盡管很強(qiáng)大,但并非數(shù)據(jù)庫模式的可信來源。
Active Record通過檢查數(shù)據(jù)庫生成的db/schema.rb文件或SQL文件才是數(shù)據(jù)庫模式的可信來源。
這兩個(gè)可信來源不應(yīng)該被修改,它們僅用于表示數(shù)據(jù)庫的當(dāng)前狀態(tài)。
當(dāng)需要部署Rails的新實(shí)例時(shí),不必把所有遷移重新運(yùn)行一遍,直接加載當(dāng)前數(shù)據(jù)庫的模式文件要簡單和快速的多。
例如,我們可以這樣創(chuàng)建測試數(shù)據(jù)庫,把當(dāng)前的開發(fā)數(shù)據(jù)庫轉(zhuǎn)儲(chǔ)為db/schema.rb或db/structure.sql文件,然后加載到測試數(shù)據(jù)庫。
數(shù)據(jù)庫模式文件還可以用于快速查看。
3.6.2 數(shù)據(jù)庫模式轉(zhuǎn)儲(chǔ)的類型
config/application.rb文件的config.active_record.schema_format選項(xiàng)來設(shè)置想要采用的方式,即:sql或:ruby
盡管如此,db/schema.rb在設(shè)計(jì)上有所取舍,不能表達(dá)數(shù)據(jù)庫的特定項(xiàng)目,如觸發(fā)器、存儲(chǔ)過程或檢查約束。
:sql格式的數(shù)據(jù)庫模式,只能加載到和原有數(shù)據(jù)庫類型相同的數(shù)據(jù)庫,而不能加載到其他類型的數(shù)據(jù)庫。
4.1
accepts_nested_attributes_for是什么?
4.2 數(shù)據(jù)驗(yàn)證的輔助方法
4.2.5 format
這個(gè)輔助方法檢查屬性的值是否匹配:with選項(xiàng)指定的正則表達(dá)式
class Coffe
validates :le, format: { with: /\A[a-zA-Z]+\z/
message: "only allows letters"
}
end
4.2.6 inclusion
檢查屬性的值是否在指定的集合中,集合可以是任何一種可枚舉的對(duì)象。
class Coffee
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size"}
end
4.2.7 length
這個(gè)輔助方法驗(yàn)證屬性值的長度,有多個(gè)選項(xiàng),可以使用不同的方法指定長度約束。
class Person < ApplicationRecord
validates :name, length: { minimum: 2 }
validates :bio, length: { maximum: 500 }
validates :password, length: { in: 6..20 }
validates :registration_number, length: { is: 6 }
end
可以自定義錯(cuò)誤方法
class Person < ApplicationRecord
validates :name, length: { minimum: 2, too_short: "%{count} characters is the mininum allowed" }
validates :bio, length: { maximum: 500, too_long: "%{count} characters is the maximum allowed" }
end
4.2.8 numericality
這個(gè)輔助方法檢查屬性的值是否只包含數(shù)字,默認(rèn)情況下,匹配的值是可選的正負(fù)符號(hào)后加整數(shù)或浮點(diǎn)數(shù)。
如果把:only_integer 的值設(shè)置為true,使用下面的正則表達(dá)式驗(yàn)證屬性的值:
/\A[+-]?\d+\z/
否則,會(huì)嘗試使用Float把值轉(zhuǎn)換成數(shù)字。
class Player < ApplicationRecord
validates :points, numericality: true
validates :games_played, numericality: { only_integer: true }
end
除了:only_integer之外,這個(gè)方法還可指定一下選項(xiàng),限制可接受的值。
4.2.9 presence
這個(gè)輔助方法檢查指定的屬性是否為非空值,它調(diào)用blank?方法檢查值是否為nil或空字符串,即空字符串或只包含空白的字符串。
class Person < ApplicationRecord
validates :name, :login, :email, presence: true
end
如果確保關(guān)聯(lián)對(duì)象是否存在,要在關(guān)聯(lián)中指定:inverse_of選項(xiàng)。
class LineItem < ApplicationRecord
belongs_to :Order
validates :order, presence: true
end
為了能驗(yàn)證關(guān)聯(lián)對(duì)象是否存在,要在關(guān)聯(lián)中指定:inverse_of選項(xiàng)。
class Order
has_many :line_items, inverse_of: :order
end
4.2.11 uniqueness 驗(yàn)證屬性值是否唯一,該方法不會(huì)在數(shù)據(jù)庫中創(chuàng)建唯一性約束。
class Account < ApplicationRecord
validates :email, uniqueness: true
end
這個(gè)驗(yàn)證會(huì)在模型對(duì)應(yīng)的表中執(zhí)行一個(gè)SQL查詢,檢查現(xiàn)有的記錄中該字段是否已經(jīng)出現(xiàn)過相同的值。
:scope選項(xiàng)用于指定檢查唯一性時(shí)使用的一個(gè)或多個(gè)屬性。
class Holiday < ApplicationRecord
validates :name, uniqueness: { scope: :year
message: "should happen once per year" }
end
如果想確保使用:scope選項(xiàng)的唯一性時(shí)使用的一個(gè)或多個(gè)屬性。
4.2.12 validates_with 這個(gè)輔助方法把記錄交給其他類做驗(yàn)證
4.2.13 validates_each 這個(gè)輔助方法使用代碼塊中的代碼驗(yàn)證屬性,它沒有預(yù)先定義驗(yàn)證函數(shù),你要在代碼塊中定義驗(yàn)證方式,要驗(yàn)證的每個(gè)屬性都會(huì)傳入塊中,在下面的例子,我們確保名和姓都不能以小寫字母開頭。
class Person < ApplicationReocrd
validates_each :name, :surname do |record, attr, value|
record.errors.add(attr, 'must start with upper case') if value =~ /\A[[:lower]]/
end
end
4.3 常用的驗(yàn)證選項(xiàng)
4.3.1 :allow_nil
指定:allow_nil選項(xiàng)后,如果要驗(yàn)證的值為nil就跳過驗(yàn)證。
class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size"}, allow_nil: true
end
4.3.2 :allow_blank
指定:allow_blank和:allow_nil選項(xiàng)類似,如果要驗(yàn)證的值為nil(調(diào)用blank?方法判斷,例如nil或空字符串),就跳過驗(yàn)證。
validates :started_on, timeliness: true, allow_blank: true
4.3.3 :message
前面已經(jīng)介紹過,如果驗(yàn)證失敗,會(huì)把:message選項(xiàng)指定的字符串添加到errors集合中,如果沒指定這個(gè)選項(xiàng),Active Record使用各個(gè)驗(yàn)證輔助房的默認(rèn)錯(cuò)誤消息.
4.3.4 :on
:on 選項(xiàng)指定什么時(shí)候驗(yàn)證,所有內(nèi)置的驗(yàn)證輔助方法默認(rèn)都在保存時(shí)(新建記錄或更新記錄)驗(yàn)證,如果想修改,可以使用om: :create,指定只在創(chuàng)建記錄時(shí)驗(yàn)證;或者使用on::update,指定只在更新記錄時(shí)驗(yàn)證。
validates :number, format: /\A\d{10}\Z/, on: :create
4.4 嚴(yán)格驗(yàn)證
數(shù)據(jù)驗(yàn)證還可以使用嚴(yán)格模式,當(dāng)對(duì)象無效時(shí)拋出ActiveModel::StrictValidationFailed異常
class Person < ApplicationRecord
validates :name, presence: { strict: true }
end
Person.new.valid?
4.5 條件驗(yàn)證
有時(shí),只有滿足特定條件時(shí)做驗(yàn)證才說的通,條件可通過 :if和:unless選項(xiàng)指定,這兩個(gè)選項(xiàng)的值可以是符號(hào)、字符串、Proc或數(shù)組,:if選項(xiàng)指定何時(shí)做驗(yàn)證,如果指定何時(shí)不做驗(yàn)證,使用:unless選項(xiàng)。
4.6 自定義驗(yàn)證
自定義的驗(yàn)證類繼承自ActiveModel::Validator,必須實(shí)現(xiàn)validate方法,其參數(shù)是要驗(yàn)證的記錄,然后驗(yàn)證這個(gè)記錄是否有效,自定義的驗(yàn)證類通過validates_with方法調(diào)用。
class MyValidator < ActiveModel::Validator
def validate(record)
unless record.name.starts_with? 'X'
record.errors[:name] << 'Need a name starting with X please'
end
end
在自定義的驗(yàn)證類中驗(yàn)證單個(gè)屬性,最簡單的方法世紀(jì)城ActiveModel::EachValidator類,此時(shí),自定義的驗(yàn)證類必須實(shí)現(xiàn)validate_each方法,這個(gè)方法接收三個(gè)參數(shù):記錄、屬性名和屬性值。它們分別對(duì)應(yīng)模型實(shí)例、要驗(yàn)證的屬性及其值。
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.erros[attribute] << (options[:message]|| "is not an email")
end
end
end
4.6.2 自定義驗(yàn)證方法
你還可以自定義方法,驗(yàn)證模型的狀態(tài),如果驗(yàn)證失敗,向errors集合添加錯(cuò)誤消息,驗(yàn)證方法必須使用類方法validate(API)注冊(cè),傳入自定義驗(yàn)證方法名的符號(hào)形式。
這個(gè)類方法可以接受多個(gè)符號(hào),自定義的驗(yàn)證方法會(huì)按照注冊(cè)的順序執(zhí)行。
valid?方法可以接受多個(gè)符號(hào),自定義的驗(yàn)證方法名的符號(hào)形式順序執(zhí)行。
4.7 處理驗(yàn)證錯(cuò)誤
除了前面介紹的valid和valid?方法之外,Rails還提供了很多方法用來處理errors集合,以及查詢對(duì)象的有效性。
下面介紹一些最常用的方法,所有可用的方法請(qǐng)查閱ActiveModel::Errors的文檔。
4.7.1 errors
ActiveModel::Errors的實(shí)例包含所有的錯(cuò)誤,鍵是每個(gè)屬性的名稱,值是一個(gè)數(shù)組,包含錯(cuò)誤消息字符串。
Active Person < ApplicationRecord
end
4.8 在視圖中顯示驗(yàn)證錯(cuò)誤
在模型中加入數(shù)據(jù)驗(yàn)證后,如果在表單中創(chuàng)建模型,出錯(cuò)時(shí),你或許想把錯(cuò)誤消息顯示出來。
因?yàn)槊總€(gè)應(yīng)用顯示錯(cuò)誤消息的方式不同,所以Rails沒有直接提供用于顯示錯(cuò)誤消息的視圖輔助方法,不過,Rails提供了這么多方法用來處理驗(yàn)證,自己編寫一個(gè)也不難,使用腳手架,Rails會(huì)在生成的_form.html.erb中加入一些ERB代碼,顯示模型錯(cuò)誤消息的完整列表。
第五章 Active Record 回調(diào)
對(duì)象的生命周期的某些時(shí)刻被調(diào)用的方法,通過回調(diào),我們可以編寫在創(chuàng)建、保存、更新、刪除、驗(yàn)證或從數(shù)據(jù)庫中加載Active Record對(duì)象時(shí)執(zhí)行的代碼。
注冊(cè)回調(diào)
class user < ApplicationRecord
validates :login, :email, presence: true
before_validation :ensure_login_has_a_value
protected
def ensure_login_has_a_value
if login.nil?
self.login = email unless email.blank?
end
end
end
5.3.4 after_initialize和after_find回調(diào)
當(dāng)Active Record對(duì)象被實(shí)例化時(shí),不管是通過直接使用new方法,還是從數(shù)據(jù)庫加載記錄,都會(huì)調(diào)用after_initialize回調(diào)。使用這個(gè)回調(diào)可以避免直接覆蓋Active Record的initialize方法。
當(dāng)Active Record從數(shù)據(jù)庫中加載記錄時(shí),會(huì)調(diào)用after_find回調(diào),如果同時(shí)定義了after_initialize和after_find回調(diào)。
5.4 調(diào)用回調(diào)
5.5 跳過回調(diào)
7.1 數(shù)據(jù)庫查詢
- find 可以輸入數(shù)組,返回的也是數(shù)組
client = Client.find([1,10])
SELECT * FROM clients WHERE (clients.id in(1,10))
- take 檢索一條記錄而不考慮排序。
client = Client.take
7.2 條件查詢
where方法用于指明限制返回記錄所用的條件,相當(dāng)于SQL語句的WHERE部分。條件可以使用字符串、數(shù)組或散列指定。
7.2.1 純字符串條件
可以直接用純字符串為查找添加條件,例如,Client.where("orders_count='2'")會(huì)查找所有orders_count字段的值為2的客戶記錄。
7.2.2 數(shù)組條件
如果Client.where("orders_count = '2'")這個(gè)例子中的數(shù)字是變化的,比如說是從別處傳遞過來的參數(shù),那么可以像下面這樣進(jìn)行查找:
Client.where("orders_count = ?", params[:orders])
Active Record會(huì)把第一個(gè)參數(shù)作為條件字符串,并用之后的其他參數(shù)來替換條件字符串中的問號(hào)(?)
我們還可以指定多個(gè)條件:
Client.where("orders_count = ? AND locked = ?", params[:orders], false)
在上面的例子中,第一個(gè)問號(hào)會(huì)被替換為params[:orders]的值,第二個(gè)問號(hào)會(huì)被替換為false在SQL中對(duì)應(yīng)的值,這個(gè)值是什么取決于所使用的數(shù)據(jù)庫適配器。
強(qiáng)烈推薦使用下面這種寫法:
Client.where("orders_count = ?", params[:orders])
而不是
Client.where("orders_count = #{params[:orders]}", )
原因是,處于安全的考慮,把變量直接放入條件字符串會(huì)導(dǎo)致變量原封不動(dòng)地傳遞給數(shù)據(jù)庫,這意味著即使是惡意用戶提交的變量也不會(huì)被轉(zhuǎn)義,這樣一來,整個(gè)數(shù)據(jù)庫就處于風(fēng)險(xiǎn)之中。
7.2.2.1 條件中的占位符
和問號(hào)占位符(?)類似,我們還可以在條件字符串中使用符號(hào)占位符,并通過散列提供符號(hào)對(duì)應(yīng)的值:
Client.where("Created_at >= :start_date AND created_at <= :end_date",
{start_date: params[:start_date], end_date: params[:end_date]})
如果條件中有很多變量,那么上面這種寫法的可讀性更高。
7.2.3 散列條件
Active Record還允許使用散列條件,以提高條件語句的可讀性,使用散列條件時(shí),散列的鍵指明需要限制的字段,鍵對(duì)應(yīng)的值指明如何限制。
7.2.3.1 相等性條件
Client.where(locked: true)
上面的代碼會(huì)生成下面的SQL語句:
SELECT * FROM clients WHERE (clients.locked = 1)
其中字段名也可以是字符串:
Client.where("locked" => true)
對(duì)于belongs_to關(guān)聯(lián)來說,如果使用Active Record對(duì)象作為值,就可以使用關(guān)聯(lián)鍵來指定模型。這種方法也適用多態(tài)關(guān)聯(lián)。
7.2.3.1 相等性條件
Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
7.3 排序
要想按特定順序從數(shù)據(jù)庫中檢索記錄,可以使用order方法。
例如,如果想按created_at字段的升序方式取回記錄:
Client.order(:created_at)
或
Client.order("created_at")
7.4 選擇特定字段
Client.select("viewable_by, locked")
# 生成
SELECT viewable_by, locked FROM clients
7.5 限量和偏移量
要想在Model.find生成的SQL語句中使用LIMIT子句,可以關(guān)聯(lián)上使用limit和offset方法。
limit方法用于指明想要取回的記錄數(shù)量,offset方法用于指明取回記錄時(shí)再第一條記錄之前要跳過多少條記錄。
例如:
Client.limit(5)
上面的代碼會(huì)返回5條記錄,因?yàn)闆]有使用offset方法,所以返回的這5條記錄就是前5條記錄,生成的SQL語句如下:
SELECT * FROM clients LIMIT 5
如果使用offset方法:
Client.limit(5).offset(30)
這時(shí)會(huì)返回從第31條記錄開始的第5條記錄,生成的SQL語句如下:
SELECT * FROM clients LIMIT 5 OFFSET 30
這時(shí)會(huì)返回從第31條記錄開始的第5條記錄,生成的SQL語句如下:
SELECT * FROM clients LIMIT 5 OFFSET 30
7.6 分組
要想在查找方法生成的SQL語句中使用GROUP BY子句,可以使用group方法。
例如,如果我們想根據(jù)訂單創(chuàng)建日期查找訂單記錄:
Article.select("created_at as ordered_date").group("created_at")
ps:怎么查詢不出來
7.6.1 分組項(xiàng)目的總數(shù)
要想得到一次查詢中分組項(xiàng)目的總和,可以在調(diào)用group方法后調(diào)用count方法。
Order.group(:status).count
=> { ‘a(chǎn)waiting_approval’ => 7, 'paid' => 12 }
下面的代碼會(huì)生成SQL語句:
SELECT COUNT (*) AS count_all, status AS status FROM "orders" GROUP BY status
7.7 having方法
SQL語句用HAVING子句指明GROUP BY字段的約束條件,要想在Model.find生成的SQL語句中使用HAVING子句,可以使用having方法,例如:
Article.select("date(created_at) as ordered_date, sum(author3) as
author").group("date(created_at)").having("sum(price) > ?", 100)
7.8 條件覆蓋
7.8.1 unscope 方法
可以使用unscope方法刪除某些條件,例如:
Article.where('id > 10').limit(20).order('id asc').unscope(:order)
上面的代碼會(huì)生成下面的SQL語句:
SELECT * FROM articles where id > 10 LIMIT 20
還可以使用unscope方法刪除where方法中的某些條件。例如:
Article.where(id:10, trashed:false).unscope(where: :id)
在關(guān)聯(lián)中使用unscope方法,會(huì)對(duì)整個(gè)關(guān)聯(lián)造成影響。
Article.order('id asc').merge(Article.unscope(:order))
在關(guān)聯(lián)中使用unscope方法,會(huì)對(duì)整個(gè)關(guān)聯(lián)造成影響:
Article.order('id asc').merge(Article.unscope(:order))
7.8.2 only方法
可以使用only方法覆蓋某些條件。例如:
Article.where('id > 10').limit(20).order('id desc').only(:order, :where)
7.8.3 reorder方法
可以使用reorder方法覆蓋默認(rèn)作用域中的排序方式。例如:
class Article < ApplicationRecord
has_many :comments, ->{ order('pasted_at DESC') }
end
Article.find(10).comments.reorder('name')
上面的代碼會(huì)生成下面的SQL語句:
SELECT * FROM articles WHERE id = 10
SELECT * FROM articles WHERE article_id = 10 ORDER BY name
7.8.4 reverse_order方法
可以使用reverse_order方法反轉(zhuǎn)排序條件。
Client.where("orders_count > 10").order(:name).reverse_order
上面的代碼會(huì)生成下面的SQL語句:
7.8.5 rewhere方法
可以使用rewhere方法覆蓋where方法中指定的條件。
7.9 空關(guān)系
none方法返回可以再鏈?zhǔn)秸{(diào)用中使用的、不包含任何記錄的空關(guān)系,在這個(gè)空關(guān)系上應(yīng)用后續(xù)條件鏈,會(huì)繼續(xù)生成空關(guān)系,對(duì)于可能返回的零結(jié)果、但又需要在鏈?zhǔn)秸{(diào)用中,使用的方法或作用域,可以使用none方法提供返回值。
Article.none
7.10 只讀對(duì)象
在關(guān)聯(lián)中使用Active Record提供的readonly方法,可以顯式禁止修改任何返回對(duì)象,如果嘗試修改只讀對(duì)象,不但不會(huì)成功,還會(huì)拋出ActiveRecord::Readonly異常。
client = Client.readonly.first
client.visits += 1
client.save
7.11在更新時(shí)鎖定記錄
在數(shù)據(jù)庫中,鎖定用于避免更新記錄時(shí)的條件競爭,并確保原子更新.
- 樂觀鎖定
- 悲觀鎖定
為了使用樂觀鎖定,數(shù)據(jù)表中需要有一個(gè)整數(shù)類型的lock_version字段,每次更新記錄時(shí),Active Record都會(huì)增加lock_version字段的值,如果更新請(qǐng)求中l(wèi)ock_version字段的值比當(dāng)前數(shù)據(jù)庫中l(wèi)ock_version字段的值小,更新請(qǐng)求就會(huì)失敗,并拋出ActiveRecord::StaleObjectError異常。例如:
c1 = Client.find(1)
c2 = Client.find(1)
c1.first_name = "Michael"
c1.save
c2.name = "should fail"
c2.save
拋出異常后,我們需要救援異常并處理沖突,或回滾,或合并,或應(yīng)用其他業(yè)務(wù)邏輯來解決沖突。
通過設(shè)置異常后,我們需要救援異常并處理沖突,或回滾,或合并,或應(yīng)用其他業(yè)務(wù)邏輯來解決沖突。