<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國際加速解決方案。 廣告
                # 12.1 “關系”模型 為了實現用戶關注功能,首先要創建一個看上去并不是那么直觀的數據模型。一開始我們可能會認為 `has_many` 關聯能滿足我們的要求:一個用戶關注多個用戶,而且也被多個用戶關注。但實際上這種實現方式有問題,下面我們會學習如何使用 `has_many :through` 解決。 和之前一樣,如果使用 Git,現在應該新建一個主題分支: ``` $ git checkout master $ git checkout -b following-users ``` ## 12.1.1 數據模型帶來的問題以及解決方法 在構建關注用戶所需的數據模型之前,我們先來分析一個典型的案例。假如一個用戶關注了另外一個用戶,比如 Calvin 關注了 Hobbes,也就是 Hobbes 被 Calvin 關注了,那么 Calvin 就是“關注人”(follower),Hobbes 則是“被關注人”(followed)。按照 Rails 默認的復數命名習慣, 我們稱關注了某個用戶的所有用戶為這個用戶的“followers”,因此,`hobbes.followers` 是一個數組,包含所有關注了 Hobbes 的用戶。不過,如果順序顛倒,這種表述就說不通了:默認情況下,所有被關注的用戶應該叫“followeds”,但是這樣說并不符合英語語法。所以,參照 Twitter 的叫法,我們把被關注的用戶叫做“following”(例如,“50 following, 75 followers”)。因此,Calvin 關注的人可以通過 `calvin.following` 數組獲取。 經過上述討論,我們可以按照[圖 12.6](#fig-naive-user-has-many-following) 中的方式構建被關注用戶的模型——一個 `following` 表和 `has_many` 關聯。由于 `user.following` 應該是一個用戶對象組成的數組,所以 `following` 表中的每一行都應該是一個用戶,通過 `followed_id` 列標識。然后再通過 `follower_id` 列建立關聯。[[2](#fn-2)]除此之外,由于每一行都是一個用戶,所以還要在表中加入用戶的其他屬性,例如名字、電子郵件地址和密碼等。 ![naive user has many following](https://box.kancloud.cn/2016-05-11_5733307a1d245.png)圖 12.6:用戶關注的人(天真方式) [圖 12.6](#fig-naive-user-has-many-following) 中的數據模型有個問題——存在非常多的冗余,每一行不僅包括了被關注用戶的 ID,還包括了他們的其他信息,而這些信息在 `users` 表中都有。 更糟糕的是,為了保存關注我的人,還需要另一個同樣冗余的 `followers` 表。這么做會導致數據模型極難維護:用戶修改名字時,不僅要修改 `users` 表中的數據,還要修改 `following` 和 `followers` 表中包含這個用戶的每一個記錄。 造成這個問題的原因是缺少了一層抽象。找到合適的抽象有一種方法:思考在應用中如何實現關注用戶的操作。[7.1.2 節](chapter7.html#a-users-resource)介紹過,REST 架構涉及到資源的創建和銷毀兩個操作。 由此引出了兩個問題:用戶關注另一個用戶時,創建了什么?用戶取消關注另一個用戶時,銷毀了什么?按照這樣的方式思考,我們會發現,在關注用戶的過程中,創建和銷毀的是兩個用戶之間的“關系”。因此,一個用戶有多個“關系”,從而通過這個“關系”得到很多我關注的人(`following`)和關注我的人(`followers`)。 在實現應用的數據模型時還有一個細節要注意:Facebook 實現的關系是對稱的,A 關注 B 時,B 也就關注了 A;而我們要實現的關系和 Twitter 類似,是不對稱的,Calvin 可以關注 Hobbes,但 Hobbes 并不需要關注 Calvin。為了區分這兩種情況,我們要使用專業的術語:如果 Calvin 關注了 Hobbes,但 Hobbes 沒有關注 Calvin,那么 Calvin 和 Hobbes 之間建立的是“主動關系”(Active Relationship),而 Hobbes 和 Calvin 之間是“被動關系”(Positive Relationship)。[[3](#fn-3)] 現在我們集中精力實現“主動關系”,即獲取我關注的用戶。[12.1.5 節](#followers)會實現“被動關系”。從[圖 12.6](#fig-naive-user-has-many-following) 中可以看出實現的方式:既然我關注的每一個用戶都由 `followed_id` 獨一無二的標識出來了,我們就可以把 `following` 表轉化成 `active_relationships` 表,刪掉用戶的屬性,然后使用 `followed_id` 從 `users` 表中獲取我關注的用戶的信息。這個數據模型如[圖 12.7](#fig-user-has-many-following) 所示。 ![user has many following 3rd edition](https://box.kancloud.cn/2016-05-11_5733307a32226.png)圖 12.7:通過“主動關系”獲取我關注的用戶 因為“主動關系”和“被動關系”最終會存儲在同一個表中,所以我們把這個表命名為“relationships”。這個表對應的模型是 `Relationship`,如[圖 12.8](#fig-relationship-model) 所示。從 [12.1.4 節](#followed-users)開始,我們會介紹如何使用這個模型同時實現“主動關系”和“被動關系”。 ![relationship model](https://box.kancloud.cn/2016-05-11_5733307a4e1bf.png)圖 12.8:Relationship 數據模型 為此,我們要生成所需的模型: ``` $ rails generate model Relationship follower_id:integer followed_id:integer ``` 因為我們會通過 `follower_id` 和 `followed_id` 查找關系,所以還要為這兩個列建立索引,提高查詢的效率,如[代碼清單 12.1](#listing-relationships-migration) 所示。 ##### 代碼清單 12.1:在 `relationships` 表中添加索引 db/migrate/[timestamp]_create_relationships.rb ``` class CreateRelationships < ActiveRecord::Migration def change create_table :relationships do |t| t.integer :follower_id t.integer :followed_id t.timestamps null: false end add_index :relationships, :follower_id add_index :relationships, :followed_id add_index :relationships, [:follower_id, :followed_id], unique: true end end ``` 在[代碼清單 12.1](#listing-relationships-migration) 中,我們還設置了一個“多鍵索引”,確保 (`follower_id, followed_id`) 組合是唯一的,避免多次關注同一個用戶。(可以和[代碼清單 6.28](chapter6.html#listing-email-uniqueness-index) 中保持電子郵件地址唯一的索引比較一下。)從 [12.1.4 節](#followed-users)起會看到,用戶界面不會允許這樣的事發生,但添加索引后,如果用戶試圖創建重復的關系(例如使用 `curl` 這樣的命令行工具),應用會拋出異常。 為了創建 `relationships` 表,和之前一樣,我們要執行遷移: ``` $ bundle exec rake db:migrate ``` ## 12.1.2 用戶和“關系”模型之間的關聯 在獲取我關注的人和關注我的人之前,我們要先建立用戶和“關系”模型之間的關聯。一個用戶有多個“關系”(`has_many`), 因為一個“關系”涉及到兩個用戶,所以“關系”同時屬于(`belongs_to`)該用戶和被關注的用戶。 和 [11.1.3 節](chapter11.html#user-micropost-associations)創建時微博一樣,我們要通過關聯創建“關系”,如下面的代碼所示: ``` user.active_relationships.build(followed_id: ...) ``` 此時,你可能想在應用中加入類似于 [11.1.3 節](chapter11.html#user-micropost-associations)使用的代碼。我們要添加的代碼確實很像,但有兩處不同。 首先,把用戶和微博關聯起來時我們寫成: ``` class User < ActiveRecord::Base has_many :microposts . . . end ``` 之所以可以這么寫,是因為 Rails 會尋找 `:microposts` 符號對應的模型,即 `Micropost`。[[4](#fn-4)]可是現在模型名為 `Relationship`,而我們想寫成: ``` has_many :active_relationships ``` 所以要告訴 Rails 模型的類名。 其次,前面在微博模型中是這么寫的: ``` class Micropost < ActiveRecord::Base belongs_to :user . . . end ``` 之所以可以這么寫,是因為 `microposts` 表中有識別用戶的 `user_id` 列([11.1.1 節](chapter11.html#the-basic-model))。這種連接兩個表的列,我們稱之為“外鍵”(foreign key)。當指向用戶模型的外鍵為 `user_id` 時,Rails 會自動獲知關聯,因為默認情況下,Rails 會尋找名為 `&lt;class&gt;_id` 的外鍵,其中 `&lt;class&gt;` 是模型類名的小寫形式。[[5](#fn-5)]現在,盡管我們處理的還是用戶,但識別用戶使用的外鍵是 `follower_id`,所以要告訴 Rails 這一變化。 綜上所述,用戶和“關系”模型之間的關聯如[代碼清單 12.2](#listing-user-relationships-association) 和[代碼清單 12.3](#listing-relationship-belongs-to) 所示。 ##### 代碼清單 12.2:實現“主動關系”中的 `has_many` 關聯 app/models/user.rb ``` class User < ActiveRecord::Base has_many :microposts, dependent: :destroy has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy . . . end ``` (因為刪除用戶時也要刪除涉及這個用戶的“關系”,所以我們在關聯中加入了 `dependent: :destroy`。) ##### 代碼清單 12.3:在“關系”模型中添加 `belongs_to` 關聯 app/models/relationship.rb ``` class Relationship < ActiveRecord::Base belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" end ``` 盡管 [12.1.5 節](#followers)才會用到 `followed` 關聯,但同時添加易于理解。 建立上述關聯后,會得到一系列類似于[表 11.1](chapter11.html#table-association-methods) 中的方法,如[表 12.1](#table-association-methods-relationships) 所示。 表 12.1:用戶和“主動關系”關聯后得到的方法簡介 | 方法 | 作用 | | --- | --- | | `active_relationship.follower` | 獲取關注我的用戶 | | `active_relationship.followed` | 獲取我關注的用戶 | | `user.active_relationships.create(followed_id: other_user.id)` | 創建 `user` 發起的“主動關系” | | `user.active_relationships.create!(followed_id: other_user.id)` | 創建 `user` 發起的“主動關系”(失敗時拋出異常) | | `user.active_relationships.build(followed_id: other_user.id)` | 構建 `user` 發起的“主動關系”對象 | ## 12.1.3 數據驗證 在繼續之前,我們要在“關系”模型中添加一些驗證。測試([代碼清單 12.4](#listing-relationship-validation-tests))和應用代碼([代碼清單 12.5](#listing-relationship-validations))都非常直觀。和生成的用戶固件一樣([代碼清單 6.29](chapter6.html#listing-default-fixtures)),生成的“關系”固件也違背了遷移中的唯一性約束([代碼清單 12.1](#listing-relationships-migration))。這個問題的解決方法也和之前一樣([代碼清單 6.30](chapter6.html#listing-empty-fixtures))——刪除自動生成的固件,如[代碼清單 12.6](#listing-empty-relationship-fixture) 所示。 ##### 代碼清單 12.4:測試“關系”模型中的驗證 test/models/relationship_test.rb ``` require 'test_helper' class RelationshipTest < ActiveSupport::TestCase def setup @relationship = Relationship.new(follower_id: 1, followed_id: 2) end test "should be valid" do assert @relationship.valid? end test "should require a follower_id" do @relationship.follower_id = nil assert_not @relationship.valid? end test "should require a followed_id" do @relationship.followed_id = nil assert_not @relationship.valid? end end ``` ##### 代碼清單 12.5:在“關系”模型中添加驗證 app/models/relationship.rb ``` class Relationship < ActiveRecord::Base belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" validates :follower_id, presence: true validates :followed_id, presence: true end ``` ##### 代碼清單 12.6:刪除“關系”固件 test/fixtures/relationships.yml ``` # empty ``` 現在,測試應該可以通過: ##### 代碼清單 12.7:**GREEN** ``` $ bundle exec rake test ``` ## 12.1.4 我關注的用戶 現在到“關系”的核心部分了——獲取我關注的用戶(`following`)和關注我的用戶(`followers`)。這里我們要首次用到 `has_many :through` 關聯:用戶通過“關系”模型關注了多個用戶,如[圖 12.7](#fig-user-has-many-following) 所示。默認情況下,在 `has_many :through` 關聯中,Rails 會尋找關聯名單數形式對應的外鍵。例如: ``` has_many :followeds, through: :active_relationships ``` Rails 發現關聯名是“followeds”,把它變成單數形式“followed”,因此會在 `relationships` 表中獲取一個由 `followed_id` 組成的集合。不過,[12.1.1 節](#a-problem-with-the-data-model-and-a-solution)說過,寫成 `user.followeds` 有點說不通,所以我們會使用 `user.following`。Rails 允許定制默認生成的關聯方法:使用 `source` 參數指定 `following` 數組由 `followed_id` 組成,如[代碼清單 12.8](#listing-has-many-following-through-active-relationships) 所示。 ##### 代碼清單 12.8:在用戶模型中添加 `following` 關聯 app/models/user.rb ``` class User < ActiveRecord::Base has_many :microposts, dependent: :destroy has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy has_many :following, through: :active_relationships, source: :followed . . . end ``` 定義這個關聯后,我們可以充分利用 Active Record 和數組的功能。例如,可以使用 `include?` 方法([4.3.1 節](chapter4.html#arrays-and-ranges))檢查我關注的用戶中有沒有某個用戶,或者通過關聯查找一個用戶: ``` user.following.include?(other_user) user.following.find(other_user) ``` 很多情況下我們都可以把 `following` 當成數組來用,Rails 會使用特定的方式處理 `following`,所以這么做很高效。例如: ``` following.include?(other_user) ``` 看起來好像是要把我關注的所有用戶都從數據庫中讀取出來,然后再調用 `include?`。其實不然,為了提高效率,Rails 會直接在數據庫層執行相關的操作。(和 [11.2.1 節](chapter11.html#rendering-microposts)使用 `user.microposts.count` 獲取數量一樣,都直接在數據庫中操作。) 為了處理關注用戶的操作,我們要定義兩個輔助方法:`follow` 和 `unfollow`。這樣我們就可以寫 `user.follow(other_user)`。我們還要定義 `following?` 布爾值方法,檢查一個用戶是否關注了另一個用戶。[[6](#fn-6)] 現在是編寫測試的好時機,因為我們還要等很久才會開發關注用戶的網頁界面,如果一直沒人監管,很難向前推進。我們可以為用戶模型編寫一個簡短的測試,先調用 `following?` 方法確認某個用戶沒有關注另一個用戶,然后調用 `follow` 方法關注這個用戶,再使用 `following?` 方法確認關注成功了,最后調用 `unfollow` 方法取消關注,并確認操作成功,如[代碼清單 12.9](#listing-utility-method-tests) 所示。 ##### 代碼清單 12.9:測試關注用戶相關的幾個輔助方法 RED test/models/user_test.rb ``` require 'test_helper' class UserTest < ActiveSupport::TestCase . . . test "should follow and unfollow a user" do michael = users(:michael) archer = users(:archer) assert_not michael.following?(archer) michael.follow(archer) assert michael.following?(archer) michael.unfollow(archer) assert_not michael.following?(archer) end end ``` 參照[表 12.1](#table-association-methods-relationships),我們要使用 `following` 關聯定義 `follow`、`unfollow` 和 `following?` 方法,如[代碼清單 12.10](#listing-follow-unfollow-following) 所示。(注意,只要可能,我們就省略 `self`。) ##### 代碼清單 12.10:定義關注用戶相關的幾個輔助方法 GREEN app/models/user.rb ``` class User < ActiveRecord::Base . . . def feed . . . end # 關注另一個用戶 def follow(other_user) active_relationships.create(followed_id: other_user.id) end # 取消關注另一個用戶 def unfollow(other_user) active_relationships.find_by(followed_id: other_user.id).destroy end # 如果當前用戶關注了指定的用戶,返回 true def following?(other_user) following.include?(other_user) end private . . . end ``` 現在,測試能通過了: ##### 代碼清單 12.11:**GREEN** ``` $ bundle exec rake test ``` ## 12.1.5 關注我的人 “關系”的最后一部分是定義與 `user.following` 對應的 `user.followers` 方法。從[圖 12.7](#fig-user-has-many-following) 中得知,獲取關注我的人所需的數據都已經存在于 `relationships` 表中(我們要參照[代碼清單 12.2](#listing-user-relationships-association) 中實現 `active_relationships` 表的方式)。其實我們要使用的方法和實現我關注的人一樣,只要對調 `follower_id` 和 `followed_id` 的位置,并把 `active_relationships` 換成 `passive_relationships` 即可,如[圖 12.9](#fig-user-has-many-followers) 所示。 ![user has many followers 3rd edition](https://box.kancloud.cn/2016-05-11_5733307a6029b.png)圖 12.9:通過“被動關系”獲取關注我的用戶 參照[代碼清單 12.8](#listing-has-many-following-through-active-relationships),我們可以使用[代碼清單 12.12](#listing-has-many-following-through-passive-relationships) 中的代碼實現[圖 12.9](#fig-user-has-many-followers) 中的模型。 ##### 代碼清單 12.12:使用“被動關系”實現 `user.followers` app/models/user.rb ``` class User < ActiveRecord::Base has_many :microposts, dependent: :destroy has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy has_many :passive_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy has_many :following, through: :active_relationships, source: :followed has_many :followers, through: :passive_relationships, source: :follower . . . end ``` 值得注意的是,其實我們可以省略 `followers` 關聯中的 `source` 參數,直接寫成: ``` has_many :followers, through: :passive_relationships ``` 因為 Rails 會把“followers”轉換成單數“follower”,然后查找名為 `follower_id` 的外鍵。[代碼清單 12.12](#listing-has-many-following-through-passive-relationships) 之所以保留了 `source` 參數,是為了和 `has_many :following` 關聯的結構保持一致。 我們可以使用 `followers.include?` 測試這個數據模型,如[代碼清單 12.13](#listing-followers-test) 所示。(這段測試本可以使用與 `following?` 方法對應的 `followed_by?` 方法,但應用中用不到,所以沒這么做。) ##### 代碼清單 12.13:測試 `followers` 關聯 GREEN test/models/user_test.rb ``` require 'test_helper' class UserTest < ActiveSupport::TestCase . . . test "should follow and unfollow a user" do michael = users(:michael) archer = users(:archer) assert_not michael.following?(archer) michael.follow(archer) assert michael.following?(archer) assert archer.followers.include?(michael) michael.unfollow(archer) assert_not michael.following?(archer) end end ``` 我們只在[代碼清單 12.9](#listing-utility-method-tests) 的基礎上增加了一行代碼,但若想讓這個測試通過,很多事情都要正確處理才行,所以足以測試[代碼清單 12.12](#listing-has-many-following-through-passive-relationships) 中的關聯。 現在,整個測試組件都能通過: ``` $ 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>

                              哎呀哎呀视频在线观看