over 3 years ago

本章的作業目標:

  • 安裝 gem "devise"
  • 在 navbar 安裝登入/登出按鈕
  • 調整修改 devise views ( 註冊, 登入, 修改帳號頁面 ... etc)
  • 利用 before_action :authenticate_user! 來做要求登入的設定
  • 客製化 devise => 新增一個 "name" 的欄位
  • 使用者功能 整合進 group / post 裡面 (作者機制)
  • 只有作者才能有 group / post 的修改/刪除權限

安裝 gem "devise"

gemfile 插入 『 gem 'devise' 』

gemfile
gem "devise", "~> 3.4.1"

bundle install
安裝新增的 gem

設定 Devise

rails g devise:install
devise 安裝

rails g devise user
建立 user 功能

rake db:migrate
由於需要一個資料庫來儲存使用者資料
devise 很聰明的幫我們把相關設定都做好了
所以只需要執行這行來建立 User 的資料庫

rails g devise:views
叫出(原本是隱藏的) devise views
未來可以客製化修改

別忘了重開 rails server


在 navbar 安裝登入/登出按鈕

app/views/common/_navbar.html.erb
...
...
+       <% if !current_user %>
+         <li> <%= link_to("註冊", new_user_registration_path) %> </li>
-         <li> <%= link_to("登入", "#") %> </li>
+         <li> <%= link_to("登入", new_user_session_path) %> </li>
+       <% else %>
+         <li class="dropdown">
+           <a href="#" class="dropdown-toggle" data-toggle="dropdown">
+             Hi!, <%= current_user.email %>
+             <b class="caret"></b>
+           </a>
+           <ul class="dropdown-menu">
+             <li> <%= link_to("登出", destroy_user_session_path, method: :delete) %> </li>
+           </ul>
+         </li>
+       <% end %>
...
...
app/assets/javascripts/application.js
...(略)

//= require turbolinks
+ //= require bootstrap/dropdown
//= require bootstrap/alert
//= require_tree .

...(略)

調整修改 devise views ( 註冊, 登入, 修改帳號頁面 ... etc)

因為導入 bootstrap ,所以跟原始的 CSS 設定有衝突導致版面炸掉
我們在舊版本是用手動修改的方式處理

現在可以直接用作者開發的新功能,自動化幫我們處理,讓 deivse views 可以支援 bootstrap 3 的 css 設定

打開 Gemfile

原本

Gemfile
...
...
gem "simple_form"
...
...

改成

Gemfile
...
...
gem "simple_form", "~> 3.1.0.rc2", github: "plataformatec/simple_form", branch: "master"
...
...

更新安裝 gem
bundle install

安裝支援 bootstrap 3 套件
$ rails generate simple_form:install --bootstrap

before

after

--

利用 before_action :authenticate_user! 來做要求登入的設定

在 groups_controller.rb 加入

before_action :authenticate_user!  

這是 devise 內建的功能,只要把它放進 controller 裡面,
就會自動驗證使用者是否入

if yes => 繼續下面的程序
if no => 轉到登入畫面

我們可以把這一行放進前面做的二個 controller : groups 跟 posts 裡面

app/controllers/groups_controller.rb
class GroupsController < ApplicationController

  before_action :authenticate_user!
...
...
app/controllers/posts_controller.rb
class PostsController < ApplicationController

  before_action :authenticate_user!
...
...

new, create, edit, update, destroy 等 action 必須先登入才能操作

我們會發現,不是所有 action 都一定要登入才行
只有跟 新增 / 修改 / 刪除 有關的 action 才需要先登入

app/controllers/groups_controller.rb
class GroupsController < ApplicationController
    
- before_action :authenticate_user!
+ before_action :authenticate_user!, only: [:new, :edit, :create, :update, :destroy] 
...
...

posts_controller 由於只有以上五個 action, 就不需要再設定 only:


客製化 devise => 新增一個 "name" 的欄位

devise 內建的資料格式並沒有 name 這個欄位,所以我們要客製化一個出來

rails g migration add_name_to_user
新增一個資料庫異動設定, 名稱是 add_name_to_user ( user 表單新增一個 name 欄位)

