<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 4.1 模型的基礎操作 ## 概要: 本課時講解模型的基礎操作,數據遷移,常用的 CRUD 方法,在數據查詢時,如何避免 N+1問題,如何使用 scope 包裝查詢條件,編寫模型 Rspec 測試。 ## 知識點: 1. Active Record 1. Migration 1. CRUD ## 正文 ### 4.1.1 Active Record 簡介 Active Record 模式,是由 Martin Fowler 的《企業應用架構模式》一書中提出的,在該模式中,一個 Active Record(簡稱 AR)對象包含了持久數據(保存在數據庫中的數據)和數據操作(對數據庫里的數據進行操作)。 對象關系映射(Object-Relational Mapping,簡稱 ORM),是將程序中的對象(Object)和關系型數據庫(Relational Database)的表之間進行關聯。使用 ORM 可以方便的將對象的 `屬性` 和 `關聯關系` 保存入數據庫,這樣可以不必編寫復雜的 SQL 語句,而且不必擔心使用的是哪種數據庫,一次編寫的代碼可以應用在 Sqlite,Mysql,PostgreSQL 等各種數據庫上。 Active Record 就是個 ORM 框架。 所以,我們可以用 Actice Record 來做這幾件事: - 表示模型(Model)和模型數據 - 表示模型間的關系(比如一對多,多對多關系) - 通過模型間關聯表示繼承層次 - 在保存如數據庫前,校驗模型(比如屬性校驗) - 用 `面向對象` 的方式處理數據庫 ### 4.1.2 Active Record 中的約定 Rails 中使用了 ActiveRecord 這個 Gem,使用它可以不必去做任何配置(大多數情況是這樣的),還記得 Rails 的兩個哲學理念之一么:`約定優于配置`。(另一個是 `不要重復自己`,這是 Dave Thomas 在《程序員修煉之道》一書里提出的。) 那么,我們講兩個 Active Record 中的約定: #### 4.1.2.1 命名約定 - 數據表名:復數,下劃線分隔單詞(例如 book_clubs) - 模型類名:單數,每個單詞的首字母大寫(例如 BookClub) 比如: | 模型(Class) | 數據表(Schema) | |-----|-----| | Post | posts | | LineItem | line_items | | Deer | deers | | Mouse | mice | | Person | people | 單詞在單復數轉換時,是按照英文語法約定的。 #### 4.1.2.2 Schema 約定 注:數據庫中的 Schema,指數據庫對象集合,可以被用戶直接使用。Schema 包含數據的邏輯結構,用戶可以通過命名調用數據庫對象,并且安全的管理數據庫。 - 外鍵 - 使用 singularized_table_name_id 形式命名,例如 item_id,order_id。創建模型關聯后,Active Record 會查找這個字段; - 主鍵 - 默認情況下,Active Record 使用整數字段 id 作為表的主鍵。使用 Active Record 遷移創建數據表時,會自動創建這個字段; 在數據庫字段命名的時候,有幾個特殊意義的名字,盡量回避: - created_at - 創建記錄時,自動設為當前的時間戳 - updated_at - 更新記錄時,自動設為當前的時間戳 - lock_version - 在模型中添加樂觀鎖定功能 - type - 讓模型使用單表繼承,給字段命名的時候,盡量避開這個詞 - (association_name)_type - 多態關聯的類型 - (table_name)_count - 保存關聯對象的數量。例如,posts 表中的 comments_count 字段,Rails 會自動更新該文章的評論數 ### 4.1.3 數據庫遷移(Migration) 在我們使用 scaffold 創建資源的時候,或者使用 generate 創建 model 的時候,Rails 會給我們自動創建一個數據庫遷移文件,它在 `db/migrate` 中,它的前綴是時間戳,他們按照時間的先后順序排列,當運行數據庫遷移時,他們按照時間順序先后被執行。 新創建的遷移文件,我們使用 `rake db:migrate` 命令執行它(們),這里會判斷,哪個遷移文件是還沒有被執行的。 如果我們對執行過的遷移操作不滿意,我們可以回滾這個遷移: ~~~ rake db:rollback [1] rake db:rollback STEP=3 [2] ~~~ [1] 回滾最近的一個遷移 [2] 回滾指定的遷移個數 回滾之后,遷移停留在回滾到的那個位置的,schema 也會更新到那個位置時的狀態。比如,我們上一次遷移執行了5個文件,我們回滾的時候,是一個個文件回滾的,所以我們指定 STEP=5,才能把剛才遷移的5個文件回滾。 在我們開發代碼的過程中,有是會因為失誤少寫了一個字段,我們回滾之后,在遷移文件中把它加上,然后,我們 `rake db:migrate` 再次運行。不過,`rake db:migrate:redo [STEP=3]` 直接回滾然后再次運行遷移,這樣會方便些。 這種回滾操作適合開發過程中,出現了新的想法,而回滾最近連續的幾個遷移。 如果我們想回滾很久以前的某個操作,而且在那個遷移之后,我們已經執行了多個遷移。這時該如何處理呢? 如果在開發階段,我們干脆 `rake db:drop`,`rake db:create`,`rake db:migrate`。但是在生產環境,我們決不能這么做,這時我們要針對需求,編寫一個遷移文件: ~~~ class ChangeProductsPrice < ActiveRecord::Migration def change reversible do |dir| change_table :products do |t| dir.up { t.change :price, :string } dir.down { t.change :price, :integer } end end end end ~~~ 或者: ~~~ class ChangeProductsPrice < ActiveRecord::Migration def up change_table :products do |t| t.change :price, :string end end def down change_table :products do |t| t.change :price, :integer end end end ~~~ `up` 是向前遷移到最新的,`down`用于回滾。 我們創建一個 model 的時候,會自動創建它的 migration 文件,我們還可以使用 `rails g migration XXX`的方法,添加自定義的遷移文件。如果我們的命名是 "AddXXXToYYY" 或者 "RemoveXXXFromYYY" 時,會自動為我們添加字符類型的字段,比如我為 variant 添加一個color 字段: ~~~ rails g migration AddColorToVariants color:string ~~~ 它的內容是: ~~~ class AddColorToVariants < ActiveRecord::Migration def change add_column :variants, :color, :string end end ~~~ ### 4.1.4 CRUD CRUD并不是一個 Rails 的概念,它表示系統(業務層)和數據庫(持久層)之間的基本操作,簡單的講叫“增(C)刪(D)改(U)查(R)”。 我們已經使用 scaffold 命令創建了資源:商品(product),我們現在使用 `app/models/product.rb` 來演示這些操作。 首先,我們需要讓 Product 類繼承 ActiveRecord: ~~~ class Product < ActiveRecord::Base end ~~~ 這樣,Product 類就可以操作數據庫了,是不是很簡單。 ### 4.1.5 創建記錄 我們使用 Product 類,向數據添加一條記錄,我們先進入 Rails 控制臺: ~~~ % rails c Loading development environment (Rails 4.2.0) > Product.create [1] (0.2ms) begin transaction [2] SQL (2.8ms) INSERT INTO "products" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2015-03-14 16:23:44.640578"], ["updated_at", "2015-03-14 16:23:44.640578"]] (0.8ms) commit transaction [2] => #<Product id: 1, name: nil, description: nil, price: nil, created_at: "2015-03-14 16:23:44", updated_at: "2015-03-14 16:23:44"> [3] ~~~ 這里,我貼出了完整的代碼。 [1],我們使用了 Product 的類方法 create,創建了一條記錄。我們還有其他的方法保存記錄。 [2],begin 和 commit ,將我們的數據保存入數據庫。如果在保存的時候出現錯誤,比如屬性校驗失敗,拋出異常等,不會將記錄保存到數據庫。 [3],我們拿到了一個 Product 類的實例。 除了類方法,我們還可以使用實例的 `save` 方法,來保存記錄到數據,比如: ~~~ > product = Product.new [1] => #<Product id: nil, name: nil, description: nil, price: nil, created_at: nil, updated_at: nil> [2] > product.save [3] (0.1ms) begin transaction [4] SQL (0.9ms) INSERT INTO "products" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2015-03-14 16:47:26.817663"], ["updated_at", "2015-03-14 16:47:26.817663"]] (9.3ms) commit transaction [4] => true [5] ~~~ [1],我們使用類方法 new,來創建一個實例,注意,[2] 告訴我們,這是一個沒有保存到數據庫的實例,因為它的 id 還是 nil。 [3] 我們使用實例方法 save,把這個實例,保存到數據庫。 [4] 調用 save 后,會返回執行結果,true 或者 false。這種判斷很有用,而且也很常見,如果你現在打開 `app/controllers/products_controller.rb` 的話,可以看到這樣的判斷: ~~~ if @product.save ... else ... end ~~~ 那么,你可能會有個疑問,使用類方法 create 保存的時候,如果失敗,會返回我們什么呢?是一個實例,還是 false? 我們使用下一章里要介紹的屬性校驗,來讓保存失敗,比如,我們讓商品的名稱必須填寫: ~~~ class Product < ActiveRecord::Base validates :name, presence: true [1] end ~~~ [1] validates 是校驗命令,要求 name 屬性必須填寫。 好了,我們來測試下類方法 create 會返回給我們什么: ~~~ > product = Product.create (0.3ms) begin transaction (0.1ms) rollback transaction => #<Product id: nil, name: nil, description: nil, price: nil, created_at: nil, updated_at: nil> 2.2.0 :003 > ~~~ 答案揭曉,它返回給我們一個未保存的實例,它有一個實用的方法,可以查看哪里出了錯誤: ~~~ > product.errors.full_messages => ["名稱不能為空字符"] ~~~ 當然,判斷一個實例是否保存成功,不必去檢查它的 errors 是否為空,有兩個方法會根據 errors 是否添加,而返回實例的狀態: ~~~ person = Person.new person.invalid? person.valid? ~~~ 要留意的是,invalid? 和 valid? 都會調用實例的校驗。 我使用類方法和實例方法的稱呼,希望沒有給你造成理解的障礙,如果有些難理解,建議你先看一看 Ruby 中關于類和實例的介紹。 ### 4.1.6 查詢記錄 #### 4.1.6.1 Find 查詢 數據查詢,是 Rails 項目經常要做的操作,如何拿到準確的數據,優化查詢,是我們要重點關注的。 查詢時,會得到兩種結果,一個實例,或者實例的集合(Array)。如果找不到結果,也會給有兩種情況,返回 nil或空數組,或者拋出 ActiveRecord::RecordNotFound 異常。 Rails 給我們提供了這些常用的查詢方法: | 方法名稱 | 含義 | 參數 | 例子 | 找不到時 | |-----|-----|-----|-----|-----| | find | 獲取指定主鍵對應的對象 | 主鍵值 | Product.find(10) | 異常 | | take | 獲取一個記錄,不考慮任何順序 | 無 | Product.take | nil | | first | 獲取按主鍵排序得到的第一個記錄 | 無 | Product.first | nil | | last | 獲取按主鍵排序得到的最后一個記錄 | 無 | Product.last | nil | | find_by | 獲取滿足條件的第一個記錄 | hash | Product.find_by(name: "T恤") | nil | 表中的四個方法不會拋出異常,如果需要拋出異常,可以在他們名字后面加上 `!`,比如 Product.take!。 如果將上面幾個方法的參數改動,我們就會得到集合: | 方法名稱 | 含義 | 參數 | 例子 | 找不到時 | |-----|-----|-----|-----|-----| | find | 獲取指定主鍵對應的對象 | 主鍵值集合 | Product.find([1,2,3]) | 異常 | | take | 獲取一個記錄,不考慮任何順序 | 個數 | Product.take(2) | [] | | first | 獲取按主鍵排序得到的第N個記錄 | 個數 | Product.first(3) | [] | | last | 獲取按主鍵排序得到的最后N個記錄 | 個數 | Product.last(4) | [] | | all | 獲取按主鍵排序得到的全部記錄 | 無 | Product.all | [] | Rails 還提供了一個 find_by 的查詢方法,它可以接收多個查詢參數,返回符合條件的第一個記錄。比如: ~~~ Product.find_by(name: 'T-Shirt', price: 59.99) ~~~ `find_by` 有一個常用的變形,比如: ~~~ Product.find_by_name("Hat") Product.find_by_name_and_price("Hat", 9.99) ~~~ 如果需要查詢不到結果拋出異常,可以使用 `find_by!`。通常,以`!`結尾的方法都會拋出異常,這也是一種約定。不過,直接使用 find,會查詢主索引,查詢不到直接拋出異常,所以是沒有 `find!` 方法的。 使用 find_by 的時候,還可以使用 sql 語句,比如: ~~~ Product.find_by("name = ?", "T") ~~~ 這是一個有用的查詢,當我們搜索多個條件,并且是 OR 關系時,可以這樣做: ~~~ User.find_by("id = ? OR login = ?", params[:id], params[:id]) ~~~ 這句話還可以改寫成: ~~~ User.find_by("id = :id OR login = :name", id: params[:id], name: params[:id]) ~~~ 或者更簡潔的: ~~~ User.find_by("id = :q OR login = :q", q: params[:id]) ~~~ #### 4.1.6.2 Where 查詢 集合的查找,最常用的方法是 `where`,它可以通過多種形式查找記錄: | 查詢形式 | 實例 | |-----|-----| | 數組(Array)查詢 | Product.where("name = ? and price = ?", "T恤", 9.99) | | 哈希(hash)查詢 | Product.where(name: "T恤", price: 9.99) | | Not查詢 | Product.where.not(price: 9.99) | | 空 | Product.none | 使用 where 查詢,常見的還有模糊查詢: ~~~ Product.where("name like ?", "%a%") ~~~ 查詢某個區間: ~~~ Product.where(price: 5..6) ~~~ 以及上面提到的,sql 的查詢: ~~~ Product.where("color = ? OR price > ?", "red", 9) ~~~ Active Record 有多種查詢方法,以至于 Rails 手冊中單獨列出一章來講解,而且講解的很細致,如果你想靈活的掌握這些數據查詢方法,建議你經常閱讀 [Active Record Query Interface](http://guides.rubyonrails.org/active_record_querying.html) 一章,這是 [中文版](http://guides.ruby-china.org/active_record_querying.html)。 ### 4.1.7 更新記錄(Update) 和創建記錄一樣,更新記錄也可以使用類方法和實力方法。 類方法是 update,比如: ~~~ Product.update(1, name: "T-Shirt", price: 23) ~~~ 1 是更新目標的 ID,如果該記錄不存在,update 會拋出 `ActiveRecord::RecordNotFound` 異常。 `update` 也可以更新多條記錄,比如: ~~~ Product.update([1, 2], [{ name: "Glove", price: 19 }, { name: "Scarf" }]) ~~~ 我們看看它的源代碼: ~~~ # File activerecord/lib/active_record/relation.rb, line 363 def update(id, attributes) if id.is_a?(Array) id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) } else object = find(id) object.update(attributes) object end end ~~~ 如果要更新全部記錄,可以使用 update_all : ~~~ Product.update_all(price: 20) ~~~ 在使用 update 更新記錄的時候,會調用 Model 的 validates(校驗) 和 callbacks(回調),保證我們寫入正確的數據,這個是定義在 Model 中的方法。但是,update_all 會略過校驗和回調,直接將數據寫入到數據庫中。 和 update_all 類似,update_column/update_columns 也是將數據直接寫入到數據庫,它是一個實例方法: ~~~ product = Product.first product.update_column(:name, "") product.update_columns(name: "", price: 0) ~~~ 雖然為 product 增加了 name 非空的校驗,但是 update_column(s) 還是可以講數據寫入數據庫。 當我們創建遷移文件的時候,Rails 默認會添加兩個時間戳字段,created_at 和 updated_at。 當我們使用 update 更新記錄時,觸發 Model 的校驗和回調時,也會自動更新 updated_at 字段。但是 Model.update_all 和 model.update_column(s) 在跳過回調和校驗的同時,也不會更新 updated_at 字段。 我們也可以用 save 方法,將新的屬性保存到數據庫,這也會觸發調用和回調,以及更新時間戳: ~~~ product = Product.first product.name = "Shoes" product.save ~~~ ### 4.1.8 刪除記錄(Destroy) 在我們接觸計算機英語里,表示刪除的英文有很多,這里我們用到的是 destroy, delete。 #### 4.1.8.1 Delete 刪除 使用 delete 刪除時,會跳過回調,以及關聯關系中定義的 `:dependent` 選項,直接從數據庫中刪除,它是一個類方法,比如: ~~~ Product.delete(1) Product.delete([2,3,4]) ~~~ 當傳入的 id 不存在的時候,它不會拋出任何異常,看下它的源碼: ~~~ # File activerecord/lib/active_record/relation.rb, line 502 def delete(id_or_array) where(primary_key => id_or_array).delete_all end ~~~ 它使用不拋出異常的 where 方法查找記錄,然后調用 delete_all。 delete 也可以是實例方法,比如: ~~~ product = Product.first product.delete ~~~ 在有具體實例的時候,可以這樣使用,否則會產生 `NoMethodError: undefined method`delete' for nil:NilClass`,這在我們設計邏輯的時候要注意。 delete_all 方法和 delete 是一樣的,直接發送數據刪除的命令,看一下 api 文檔中的例子: ~~~ Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')") Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else']) Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all ~~~ #### 4.1.8.2 Destroy 刪除 destroy 方法,會觸發 model 中定義的回調(before_remove, after_remove , before_destroy 和 after_destroy),保證我們正確的操作。它也可以是類方法和實例方法,用法和前面的一樣。 需要說明,delete/delete_all 和 destroy/destroy_all 都可以作用在關系查詢結果,也就是(ActiveRecord::Relation)上,刪掉查找到的記錄。 如果你不想真正從數據庫中抹掉數據,而是給它一個刪除標注,可以使用 [https://github.com/radar/paranoia](https://github.com/radar/paranoia) 這個 gem,他會給記錄一個 deleted_at 時間戳,并且使用 `restore` 方法把它從數據庫中恢復過來,或者使用 `really_destroy!` 將它真正的刪除掉。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看