about 2 years ago

備註:

  1. 記得弄個新branch
    $ git checkout master
    $ git checkout -b following-users
    
  2. map怎麼寫

重點:

  1. hobbes.followers: 有哪些追隨者
  2. calvin.following: 追隨了哪些人
  3. 用Relationship model 來當中介可以簡化結構 $ rails generate model Relationship follower_id:integer followed_id:integer
  4. 為了效率,所以增添index
    Because we will be finding relationships by follower_id and by followed_id, we should add an index on each column for efficiency, as shown in Listing 12.1.

    db/migrate/[timestamp]_create_relationships.rb (Listing 12.1)
     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
    

  5. add_index :relationships, [:follower_id, :followed_id], unique: true這行可以避免user重覆跟隨另一個user
    Listing 12.1 also includes a multiple-key index that enforces uniqueness on (follower_id, followed_id) pairs, so that a user can’t follow another user more than once. (Compare to the email uniqueness index from Listing 6.28 and the multiple-key index in Listing 11.1.) As we’ll see starting in Section 12.1.4, our user interface won’t allow this to happen, but adding a unique index arranges to raise an error if a user tries to create duplicate relationships anyway (for example, by using a command-line tool such as curl).

  6. 因為都是User,所以另外取名會比較好記。取名作follower跟followes,但其實都是連結回去User Class

    app/models/relationship.rb (Listing 12.3)
     class Relationship < ActiveRecord::Base
    belongs_to :follower, class_name: "User"
    belongs_to :followed, class_name: "User"
    end
    

    這邊是跟隨者has_many :active_relationships, class_name: "Relationship",foreign_key: "follower_id",dependent: :destroy

    app/models/user.rb(Listing 12.2)
     class User < ActiveRecord::Base
    has_many :microposts, dependent: :destroy
    has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
    end
    

    這邊是跟隨誰:has_many :following, through: :active_relationships, source: :followed

    app/models/user.rb (Listing 12.8)
     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
    


    Rails would see “followeds” and use the singular “followed”, assembling a collection using the followed_id in the relationships table. But, as noted in Section 12.1.1, user.followeds is rather awkward, so we’ll write user.following instead.
    Naturally, Rails allows us to override the default, in this case using the source parameter (as shown in Listing 12.8), which explicitly tells Rails that the source of the following array is the set of followed ids.
    跟隨者:是active的關係
    跟隨誰:是passive的關係
    其實都是連到Relationship這個table,這樣取名(:active_relationships:passive_relationships)是為了比較好了解。要用class_name
    然後要去找到特定的foreign_key,active去找follower_id,passive去找followed_id

    app/models/user.rb (Listing 12.12)
     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
    
  7. 跟隨誰,應該是用followed,不過英文語法不對,所以改成following,不過還是要讓rails知道要去哪裡找,所以要加上source: :followed

    app/models/user.rb (Listing 12.8)
     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
    

    Rails would see “followeds” and use the singular “followed”, assembling a collection using the followed_id in the relationships table. But, as noted in Section 12.1.1, user.followeds is rather awkward, so we’ll write user.following instead. Naturally, Rails allows us to override the default, in this case using the source parameter (as shown in Listing 12.8), which explicitly tells Rails that the source of the following array is the set of followed ids.

  8. 跟隨跟不跟隨

    app/models/user.rb (Listing 12.10)
     class User < ActiveRecord::Base
    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
    def following?(other_user)
    following.include?(other_user)
    end
    end
    

    Follows a user.

    app/models/user.rb (Listing 12.10)
     class User < ActiveRecord::Base
    def follow(other_user)
    active_relationships.create(followed_id: other_user.id)
    end
    end
    

    Unfollows a user.

    app/models/user.rb (Listing 12.10)
     class User < ActiveRecord::Base
    def unfollow(other_user)
    active_relationships.find_by(followed_id: other_user.id).destroy
    end
    end
    

    Returns true if the current user is following the other user.

    app/models/user.rb (Listing 12.10)
     class User < ActiveRecord::Base
    def following?(other_user)
    following.include?(other_user)
    end
    end
    
  9. user.following => 會利用follower_id這個foreign key,去找active_relationships的followed_id (用source告知)
    (會利用follower_id:跟隨者有幾個被跟隨者)
    Figure 12.7: A model of followed users through active relationships.


    user.followers => 會利用followed_id這個foreign key,去找passive_relationships的follower_id (用source告知)
    (會利用followed_id:被跟隨的這個人有幾個跟隨者)
    Figure 12.9: A model for user followers through passive relationships.

    app/models/user.rb (Listing 12.12)
     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
    

    其實follower_id跟followed_id都是跑回去User放在relationship裡面的foreign_key

    app/models/relationship.rb (Listing 12.3)
     class Relationship < ActiveRecord::Base
    belongs_to :follower, class_name: "User"
    belongs_to :followed, class_name: "User"
    end
    

  10. 12.2.1 Sample following data 可以先用db/seeds.rb來互相跟隨

  11. 12.2.2 Stats and a follow form

    config/routes.rb(Listing 12.15)
     Rails.application.routes.draw do
    resources :users do
    member do
      get :following, :followers
    end
    end
    resources :account_activations, only: [:edit]
    resources :password_resets,     only: [:new, :create, :edit, :update]
    resources :microposts,          only: [:create, :destroy]
    end
    

    /users/1/following/users/1/followers
    You might suspect that the URLs for following and followers will look like /users/1/following and /users/1/followers, and that is exactly what the code in Listing 12.15 arranges. Since both pages will be showing data, the proper HTTP verb is a GET request, so we use the get method to arrange for the URLs to respond appropriately. Meanwhile, the member method arranges for the routes to respond to URLs containing the user id. The other possibility, collection, works without the id, so that
    /users/tigers

    resources :users do
    collection do
    get :tigers
    end
    end
    

    would respond to the URL /users/tigers (presumably to display all the tigers in our application).

  12. 用partial來做view

    app/views/shared/_stats.html.erb(Listing 12.16)
     <% @user ||= current_user %>
    <div class="stats">
    <a href="<%= following_user_path(@user) %>">
    <strong id="following" class="stat">
      <%= @user.following.count %>
    </strong>
    following
    </a>
    <a href="<%= followers_user_path(@user) %>">
    <strong id="followers" class="stat">
      <%= @user.followers.count %>
    </strong>
    followers
    </a>
    </div>
    
  13. count可以在資料庫裡面直接計算數字出來,比較有效率
    As in that case, Rails calculates the count directly in the database for efficiency.

  14. partial的使用方法:<%= render 'shared/stats' %>

    app/views/static_pages/home.html.erb(Listing 12.17)
     <% if logged_in? %>
    <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="stats">
        <%= render 'shared/stats' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3>Micropost Feed</h3>
      <%= render 'shared/feed' %>
    </div>
    </div>
    <% else %>
    .
    .
    .
    <% end %>
    
  15. _follow_form.html.erb這個partial,用render執行另外兩個partial:_follow.html.erb_unfollow.html.erb

    12.19: A partial for a follow/unfollow form.)
     <% unless current_user?(@user) %>
    <div id="follow_form">
    <% if current_user.following?(@user) %>
    <%= render 'unfollow' %>
    <% else %>
    <%= render 'follow' %>
    <% end %>
    </div>
    <% end %>
    

    跟隨的partial,要記得hidden_field_tag:followed_idcreate action
    這邊是用build
    其實只是個button(還不能作用,因為還沒有寫好controller,要到12.2.4才行)

    12.21: A form for following a user.)
     <%= form_for(current_user.active_relationships.build) do |f| %>
    <div><%= hidden_field_tag :followed_id, @user.id %></div>
    <%= f.submit "Follow", class: "btn btn-primary" %>
    <% end %>
    

    解除跟隨的partial,這邊是delete,其實只是個button(還不能作用,因為還沒有寫好controller,要到12.2.4才行)

    12.22: A form for unfollowing a user.)
     <%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
             html: { method: :delete }) do |f| %>
    <%= f.submit "Unfollow", class: "btn" %>
    <% end %>
    
  16. Listing12.16的stats parital裡面的<%= following_user_path(@user) %><%= followers_user_path(@user) %>必須要靠Listing12.15裡面的member去寫出來,可是Controller跟View都還沒寫好,就要看Listing 12.25跟Listing 12.26

    app/views/shared/_stats.html.erb(Listing 12.16)
     <% @user ||= current_user %>
    <div class="stats">
    <a href="<%= following_user_path(@user) %>">
    <strong id="following" class="stat">
      <%= @user.following.count %>
    </strong>
    following
    </a>
    <a href="<%= followers_user_path(@user) %>">
    <strong id="followers" class="stat">
      <%= @user.followers.count %>
    </strong>
    followers
    </a>
    </div>
    
    config/routes.rb(Listing 12.15)
     Rails.application.routes.draw do
    resources :users do
    member do
      get :following, :followers
    end
    end
    end
    

    註:member寫好會變這樣,網址會是:/users/1/following/users/1/followers但要記得回去Controller寫Action/Method = followingfollowers
    HTTP request URL Action Named route
    GET /users/1/following following following_user_path(1)
    GET /users/1/followers followers followers_user_path(1)

    app/controllers/users_controller.rb(Listing 12.25)
     class UsersController < ApplicationController
    before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
                                        :following, :followers]
    def following
    @title = "Following"
    @user  = User.find(params[:id])
    @users = @user.following.paginate(page: params[:page])
    render 'show_follow'
    end
    def followers
    @title = "Followers"
    @user  = User.find(params[:id])
    @users = @user.followers.paginate(page: params[:page])
    render 'show_follow'
    end
    private
    end
    
    app/views/users/show_follow.html.erb(Listing 12.26)
     <% provide(:title, @title) %>
    <div class="row">
    <aside class="col-md-4">
    <section class="user_info">
      <%= gravatar_for @user %>
      <h1><%= @user.name %></h1>
      <span><%= link_to "view my profile", @user %></span>
      <span><b>Microposts:</b> <%= @user.microposts.count %></span>
    </section>
    <section class="stats">
      <%= render 'shared/stats' %>
      <% if @users.any? %>
        <div class="user_avatars">
          <% @users.each do |user| %>
            <%= link_to gravatar_for(user, size: 30), user %>
          <% end %>
        </div>
      <% end %>
    </section>
    </aside>
    <div class="col-md-8">
    <h3><%= @title %></h3>
    <% if @users.any? %>
      <ul class="users follow">
        <%= render @users %>
      </ul>
      <%= will_paginate %>
    <% end %>
    </div>
    </div>
    
  17. 12.2.4:開始讓button可以用
    $ rails generate controller Relationships
    幫用戶A在用戶B的頁面,看下follow button(12.21)之後,會將:followed_id送到Relationships Controllercreate method去 (12.32),執行user = User.find(params[:followed_id]),接著執行current_user.follow(user),會跑回去12.10的follow methodactive_relationships.create(followed_id: other_user.id),在Relationships Model的table裡面新增一列

    app/views/users/_follow.html.erb(Listing 12.21)
     <%= form_for(current_user.active_relationships.build) do |f| %>
    <div><%= hidden_field_tag :followed_id, @user.id %></div>
    <%= f.submit "Follow", class: "btn btn-primary" %>
    <% end %>
    
    app/views/users/_unfollow.html.erb(Listing 12.22)
     <%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
             html: { method: :delete }) do |f| %>
    <%= f.submit "Unfollow", class: "btn" %>
    <% end %>
    
    app/models/user.rb(Listing 12.10)
    class User < ActiveRecord::Base
    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
    def following?(other_user)
    following.include?(other_user)
    end
    end
    
    app/controllers/relationships_controller.rb(Listing 12.32)
     class RelationshipsController < ApplicationController
    before_action :logged_in_user
    def create
    user = User.find(params[:followed_id])
    current_user.follow(user)
    redirect_to user
    end
    def destroy
    user = Relationship.find(params[:id]).followed
    current_user.unfollow(user)
    redirect_to user
    end
    end
    

  18. 開始用ajax,只要在view的地方加remote: true,然後在controller也要加東西
    就好 12.2.5 A working follow button with Ajax

    app/views/users/_follow.html.erb (Listing 12.33)
     <%= form_for(current_user.active_relationships.build, remote: true) do |f| %>
    <div><%= hidden_field_tag :followed_id, @user.id %></div>
    <%= f.submit "Follow", class: "btn btn-primary" %>
    <% end %>
    
    app/views/users/_unfollow.html.erb (Listing 12.34)
     <%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
             html: { method: :delete },
             remote: true) do |f| %>
    <%= f.submit "Unfollow", class: "btn" %>
    <% end %>
    
    app/controllers/relationships_controller.rb (Listing 12.35)
     class RelationshipsController < ApplicationController
    before_action :logged_in_user
    def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
    end
    def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
    end
    end
    

    這樣的寫法,即使是JavaScript 被停用了還是可以使用,不過還是要加點東西

    format.html { redirect_to @user }
    format.js
    
    config/application.rb(Listing 12.3)
    module SampleApp
    class Application < Rails::Application
    # Include the authenticity token in remote forms.
    
    config.action_view.embed_authenticity_token_in_remote_forms = true
    end
    end
    

    而且還要去把這兩個檔案寫好:create.js.erbdestroy.js.erb

    app/views/relationships/create.js.erb (Listing 12.37)
     $("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>");
    $("#followers").html('<%= @user.followers.count %>');
    
    app/views/relationships/destroy.js.erb (Listing 12.38)
     $("#follow_form").html("<%= escape_javascript(render('users/follow')) %>");
    $("#followers").html('<%= @user.followers.count %>');
    
  19. map怎麼用?
    Micropost.where("user_id = ?", id)
    Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
    [1, 2, 3, 4].map { |i| i.to_s }
    [1, 2, 3, 4].map(&:to_s)
    User.first.following.map(&:id)
    User.first.following_ids
    User.first.following_ids.join(', ')
    Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
    有5000 users就爆了

  20. subselect 子查詢
    Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
    Micropost.where("user_id IN (:following_ids) OR user_id = :user_id",
    following_ids: following_ids, user_id: id)

    following_ids = "SELECT followed_id FROM relationships
    WHERE follower_id = :user_id"

    大概是這個樣子:
    SELECT * FROM microposts
    WHERE user_id IN (SELECT followed_id FROM relationships
    WHERE follower_id = 1)
    OR user_id = 1

    app/models/user.rb(Listing 12.46)
    class User < ActiveRecord::Base
    def feed
    following_ids = "SELECT followed_id FROM relationships
                     WHERE  follower_id = :user_id"
    Micropost.where("user_id IN (#{following_ids})
                     OR user_id = :user_id", user_id: id)
    end
    end
    
← ROR TUTORIAL (3RD ED.) Ch11 User microposts 如何使用Git →