打開新增的檔案, 位於 db/migrate/(一堆數字)_add_name_to_user.rb

db/migrate/(一堆數字)_add_name_to_user.rb
class AddNameToUser < ActiveRecord::Migration
  def change
+   add_column :users, :name, :string
  end
end

接下來跑 rake db:migrate

這樣 user 資料庫就有 name 這個欄位了

客製化 devise 的 views => 註冊頁面新增 name 欄位

打開 app/views/devise/registrations/new.html.erb

app/views/devise/registrations/new.html.erb
...
...
  <div class="form-inputs">
    <%= f.input :email, required: true, autofocus: true %>
+   <%= f.input :name,  required: true %>
    <%= f.input :password, required: true, hint: ("#{@minimum_password_length} characters minimum" if @validatable) %>
    <%= f.input :password_confirmation, required: true %>
  </div>
...

現在還只是前端的部分完成,後端還要再加一個設定,才能讓 name 輸入的值真正存到資料庫裡面
(還記得前面說的 strong_params 嗎?)

加入 strong_parameters 與 devise 整合的 hack

打開 app/controller/application_controller

app/controller/application_controller.rb
class ApplicationController < ActionController::Base
...
...

+ before_filter :configure_permitted_parameters, if: :devise_controller?


+  protected

+  def configure_permitted_parameters
+   devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:name, :email, :password, :password_confirmation) }
+  end
end

這樣註冊新會員功能就完成了, 請新建立一個測試用帳號 來測試功能

navbar 上的 hi! email 改成 hi! name

既然我們都能讓會員取名字了, navbar 上的 greeting 也該改成 name 吧?

打開 app/views/common/_navbar.html.erb

app/views/common/_navbar.html.erb
...
...
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown">
-           Hi!, <%= current_user.email %>
+           Hi!, <%= current_user.name %>  
            <b class="caret"></b>
          </a>
...
...

before

after

新增 "帳號設定" 功能,並能修改自己帳號的 name

我們在前面章節也有創造一個帳號,但是卻還沒命名

所以需要做一個 "使用者帳號設定" 頁面來把還沒命名的帳號命名

Devise 已經幫我們建好內建的功能

打開 app/views/common/_navbar.html.erb

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

修改 edit_account 的 strong_parameters

app/controller/application_controller
...
...
   def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:name, :email, :password, :password_confirmation) }
+   devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:name, :email, :password, :password_confirmation, :current_password) }
   end
end


使用者功能 整合進 group / post 裡面 (作者機制)

group 資料表新增 user_id 欄位

rails g migration add_user_id_to_group

打開 db/migrate/(一堆數字)add_user_id_togroup.rb

db/migrate/(一堆數字)_add_user_id_to_group.rb
class AddUserIdToGroup < ActiveRecord::Migration
  def change
+  add_column :groups, :user_id, :integer
  end
end

rake db:migrate
這樣 group 資料庫裡面,就多了 user_id 欄位

對 migration 想了解更多的 可參考 Rails 資料庫遷移(中文) / Rails 資料庫遷移(英文)

設定 user 跟 group 之間的資料庫關聯 ( database relationship )

打開 app/models/user.rb

app/models/user.rb
class User < ActiveRecord::Base
...
...
+ has_many :groups
  
end

打開 app/models/group.rb

app/models/group.rb
class Group < ActiveRecord::Base
  validates :title, presence: true
 
  has_many :posts
 
+ belongs_to :owner, class_name: "User", foreign_key: :user_id
end

延伸閱讀 : Rails Active Record 關聯(中文)


只有作者才能有 group / post 的修改/刪除權限

只有作者才能有 group 的修改/刪除權限

打開 app/controllers/groups_controller.rb
只要找出 edit, create, update, destroy 這四個 action

app/controllers/group_controller.rb
class GroupsController < ApplicationController
...
...
  def edit
-   @group = Group.find(params[:id])
+   @group = current_user.groups.find(params[:id])
  end

  def create
-   @group = Group.new(group_params)
+   @group = current_user.groups.new(group_params)
...
...
  end

  def update
-   @group = Group.find(params[:id])
+   @group = current_user.groups.find(params[:id])
...
...
  end

  def destroy
