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

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 11.1 微博模型 實現微博資源的第一步是創建微博數據模型,在模型中設定微博的基本特征。和 [2.3 節](chapter2.html#the-microposts-resource)創建的模型類似,我們要實現的微博模型要包含數據驗證,以及和用戶模型之間的關聯。除此之外,我們還會做充分的測試,指定默認的排序方式,以及自動刪除已注銷用戶的微博。 如果使用 Git 做版本控制的話,和之前一樣,建議你新建一個主題分支: ``` $ git checkout master $ git checkout -b user-microposts ``` ## 11.1.1 基本模型 微博模型只需要兩個屬性:一個是 `content`,用來保存微博的內容;另一個是 `user_id`,把微博和用戶關聯起來。微博模型的結構如[圖 11.1](#fig-micropost-model) 所示。 ![micropost model 3rd edition](https://box.kancloud.cn/2016-05-11_5733306e48377.png)圖 11.1:微博數據模型 注意,在這個模型中,`content` 屬性的類型為 `text`,而不是 `string`,目的是存儲任意長度的文本。雖然我們會限制微博內容的長度不超過 140 個字符([11.1.2 節](#micropost-validations)),也就是說在 `string` 類型的 255 個字符長度的限制內,但使用 `text` 能更好地表達微博的特性,即把微博看成一段文本更符合常理。在 [11.3.2 節](#creating-microposts),會把文本字段換成多行文本字段,用于提交微博。而且,如果以后想讓微博的內容更長一些(例如包含多國文字),使用 `text` 類型處理起來更靈活。何況,在生產環境中使用 `text` 類型并[沒有什么性能差異](http://www.postgresql.org/docs/9.1/static/datatype-character.html),所以不會有什么額外消耗。 和用戶模型一樣([代碼清單 6.1](chapter6.html#listing-generate-user-model)),我們要使用 `generate model` 命令生成微博模型: ``` $ rails generate model Micropost content:text user:references ``` 這個命令會生成一個遷移文件,用于在數據庫中生成一個名為 `microposts` 的表,如[代碼清單 11.1](#listing-micropost-migration) 所示。可以和生成 `users` 表的遷移對照一下,參見[代碼清單 6.2](chapter6.html#listing-users-migration)。二者之間最大的區別是,前者使用了 `references` 類型。`references` 會自動添加 `user_id` 列(以及索引),把用戶和微博關聯起來。和用戶模型一樣,微博模型的遷移中也自動生成了 `t.timestamps`。[6.1.1 節](chapter6.html#database-migrations)說過,這行代碼的作用是添加 `created_at` 和 `updated_at` 兩列。([11.1.4 節](#micropost-refinements)和 [11.2.1 節](#rendering-microposts)會使用 `created_at` 列。) ##### 代碼清單 11.1:微博模型的遷移文件,還創建了索引 db/migrate/[timestamp]_create_microposts.rb ``` class CreateMicroposts < ActiveRecord::Migration def change create_table :microposts do |t| t.text :content t.references :user, index: true, foreign_key: true t.timestamps null: false end add_index :microposts, [:user_id, :created_at] end end ``` 因為我們會按照發布時間的倒序查詢某個用戶發布的所有微博,所以在上述代碼中為 `user_id` 和 `created_at` 列創建了索引(參見[旁注 6.2](chapter6.html#aside-database-indices)): ``` add_index :microposts, [:user_id, :created_at] ``` 我們把 `user_id` 和 `created_at` 放在一個數組中,告訴 Rails 我們要創建的是“多鍵索引”(multiple key index),因此 Active Record 會同時使用這兩個鍵。 然后像之前一樣,執行下面的命令更新數據庫: ``` $ bundle exec rake db:migrate ``` ## 11.1.2 微博模型的數據驗證 我們已經創建了基本的數據模型,下面要添加一些驗證,實現符合需求的約束。微博模型必須要有一個屬性表示用戶的 ID,這樣才能知道某篇微博是由哪個用戶發布的。實現這樣的屬性,最好的方法是使用 Active Record 關聯。[11.1.3 節](#user-micropost-associations)會實現關聯,現在我們直接處理微博模型。 我們可以參照用戶模型的測試([代碼清單 6.7](chapter6.html#listing-name-presence-test)),在 `setup` 方法中新建一個微博對象,并把它和固件中的一個有效用戶關聯起來,然后在測試中檢查這個微博對象是否有效。因為每篇微博都要和用戶關聯起來,所以我們還要為 `user_id` 屬性的存在性驗證編寫一個測試。綜上所述,測試如[代碼清單 11.2](#listing-micropost-validity-test) 所示。 ##### 代碼清單 11.2:測試微博是否有效 RED test/models/micropost_test.rb ``` require 'test_helper' class MicropostTest < ActiveSupport::TestCase def setup @user = users(:michael) # 這行代碼不符合常見做法 @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id) end test "should be valid" do assert @micropost.valid? end test "user id should be present" do @micropost.user_id = nil assert_not @micropost.valid? end end ``` 如 `setup` 方法中的注釋所說,創建微博使用的方法不符合常見做法,我們會在 [11.1.3 節](#user-micropost-associations)修正。 微博是否有效的測試能通過,但用戶 ID 存在性驗證的測試無法通過,因為微博模型目前還沒有任何驗證規則: ##### 代碼清單 11.3:**RED** ``` $ bundle exec rake test:models ``` 為了讓測試通過,我們要添加用戶 ID 存在性驗證,如[代碼清單 11.4](#listing-micropost-user-id-validation) 所示。(注意,這段代碼中 `belongs_to` 那行由[代碼清單 11.1](#listing-micropost-migration) 中的遷移自動生成。[11.1.3 節](#user-micropost-associations)會深入介紹這行代碼的作用。) ##### 代碼清單 11.4:微博模型 `user_id` 屬性的驗證 GREEN app/models/micropost.rb ``` class Micropost < ActiveRecord::Base belongs_to :user validates :user_id, presence: true end ``` 現在,整個測試組件應該都能通過: ##### 代碼清單 11.5:**GREEN** ``` $ bundle exec rake test ``` 接下來,我們要為 `content` 屬性加上數據驗證(參照 [2.3.2 節](chapter2.html#putting-the-micro-in-microposts)的做法)。和 `user_id` 一樣,`content` 屬性必須存在,而且還要限制內容的長度不能超過 140 個字符,這才是真正的“微”博。首先,我們要參照 [6.2 節](chapter6.html#user-validations)用戶模型的驗證測試,編寫一些簡單的測試,如[代碼清單 11.6](#listing-micropost-validations-tests) 所示。 ##### 代碼清單 11.6:測試微博模型的驗證 RED test/models/micropost_test.rb ``` require 'test_helper' class MicropostTest < ActiveSupport::TestCase def setup @user = users(:michael) @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id) end test "should be valid" do assert @micropost.valid? end test "user id should be present" do @micropost.user_id = nil assert_not @micropost.valid? end test "content should be present" do @micropost.content = " " assert_not @micropost.valid? end test "content should be at most 140 characters" do @micropost.content = "a" * 141 assert_not @micropost.valid? end end ``` 和 [6.2 節](chapter6.html#user-validations)一樣,[代碼清單 11.6](#listing-micropost-validations-tests)也用到了字符串連乘來測試微博內容長度的驗證: ``` $ rails console >> "a" * 10 => "aaaaaaaaaa" >> "a" * 141 => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ``` 在模型中添加的代碼基本上和用戶模型 `name` 屬性的驗證一樣([代碼清單 6.16](chapter6.html#listing-length-validation)),如[代碼清單 11.7](#listing-micropost-validations) 所示。 ##### 代碼清單 11.7:微博模型的驗證 GREEN app/models/micropost.rb ``` class Micropost < ActiveRecord::Base belongs_to :user validates :user_id, presence: true validates :content, presence: true, length: { maximum: 140 } end ``` 現在,測試組件應該能通過了: ##### 代碼清單 11.8:**GREEN** ``` $ bundle exec rake test ``` ## 11.1.3 用戶和微博之間的關聯 為 Web 應用構建數據模型時,最基本的要求是要能夠在不同的模型之間建立關聯。在這個應用中,每篇微博都屬于某個用戶,而每個用戶一般都有多篇微博。用戶和微博之間的關系在 [2.3.3 節](chapter2.html#a-user-has-many-microposts)簡單介紹過,如[圖 11.2](#fig-micropost-belongs-to-user) 和[圖 11.3](#fig-user-has-many-microposts) 所示。在實現這種關聯的過程中,我們會為微博模型和用戶模型編寫一些測試。 ![micropost belongs to user](https://box.kancloud.cn/2016-05-11_5733306e65763.png)圖 11.2:微博和所屬用戶之間的 `belongs_to`(屬于)關系![user has many microposts](https://box.kancloud.cn/2016-05-11_5733306e7b37d.png)圖 11.3:用戶和微博之間的 `has_many`(擁有多個)關系 使用本節實現的 `belongs_to`/`has_many` 關聯之后,Rails 會自動創建一些方法,如[表 11.1](#table-association-methods) 所示。注意,從表中可知,相較于下面的方法 ``` Micropost.create Micropost.create! Micropost.new ``` 我們得到了 ``` user.microposts.create user.microposts.create! user.microposts.build ``` 后者才是創建微博的正確方式,即通過相關聯的用戶對象創建。通過這種方式創建的微博,其 `user_id` 屬性會自動設為正確的值。所以,我們可以把[代碼清單 11.2](#listing-micropost-validity-test) 中的下述代碼 ``` @user = users(:michael) # 這行代碼不符合常見做法 @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id) ``` 改為 ``` @user = users(:michael) @micropost = @user.microposts.build(content: "Lorem ipsum") ``` (和 `new` 方法一樣,`build` 方法返回一個存儲在內存中的對象,不會修改數據庫。)只要關聯定義的正確,`@micropost` 變量的 `user_id` 屬性就會自動設為所關聯用戶的 ID。 表 11.1:用戶和微博之間建立關聯后得到的方法簡介 | 方法 | 作用 | | --- | --- | | `micropost.user` | 返回和微博關聯的用戶對象 | | `user.microposts` | 返回用戶發布的所有微博 | | `user.microposts.create(arg)` | 創建一篇 `user` 發布的微博 | | `user.microposts.create!(arg)` | 創建一篇 `user` 發布的微博(失敗時拋出異常) | | `user.microposts.build(arg)` | 返回一個 `user` 發布的新微博對象 | | `user.microposts.find_by(id: 1)` | 查找 `user` 發布的一篇微博,而且微博的 ID 為 1 | 為了讓 `@user.microposts.build` 這樣的代碼能使用,我們要修改用戶模型和微博模型,添加一些代碼,把這兩個模型關聯起來。[代碼清單 11.1](#listing-micropost-migration) 中的遷移已經自動添加了 `belongs_to :user`,如[代碼清單 11.9](#listing-micropost-belongs-to-user) 所示。關聯的另一頭,`has_many :microposts`,我們要自己動手添加,如[代碼清單 11.10](#listing-user-has-many-microposts) 所示。 ##### 代碼清單 11.9:一篇微博屬于(`belongs_to`)一個用戶 GREEN app/models/micropost.rb ``` class Micropost < ActiveRecord::Base belongs_to :user validates :user_id, presence: true validates :content, presence: true, length: { maximum: 140 } end ``` ##### 代碼清單 11.10:一個用戶有多篇(`has_many`)微博 GREEN app/models/user.rb ``` class User < ActiveRecord::Base has_many :microposts . . . end ``` 定義好關聯后,我們可以修改[代碼清單 11.2](#listing-micropost-validity-test) 中的 `setup` 方法了,使用正確的方式創建一個微博對象,如[代碼清單 11.11](#listing-micropost-validity-test-idiomatic) 所示。 ##### 代碼清單 11.11:使用正確的方式創建微博對象 GREEN test/models/micropost_test.rb ``` require 'test_helper' class MicropostTest < ActiveSupport::TestCase def setup @user = users(:michael) @micropost = @user.microposts.build(content: "Lorem ipsum") end test "should be valid" do assert @micropost.valid? end test "user id should be present" do @micropost.user_id = nil assert_not @micropost.valid? end . . . end ``` 當然,經過這次簡單的重構后測試組件應該還能通過: ##### 代碼清單 11.12:**GREEN** ``` $ bundle exec rake test ``` ## 11.1.4 改進微博模型 本節,我們要改進一下用戶和微博之間的關聯:按照特定的順序取回用戶的微博,并且讓微博依屬于用戶,如果用戶注銷了,就自動刪除這個用戶發布的所有微博。 ### 默認作用域 默認情況下,`user.microposts` 不能確保微博的順序,但是按照博客和 Twitter 的習慣,我們希望微博按照創建時間倒序排列,也就是最新發布的微博在前面。[[1](#fn-1)]為此,我們要使用“默認作用域”(default scope)。 這樣的功能很容易讓測試意外通過(就算應用代碼不對,測試也能通過),所以我們要使用測試驅動開發技術,確保實現的方式是正確的。首先,我們編寫一個測試,檢查數據庫中的第一篇微博和微博固件中名為 `most_recent` 的微博相同,如[代碼清單 11.13](#listing-micropost-order-test) 所示。 ##### 代碼清單 11.13:測試微博的排序 RED test/models/micropost_test.rb ``` require 'test_helper' class MicropostTest < ActiveSupport::TestCase . . . test "order should be most recent first" do assert_equal Micropost.first, microposts(:most_recent) end end ``` 這段代碼要使用微博固件,所以我們要定義固件,如[代碼清單 11.14](#listing-micropost-fixtures) 所示。 ##### 代碼清單 11.14:微博固件 test/fixtures/microposts.yml ``` orange: content: "I just ate an orange!" created_at: <%= 10.minutes.ago %> tau_manifesto: content: "Check out the @tauday site by @mhartl: http://tauday.com" created_at: <%= 3.years.ago %> cat_video: content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk" created_at: <%= 2.hours.ago %> most_recent: content: "Writing a short test" created_at: <%= Time.zone.now %> ``` 注意,我們使用嵌入式 Ruby 明確設置了 `created_at` 屬性的值。因為這個屬性由 Rails 自動更新,一般無法手動設置,但在固件中可以這么做。實際上可能不用自己設置這些屬性,因為在某些系統中固件會按照定義的順序創建。在這個文件中,最后一個固件最后創建(因此是最新的一篇微博)。但是絕不要依賴這種行為,因為并不可靠,而且在不同的系統中有差異。 現在,測試應該無法通過: ##### 代碼清單 11.15:**RED** ``` $ bundle exec rake test TEST=test/models/micropost_test.rb \ > TESTOPTS="--name test_order_should_be_most_recent_first" ``` 我們要使用 Rails 提供的 `default_scope` 方法讓測試通過。這個方法的作用很多,這里我們要用它設定從數據庫中讀取數據的默認順序。為了得到特定的順序,我們要在 `default_scope` 方法中指定 `order` 參數,按 `created_at` 列的值排序,如下所示: ``` order(:created_at) ``` 可是,這實現的是“升序”,從小到大排列,即最早發布的微博排在最前面。為了讓微博降序排列,我們要向下走一層,使用純 SQL 語句: ``` order('created_at DESC') ``` 在 SQL 中,`DESC` 表示“降序”,即新發布的微博在前面。在以前的 Rails 版本中,必須使用純 SQL 語句才能實現這個需求,但從 Rails 4.0 起,可以使用純 Ruby 句法實現: ``` order(created_at: :desc) ``` 把默認作用域加入微博模型,如[代碼清單 11.16](#listing-micropost-ordering) 所示。 ##### 代碼清單 11.16:使用 `default_scope` 排序微博 GREEN app/models/micropost.rb ``` class Micropost < ActiveRecord::Base belongs_to :user default_scope -> { order(created_at: :desc) } validates :user_id, presence: true validates :content, presence: true, length: { maximum: 140 } end ``` [代碼清單 11.16](#listing-micropost-ordering) 中使用了“箭頭”句法,表示一種對象,叫 Proc(procedure)或 lambda,即“匿名函數”(沒有名字的函數)。`-&gt;` 接受一個代碼塊([4.3.2 節](chapter4.html#blocks)),返回一個 Proc。然后在這個 Proc 上調用 `call` 方法執行其中的代碼。我們可以在控制臺中看一下怎么使用 Proc: ``` >> -> { puts "foo" } => #<Proc:0x007fab938d0108@(irb):1 (lambda)> >> -> { puts "foo" }.call foo => nil ``` (Proc 是高級 Ruby 知識,如果現在不理解也不用擔心。) 按照[代碼清單 11.16](#listing-micropost-ordering) 修改后,測試應該可以通過了: ##### 代碼清單 11.17:**GREEN** ``` $ bundle exec rake test ``` ### 依屬關系:destroy 除了設定恰當的順序外,我們還要對微博模型做一項改進。我們在 [9.4 節](chapter9.html#deleting-users)介紹過,管理員有刪除用戶的權限。那么,在刪除用戶的同時,有必要把該用戶發布的微博也刪除。 為此,我們可以把一個參數傳給 `has_many` 關聯方法,如[代碼清單 11.18](#listing-micropost-dependency) 所示。 ##### 代碼清單 11.18:確保用戶的微博在刪除用戶的同時也被刪除 app/models/user.rb ``` class User < ActiveRecord::Base has_many :microposts, dependent: :destroy . . . end ``` `dependent: :destroy` 的作用是在用戶被刪除的時候,把這個用戶發布的微博也刪除。這么一來,如果管理員刪除了用戶,數據庫中就不會出現無主的微博了。 我們可以為用戶模型編寫一個測試,證明[代碼清單 11.18](#listing-micropost-dependency) 中的代碼是正確的。我們要保存一個用戶(因此得到了用戶的 ID),再創建一個屬于這個用戶的微博,然后檢查刪除用戶后微博的數量有沒有減少一個,如[代碼清單 11.19](#listing-dependent-destroy-test) 所示。(和[代碼清單 9.57](chapter9.html#listing-delete-link-integration-test) 中“刪除”鏈接的集成測試對比一下。) ##### 代碼清單 11.19:測試 `dependent: :destroy` GREEN test/models/user_test.rb ``` require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end . . . test "associated microposts should be destroyed" do @user.save @user.microposts.create!(content: "Lorem ipsum") assert_difference 'Micropost.count', -1 do @user.destroy end end end ``` 如果[代碼清單 11.18](#listing-micropost-dependency) 正確,測試組件就應該能通過: ##### 代碼清單 11.20:**GREEN** ``` $ bundle exec rake test ```
                  <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>

                              哎呀哎呀视频在线观看