Rails关系: follower and followed 分析.rb

http://railstutorial-china.org/book/chapter12.html#following-users

1.0

传统是3张表 users following followers -> 抽象过后是2张表 users relationships

1.1 User 关注 User

user through active_relationship has_many user

user : id | name | email
active_relationship : follower_id(主键) | followed_id
user: id | name | email

1.2

主动(我关注): user.following = user.followeds
被动(关注我): user.followers

1.3 外键:follower_id

因为 microposts 表中有识别用户的 user_id 列。这种连接两个表的列,我们称之为“外键”(foreign key)。当指向用户模型的外键为 user_id 时,Rails 会自动获知关联,因为默认情况下,Rails 会寻找名为 <class>_id 的外键,其中 <class> 是模型类名的小写形式。现在,尽管我们处理的还是用户,但识别用户使用的外键是 follower_id。这是从被关注者角度出发(是被动关系主键),foreign_key: "follower_id",而不用 followed_id.

1.3.1 foreign_key

按照约定,用来存储外键的字段名是关联名后加 _id。:foreign_key 选项可以设置要使用的外键名:
class Order < ActiveRecord::Base belongs_to :customer, class_name: "Patron", foreign_key: "patron_id" end

不管怎样,Rails 都不会自动创建外键字段,你要自己在迁移中创建。

2.0

rails g model Relationship follower_id:integer followed_id:integer

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

add_index 因为我们会通过 follower_id 和 followed_id 查找关系,所以还要为这两个列建立索引,提高查询的效率。 设置了一个“多键索引”,确保 (follower_id, followed_id) 组合是唯一的,避免多次关注同一个用户。添加索引后,如果用户试图创建重复的关系(例如使用 curl 这样的命令行工具),应用会抛出异常。

3.0 model/user.rb

has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy

4.0 model/relationship.rb

belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User"

5.0 model/user.rb

has_many :following, through: :active_relationships, source: :followed

6.0

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

7.0 关注我的人

active_relationships 换成 passive_relationships 即可

8.0 routes.rb

  resources :users do
    member do
      get :following, :followers
    end
  end 

9.0 HTML

数量统计和关注表单/
9.1 显示数量统计的局部视图

app/views/shared/_stats.html.erb
<% @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>

9.2 在首页显示数量统计

app/views/static_pages/home.html.erb
<% 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 %>

9.3 显示关注或取消关注表单的局部视图
app/views/users/_follow_form.html.erb

<% unless current_user?(@user) %>
  <div id="follow_form">
  <% if current_user.following?(@user) %>
    <%= render 'unfollow' %>
  <% else %>
    <%= render 'follow' %>
  <% end %>
  </div>
<% end %>

9.4 添加“关系”资源的路由设置
config/routes.rb
resources :relationships, only: [:create, :destroy]

9.5  关注用户的表单
app/views/users/_follow.html.erb
<%= 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 %>

9.6 取消关注用户的表单
app/views/users/_unfollow.html.erb

<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
             html: { method: :delete }) do |f| %>
  <%= f.submit "Unfollow", class: "btn" %>
<% end %>
9.7   在用户资料页面加入关注表单和数量统计
app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
  <aside class="col-md-4">
    <section>
      <h1>
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>
    </section>
    <section class="stats">
      <%= render 'shared/stats' %>
    </section>
  </aside>
  <div class="col-md-8">
    <%= render 'follow_form' if logged_in? %>
    <% if @user.microposts.any? %>
      <h3>Microposts (<%= @user.microposts.count %>)</h3>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    <% end %>
  </div>
</div>

10.0 我关注的用户列表页面和关注我的用户列表页面/

10.1

following 和 followers 动作 RED
app/controllers/users_controller.rb
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

10.2 渲染我关注的用户列表页面和关注我的用户列表页面的 show_follow 视图

app/views/users/show_follow.html.erb
<% 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>

11 关注按钮的常规实现方式

rails generate controller Relationships

11.1   RelationshipsController 的代码
app/controllers/relationships_controller.rb
RelationshipsController 的访问限制

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

12 关注按钮的 Ajax 实现方式

代码清单 12.33:使用 Ajax 处理关注用户的表单
app/views/users/_follow.html.erb
<%= 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 %>
代码清单 12.34:使用 Ajax 处理取消关注用户的表单
app/views/users/_unfollow.html.erb
<%= 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 %>
代码清单 12.35:在 RelationshipsController 中响应 Ajax 请求
app/controllers/relationships_controller.rb
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

13 动态流

代码清单 12.46:动态流的最终实现 GREEN
app/models/user.rb
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
代码清单 12.48:home 动作中分页显示的动态流
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
    if logged_in?
      @micropost  = current_user.microposts.build
      @feed_items = current_user.feed.paginate(page: params[:page])
    end
  end
  .
  .
  .
end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容