-   @group = Group.find(params[:id])
+   @group = current_user.groups.find(params[:id])
...
...
  end
...
...
解說

這段 code 簡單來說就是
把 Group 換成 current_user.groups (無誤!)

運作邏輯是這樣:

把 create, edit, update, destroy 這四個 action 的運作

從原本

只是單純呼叫 group 的資料庫來
找某筆資料( .find(params[:id]) )
or
建立一筆新資料 ( .new(group_params) )

改成

登入的使用者( current_user ) 所擁有的 " group資料 ( .groups ) " 來做
找某筆資料( .find(params[:id]) )
or
建立一筆新資料 ( .new(group_params) )

當運作 create 創建新資料時
就會把 登入的使用者 ( current_user ) 的 id
寫進 group 資料庫裡的 user_id 的欄位裡面

所以這個 group 資料裡的 『 user_id 的欄位裡的值 』 就會等於 『 使用者( user )的 id 』 => 自動弄出作者這功能

至於 edit, update, destroy 這三個會讓資料異動的功能
則會自動驗證 group 裡的 user_id 跟 current_user 的 id 是否一致

如果是 true => 執行後續的程序
如果是 false => 直接跳 error (開發模式) or 404 (產品模式)

修改 view => 只有作者才會出現 edit / delete 按鈕

打開 app/models/group.rb

app/models/group.rb
...
...
  belongs_to :owner, class_name: "User", foreign_key: :user_id
  
+ def editable_by?(user)
+   user && user == owner
+ end
...
...

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

app/views/groups/index.html.erb
...
...
+           <% if group.editable_by?(current_user) %>
              <%= link_to("Edit", edit_group_path(group), class: "btn btn-sm btn-default")%>
              <%= link_to("Delete", group_path(group),class: "btn btn-sm btn-default", method: :delete, data: { confirm: "Are you sure?" } )%>
+           <% end %>
...
...
解說

這段 code 重點在於

# app/models/group.rb

  def editable_by?(user)
    user == owner
  end

# app/views/groups/index.html.erb

<% if group.editable_by?(current_user) %>

會去驗證 "登入的使用者" 跟 "作者的 id" 是否一致

if true => 顯示 edit 跟 delete 按鈕
if false => 隱藏

只有作者才能有 post 的修改/刪除權限

跟上述差不多,就直接貼 code , bj4 (不解釋) 了 ... XD

rails g migration add_user_id_to_post user_id:integer

rake db:migrate

打開 app/models/user.rb
app/models/user.rb
class User < ActiveRecord::Base
...
...
  has_many :groups
+ has_many :posts
end
打開 app/models/post.rb

改成

app/models/post.rb
class Post < ActiveRecord::Base
...
...
+ belongs_to :author, class_name: "User", foreign_key: :user_id
+ 
+ def editable_by?(user)
+   user && user == author
+ end
end
打開 app/controllers/posts_controller.rb
app/controllers/posts_controller.rb
...
...
 
  def edit
-   @post = @group.posts.find(params[:id])
+   @post = current_user.posts.find(params[:id])
  end
 
  def create
-   @post = @group.posts.new(post_params)
+   @post = @group.posts.build(post_params)
+   @post.author = current_user
...
...
  end 
 
  def update
-   @post = @group.posts.find(params[:id])
+   @post = current_user.posts.find(params[:id])
...
...
  end
 
  def destroy
-   @post = @group.posts.find(params[:id])
+   @post = current_user.posts.find(params[:id])
...
...
  end
...
...
打開 app/views/groups/show.html.erb
app/views/groups/show.html.erb
..
..
+           <% if post.editable_by?(current_user) %>
              <%= link_to("Edit", edit_group_post_path(post.group, post), class: "btn btn-default btn-xs")%>
              <%= link_to("Delete", group_post_path(post.group, post), class: "btn btn-default btn-xs ", method: :delete, data: { confirm: "Are you sure?" } )%>
+           <% end %>
...
..

完成圖

← [ 2.0 ] 3. 可以在討論版裡發表文章 [ 2.0 ] 5. user 可以加入、退出 group →
 
comments powered by Disqus