almost 4 years ago

本章的作業目標:

  • 建立使用者後台 ( 前置作業 )
  • 使用者可以在後台看到自己參加的 group
  • 使用可以在後台看到自己發表的 post
  • post 的排列要以時間 (最新 => 最舊) 順序排列
  • group 的排列要以 文章數量 當熱門度排列
  • bug 解決 => 當 group 刪除時,所屬的 posts 也要跟著刪除

建立使用者後台 ( 前置作業 )

我們要做出二個後台網頁, 網址是 account/groups 跟 account/posts

rails g controller account/groups

rails g controller account/posts

在 app/views/account/groups 跟 app/views/account/posts 這二個資料夾
各建出一個 index.html.erb 檔案,內容是空的

打開 config/routes

config/routes
Rails.application.routes.draw do

+ namespace :account do
+   resources :groups
+   resources :posts
+ end
...
...

這樣我們就能輸入後台網址,呈現空白網頁了!
localhost:3000/account/groups
localhost:3000/account/posts

navbar 裝上連到後台網址的按鈕

打開 app/views/common/_navbar.html.erb
app/views/common/_navbar.html.erb
...
...
          <ul class="dropdown-menu">
+           <li> <%= link_to("My Groups", account_groups_path) %></li> 
+           <li> <%= link_to("My Posts", account_posts_path) %></li> 
            <li> <%= link_to("帳號設定", edit_user_registration_path )%></li>
            <li> <%= link_to("登出", destroy_user_session_path, method: :delete ) %></li>
          </ul>
...
...

接下來實做出後台的內容


使用者可以在後台看到自己參加的 group

打開 app/controllers/account/groups_controller.rb

app/controllers/account/groups_controller.rb
class Account::GroupsController < ApplicationController

+ before_action :authenticate_user!
+
+ def index
+   @groups = current_user.participated_groups
+ end
end

打開 app/views/account/groups/index.html.erb

app/views/account/groups/index.html.erb
<div class="col-md-12">
  
  <h2 class="text-center"> 我加入的討論版 </h2>
  
  <table class="table">
    <thead>
      <tr>
        <th> # </th>
        <th> Title </th>
        <th> Description </th>
        <th> Post Count </th>
        <th> Last Update </th>
      </tr>
    </thead>
    
    <tbody>
      <% @groups.each do |group| %>
        <tr>
          <td> # </td>
          <td> <%= link_to(group.title, group_path(group)) %> </td> 
          <td> <%= group.description %> </td> 
          <td> <%= group.posts.count %> </td> 
          <td> <%= group.updated_at %> </td> 
        </tr>
      <% end %>
    </tbody>
  </table>
</div>


使用可以在後台看到自己發表的 post

打開 app/controllers/account/posts_controller.rb

app/controllers/account/posts_controller.rb
class Account::PostsController < ApplicationController

+ before_action :authenticate_user!
+
+ def index
+   @posts = current_user.posts
+ end
end

打開 app/views/account/posts/index.html.erb

app/views/account/posts/index.html.erb
<div class="col-md-12">

  <h2 class="text-center"> 我發表過的文章 </h2>

  <table class="table">
    <thead>
      <tr>
        <th> Content </th>
        <th> Group Name </th>
        <th> Last Update </th>
        <th colspan="2"></th>
      </tr>
    </thead>
    
    <tbody>
      <% @posts.each do |post| %>
        <tr>
          <td> <%= post.content %> </td> 
          <td> <%= post.group.title %> </td> 
          <td> <%= post.updated_at %> </td> 
          <td> <%= link_to('Edit', edit_group_post_path(post.group, post), class: "btn btn-info btn-xs") %></td>
          <td> <%= link_to('Delete', group_post_path(post.group, post), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-danger btn-xs") %></td>
        </tr>
      <% end %>
    </tbody>
  </table>
</div>


post 的排列要以時間 (最新 => 最舊) 順序排列

我們會發現, account/posts 裡的文章是從最舊 => 最新 的順序排列的
我們應該要把最新的放在最上面才對

運作邏輯是 => 修改時間 (updated_at) 用 倒序排列 (DESC)

打開 app/controllers/account/posts_controller.rb

app/controllers/account/posts_controller.rb
class Account::PostsController < ApplicationController
...
...
  def index
-   @posts = current_user.posts
+   @posts = current_user.posts.order("updated_at DESC")
  end
end

before

after


group 的排列要以 文章數量 當熱門度排列

我們在 app/views/account/groups/index.html.erb 裡面有一行

<td> <%= group.posts.count %> </td>

用來呈現該 group 裡的 post 篇數
但這行有個很大的問題就是 : 在程式的迴圈裡面跑 SQL 搜尋
如果我加入了二個 group, 實際上運作時會執行二次

(0.2ms) SELECT COUNT(*) FROM `posts` WHERE `posts`.`group_id` = 1
(0.2ms) SELECT COUNT(*) FROM `posts` WHERE `posts`.`group_id` = 2

如果我加入了 20 甚至 100 個 group 的話 ... 會是個很噁心的程式
造成網站 server 的負擔

解決方案就是,在 group 裡面加入 post_count 欄位來記錄 post 的篇數

rails g migration add_posts_count_to_group posts_count:integer

打開 db/migrate/(一堆數字)add_posts_countto_group.rb
db/migrate/(一堆數字)_add_posts_count_to_group.rb
class AddPostsCountToGroup < ActiveRecord::Migration
  def change
-   add_column :groups, :posts_count, :integer
+   add_column :groups, :posts_count, :integer, default: 0
  end
end

rake db:migrate
這樣 group 這個資料表就多了 posts_count 這個欄位

Rails 的 Model 裡面有一個內建的 counter_cache 功能幫助你記錄
只要設定好 counter_cache: :posts_count <== (你剛剛建立的欄位名稱)
只要 post 有 create 跟 destroy 的動作,就會自動在 posts_count 欄位 +1 跟 -1

打開 app/models/post.rb

app/models/post.rb
...
...
- belongs_to :group
+ belongs_to :group, counter_cache: :posts_count
...
...

功能測試

原始: Rails Console 初始畫面

測試: 新增文章

測試: 刪除文章

接下來把 account/groups 的 views 改一下,從用 SQL 一篇篇抓出數量改成直接抓 posts_count 的值

打開 app/views/account/groups/index.html.erb
app/views/account/groups/index.html.erb
...
...
-         <td> <%= group.posts.count %> </td>
+         <td> <%= group.posts.size %> </td>
...
...

bug 解決 => 當 group 刪除時,所屬的 posts 也要跟著刪除

當我們把 group 刪除掉的時候,
若所屬的 post 資料還在,就會變成所謂的孤兒資料

這會讓我們在打開 account/posts 頁面的時候跑出 error => 找不到該 post 所屬的 group (因為被刪了)

所以我們要設定當 group 刪除的時候,他所屬的 posts 也要跟著刪除

打開 app/models/group.rb

app/model/group.rb
...
...
- has_many :posts
+ has_many :posts, dependent: :destroy
...
...
← [ 2.0 ] 5. user 可以加入、退出 group [ 2.0 ] 7. Refactor Code 整理你的程式碼 →
 
comments powered by Disqus