Learning on Ruby on Rails Tutorial-CH9 Updating , Showing , and Deleting Users

這章會完成Users的REST動作,editupdateindexdestroy

廢話不多說,從做中學!

$ git checkout -b updating-users

修改使用者

修改其實很像新增,和新增不同的地方在於

new是對server提出POST的要求,但是update這個動作是提出PUT這個要求

還有一個最大的差別,就是所有人都可以註冊,但是update動作,只能給已經登入的user使用!

Edit 表格

直接來看測試!把它加到describe “User pages” do區塊內

spec/requests/user_pages_spec.rb

describe "edit" do
    let(:user) { FactoryGirl.create(:user) }
    before { visit edit_user_path(user) }

    describe "page" do
      it { should have_selector('h1',    text: "Update your profile") }
      it { should have_selector('title', text: "Edit user") }
      it { should have_link('change', href: 'http://gravatar.com/emails') }
    end

    describe "with invalid information" do
      before { click_button "Save changes" }

      it { should have_content('error') }
    end
  end

要編輯使用者之前一定要知道誰是使用者

別忘了可以使用params[:id]

所以我們在controller的地方可以加上這句話,app/controller/users_controller.rb

def edit
    @user = User.find(params[:id])
end

再來新增view

app/views/users/edit.html.erb

<% provide(:title, "Edit user") %> 
<h1>Update your profile</h1>

<div class="row">
  <div class="span6 offset3">
  <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>

      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.text_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.label :password_confirmation, "Confirm Password" %>
      <%= f.password_field :password_confirmation %>

      <%= f.submit "Save changes", class: "btn btn-large btn-primary" %>
    <% end %>

    <%= gravatar_for @user %>
    <a href="http://gravatar.com/emails">change</a>
  </div>
</div>

跑看看測試吧~

$ bundle exec rspec spec/requests/user_pages_spec.rb -e "edit page"

嘿嘿~ 你一定會好奇…奇怪….他跟new幾乎長得一模一樣

那Rails怎麼知道他是要用POST還是PUT?

其實他是用Active Record的一個方法:new_record?

很簡單的驗證方法

$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false

接著我們來新加另外一個測試的東西,在spec/requests/authentication_pages_spec.rb

加上使用者用正確資料登入時,會有一個新的Setting的鏈結可以選

it { should have_link('Settings', href: edit_user_path(user)) }

在下面那些底下

 it { should have_selector('title', text: user.name) }
 it { should have_link('Profile', href: user_path(user)) }
 it { should have_link('Sign out', href: signout_path) }
 it { should_not have_link('Sign in', href: signin_path) }  

還有一個地方要改~請把

before do
  fill_in "Email",    with: user.email
  fill_in "Password", with: user.password
  click_button "Sign in"
end

改成一句話!

before { sign_in user }

用一個helper來取代!把這個helper寫在spec/support/utilities.rb

def sign_in(user)
  visit signin_path
  fill_in "Email",    with: user.email
  fill_in "Password", with: user.password
  click_button "Sign in"
  # Sign in when not using Capybara as well.
  cookies[:remember_token] = user.remember_token
end

為了要確保Capybara有work

所以才加上

cookies[:remember_token] = user.remember_token

還要把Setting的連結加到header上面

也就是要改app/views/layouts/_header.html.erb

<li><%= link_to "Settings", '#' %></li>

改成

<li><%= link_to "Settings", edit_user_path(current_user) %></li>

不成功的編輯

當使用者編輯好,按下submit,會到controller的update動作執行,

所以User會使用他的update_attributes方法更新內容!

所以我們就用這個特性!

如果使用者編輯不成功的話,我們要在重新render給他edit頁面

所以來改app/controllers/users_controller.rb

def update
    @user = User.find(params[:id])
    if @user.update_attributes(params[:user])
      # Handle a successful update.
    else
      render 'edit'
    end
  end

成功的編輯

接著我們要來寫編輯的測試!

spec/requests/user_pages_spec.rbdescribe “edit” do區塊內加上

 describe "with valid information" do
      let(:new_name)  { "New Name" }
      let(:new_email) { "new@example.com" }
      before do
        fill_in "Name",             with: new_name
        fill_in "Email",            with: new_email
        fill_in "Password",         with: user.password
        fill_in "Confirm Password", with: user.password
        click_button "Save changes"
      end

      it { should have_selector('title', text: new_name) }
      it { should have_selector('div.alert.alert-success') }
      it { should have_link('Sign out', href: signout_path) }
      specify { user.reload.name.should  == new_name }
      specify { user.reload.email.should == new_email }
    end

那個比較特別的就是

specify { user.reload.name.should  == new_name }
specify { user.reload.email.should == new_email }

利用user.reload去重新讀取user的資料

OK 來寫其他東東

先來補上app/controllers/users_controller.rb

if @user.update_attributes(params[:user])
        flash[:success] = "Profile updated"
        sign_in @user
        redirect_to @user

sign_in再一次是因為當使用者儲存時,紀錄的token被重新設定,(before_save :create_remember_token)

所以我們要在重啓一次sesion!這是很好的安全設計~

跑一次測試~確保都有pass!

授權 (注意!不是認證歐XD)

我們在CH8已經寫過認證了!現在來實作授權,認證(authentication)是讓我們驗證使用者是可以使用網站的!授權(authorization)是讓我們控制使用者可以做什麼事情!

要求使用者必須登入

編輯測試先,主要是edit和update動作,必須要在登入時才可以用!測試寫在認證那邊就OK了,直接加到describe “Authentication do”裡面

spec/requests/authentication_pages_spec.rb

describe "authorization" do

    describe "for non-signed-in users" do
      let(:user) { FactoryGirl.create(:user) }

      describe "in the Users controller" do

        describe "visiting the edit page" do
          before { visit edit_user_path(user) }
          it { should have_selector('title', text: 'Sign in') }
        end

        describe "submitting to the update action" do
          before { put user_path(user) }
          specify { response.should redirect_to(signin_path) }
        end
      end
    end

這邊比較特別的就是,不使用Capybara的visit方法存取controller! 而是直接使用HTTP 要求! 所以這邊是使用put

put會要求直接到/users/1並且是update的動作,這是必要的因為沒有方法讓瀏覽器,直接visit update動作!只能透過提交edit 表格(Capybara也無法做到),但是這樣是為了測edit的動作!並沒有針對update來測!所以才需要使用put

所以~因為我們使用put這個動作!所以底下那個

specify { response.should redirect_to(signin_path) }

也是特別的!是使用response物件!不像Capybara的page物件!response讓我們測試伺服器的回應!

那所以要授權的程式碼,我們會使用before_filter這個callback,就是當使用者操作時,會讓某些特定的方法被呼叫

app/controllers/users_controller.rb

class UsersController < ApplicationController
  before_filter :signed_in_user, only: [:edit, :update]
  .
  .
  .
  private

    def signed_in_user
      redirect_to signin_path, notice: "Please sign in." unless signed_in?
    end
end

預設before_filter 是會用在所有動作的!但是我們可以用only這個來限制!

另外那個notice: “Please sign in.” 他相當於會丟一個hash給redirect_to這個函式

相當於

flash[:notice] = "Please sign in."
redirect_to signin_path

注意歐! 這邊用的是:notice,所以我們總共有:success:error以及:notice

這些Bootstrap CSS都支援!

這邊跑一下測試!

bundle exec rspec spec/

囧….怎麼錯誤變多,有9個錯誤!

原來是因為我們加了限制

spec/requests/user_pages_spec.rb

describe "edit" do
  let(:user) { FactoryGirl.create(:user) }
  before { visit edit_user_path(user) }

我們還沒有登入 就開始visit編輯畫面….所以會有問題!

所以我們可以使用helper裡面的sign_in,改成這樣!

let(:user) { FactoryGirl.create(:user) }
    before do
      sign_in user
      visit edit_user_path(user)
    end

再執行一下 測試! 恭喜!完成!

要求正確的使用者

當然我們只要求使用者登入這樣是不夠的!我們還需要是正當的使用者,所以來設計測試,當使用者用錯誤email登入時,然後點edit或是update,因為這個使用者也不行嘗試去編輯別人的頁面,使用者若嘗試去編輯別人的頁面,我們就將它導到root URL

spec/requests/authentication_pages_spec.rb

寫在describe “authorization” do區塊裡面

describe "as wrong user" do
      let(:user) { FactoryGirl.create(:user) }
      let(:wrong_user) { FactoryGirl.create(:user, email: "wrong@example.com") }
      before { sign_in user }

      describe "visiting Users#edit page" do
        before { visit edit_user_path(wrong_user) }
        it { should_not have_selector('title', text: full_title('Edit user')) }
      end

      describe "submitting a PUT request to the Users#update action" do
        before { put user_path(wrong_user) }
        specify { response.should redirect_to(root_path) }
      end
    end

所以我先來修改/app/controllers/users_controller.rb

before_filter :correct_user, only: [:edit , :update]

private

def signed_in_user
  redirect_to signin_path, notice: "Please sign in." unless signed_in?
end

def correct_user
  @user = User.find(params[:id])
  redirect_to(root_path) unless current_user?(@user)
end

那個current_user?是寫在app/helpers/session_helper.rb

記得補上歐!

def current_user?(user)
  user == current_user
end

友善的轉址

雖然一切看是完美~但是,其實有一個問題,那就是當user每次要使用保護的頁面時,他完成登入後,他都會被導向他的個人頁面~而且不是他想要去的頁面!所以我們要來改善這問題

所以先來寫測試!我一開始會先到user的編輯頁面,然後理論上他會幫我導向登入畫面,當我完成登入之後,必須幫我導到編輯的頁面

spec/requests/authentication_pages_spec.rb

加在describe “for non-signed-in users” do裡面!

describe "when attempting to visit a protected page" do
                before do
                  visit edit_user_path(user)
                  fill_in "Email",    with: user.email
                  fill_in "Password", with: user.password
                  click_button "Sign in"
                end

                describe "after signing in" do

                  it "should render the desired protected page" do
                    page.should have_selector('title', text: 'Edit user')
                  end
                end
        end

為了要完成這件事情! 我們要使用兩個function!

把它寫在session helper裡面

app/helpers/sessions_helper.rb

def redirect_back_or(default)
    redirect_to(session[:return_to] || default)
    session.delete(:return_to)
end

def store_location
    session[:return_to] = request.fullpath
end

Rails有提供session儲存的機制,(你就把它想成cookie物件),另外我們也使用request物件,取得完整連結路徑(URI),store_location就是把要求的完整路徑傳給session變數,他的key就是:return_to

所以要把store_location加到signed_in_user裡面,因為加在函式裡面,如果使用者不是登入狀態時,就要先把他想去的路徑先存起來,然後再把它導到登入畫面

那就來修改

app/controllers/users_controller.rb

def signed_in_user
  unless signed_in?
    store_location
    redirect_to signin_path, notice: "Please sign in."
  end
end

最後再來加個app/controllers/sessions_controller.rb

def create
    user = User.find_by_email(params[:session][:email])
    if user && user.authenticate(params[:session][:password])
      sign_in user
      redirect_back_or user
    else
      flash.now[:error] = 'Invalid email/password combination'
      render 'new'
    end
end

就是當使用者按下登入,並且也登入成功時,就會在session這邊create一個token

所以我們在create這邊加上剛剛寫的redirect_back_or所以如果有記錄之前要去的url,他

就會前往那邊~不然的話,就是連到user的頁面

跑一下測試確認可以work

$  bundle exec rspec spec/

Show出所有使用者

簡單來說,希望可以看到每個user的資料(還附上連結),最後還要加上換頁的東西!

User Index

可以看到所有使用者這功能,必須限定是登入使用者,另外,要可以使用users_path這個連結看到所有使用者!

So…開始寫測試吧!

spec/requests/authentication_pages_spec.rb 寫在 describe “in the Users controller” do裡面

describe "visiting the user index" do
      before { visit users_path }
      it { should have_selector('title', text: 'Sign in') }
end

因為我們要限定登入成功使用者才可以用!所以在app/controllers/users_controller.rb請補上

before_filter :signed_in_user, only: [:index, :edit, :update]

def index
end

既然寫到了user controller當然要寫一下spec/requests/user_pages_spec.rb

加在 describe “User pages” do底下

describe "index" do
    before do
      sign_in FactoryGirl.create(:user) 
      FactoryGirl.create(:user, name: "Derek" , email: "Derek@example.com")
      FactoryGirl.create(:user, name: "Edison" , email: "Edison@example.com")
      visit users_path
    end

    it { should have_selector('title',text: 'All users') }
    it "should list each user" do 
      User.all.each do |user|
        page.should have_selector('li', text: user.name)
      end
    end
end

為了要通過測試!讓我們開始補程式碼吧!

app/controllers/users_controller.rbindex請補上

def index
    @users = User.all
end

換到view的地方,先新增app/views/users/index.html.erb

<%= provide(:title, 'All users') %>
<h1>All users</h1>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 52 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

再來為user index加上一些CSS效果

app/assets/stylesheets/custom.css.scss

/* users index */

.users {
  list-style: none;
  margin: 0;
  li {
    overflow: auto;
    padding: 10px 0;
    border-top: 1px solid $grayLighter;
    &:last-child {
      border-bottom: 1px solid $grayLighter;
    }
  }
}

那我們在認證的地方也要加上一個測試!確保Users這個link可以用

spec/requests/authentication_pages_spec.rbdescribe “with valid information” do底下

it { should have_link('Users',    href: users_path) }

補上URI到app/views/layouts/_header.html.erb

<li><%= link_to "Users", '#' %></li>

改成

<li><%= link_to "Users", users_path %></li>

耶~測試吧

$ bundle exec rspec spec/

囧…有錯…奇怪..他說…gravatar_for 預設輸入參數只有一個,結果我給她兩個….

原來…是因為我沒有做CH7的練習,補上

讓我們可以改變顯圖的大小!

app/helpers/users_helper.rb

def gravatar_for(user, options = { size: 50 })
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    size = options[:size]
    gravatar_url = "http://gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
end

再跑一次!恭喜~通過~

其實可以開網頁來看! 你會發現….東西好少….好乾歐!

所以….

User 樣本

我們要安裝並使用一個叫做是faker的gem

所以先在Gemfile加上

gem 'faker', '1.0.1'

別忘了

$ bundle install

要如何使用呢?

我們必須要加一個Rake task的東東去建樣本使用者!

Rake task通常被放在lib/tasks資料夾下

所以我們新增一個檔案吧!lib/tasks/sample_data.rake

namespace :db do
  desc "Fill database with sample data"
  task populate: :environment do
    User.create!(name: "Example User",
                 email: "example@railstutorial.org",
                 password: "foobar",
                 password_confirmation: "foobar")
    99.times do |n|
      name  = Faker::Name.name
      email = "example-#{n+1}@railstutorial.org"
      password  = "password"
      User.create!(name: name,
                   email: email,
                   password: password,
                   password_confirmation: password)
    end
  end
end

所以定義了一個任務 db:populate,透過db:reset會重設development資料庫!

然後會建置99個資料

這一行

task populate: :environment do

確保Rake task 會被本地端的Rails環境存取(包含User model),另外那個create!create差在他會丟出例外原因,而不是只告訴你False

所以我們可以透過什麼方式調用(invoke)這個Rake task呢?

$ bundle exec rake db:reset
$ bundle exec rake db:populate
$ bundle exec rake db:test:prepare

OK~我們就可以看到一堆user了!

分頁功能

看到那麼多user當然我們要加上分頁功能,這個之前在我另外的自我學習已經有練過了~所以我就快速帶過~

Gemfile加上

gem 'will_paginate', '3.0.3'
gem 'bootstrap-will_paginate', '0.0.5'

別忘了

$ bundle install

OK! 當然我們也要寫測試!

但是寫測試之前….有個問題,因為要測翻頁功能有work!

所以….是必我要寫像下面那樣一百遍嗎~歐不~~

FactoryGirl.define do
  factory :user do
    name     "Michael Hartl"
    email    "michael@example.com"
    password "foobar"
    password_confirmation "foobar"
  end
end

好佳在!

FactoryGirl有提供sequence的功能!

我們可以這樣用!

factory :user do
    sequence(:name)  { |n| "Person #{n}" }
    sequence(:email) { |n| "person_#{n}@example.com"}   

它就會建出這樣的東東

“Person 1”  “person_1@example.com”
“Person 2”  “person_2@example.com”
“Person 3”  “person_3@example.com”
….等

所以我們可以定義FactoryGirl為sequence版本,把它改在

spec/factories.rb

FactoryGirl.define do
  factory :user do
    sequence(:name)  { |n| "Person #{n}" }
    sequence(:email) { |n| "person_#{n}@example.com"}   
    password "foobar"
    password_confirmation "foobar"
  end
end

然後開始寫測試摟~

spec/requests/user_pages_spec.rb

describe "index" do
    before do
      sign_in FactoryGirl.create(:user) 
      FactoryGirl.create(:user, name: "Derek" , email: "Derek@example.com")
      FactoryGirl.create(:user, name: "Edison" , email: "Edison@example.com")
      visit users_path
    end

    it { should have_selector('title',text: 'All users') }
    it "should list each user" do 
      User.all.each do |user|
        page.should have_selector('li', text: user.name)
      end
    end
  end

改成

describe "index" do

    let(:user) { FactoryGirl.create(:user) }

    before do
      sign_in user
      visit users_path
    end

    it { should have_selector('title', text: 'All users') }

    describe "pagination" do
      before(:all) { 30.times { FactoryGirl.create(:user) } }
      after(:all)  { User.delete_all }

      it { should have_link('Next') }
      its(:html) { should match('>2</a>') }

      it "should list each user" do
        User.all[0..2].each do |user|
          page.should have_selector('li', text: user.name)
        end
      end
    end
  end

這裡面有些相當特別的code!

its(:html) { should match('>2</a>') }

他的意思是 測試頁面有包含2…就這樣

ok要讓pagination可以用,我們來改app/views/users/index.html.erb

<%= provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 52 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

<%= will_paginate %>

主要是因為加上will_paginate

他會自動找尋@users這個物件,然後秀出換頁的連結

但是目前這個view是不work的!

因為@users是來自User.all,它是一個Array類別,但是will_paginate是預期物件是一個ActiveRecord::Relation

但是好佳在,我們可以使用will_paginate提供的paginate方法轉成ActiveRecord::Relation

$ rails console
>> User.all.class
=> Array
>> User.paginate(page: 1).class
=> ActiveRecord::Relation

注意!那個paginate使用:page當成其hash的key,所以User.paginate從資料庫撈出的筆數就是依據:page這個值!

所以我們可以來改寫一下我controller

@users = User.all改成

def index
  @users = User.paginate(page: params[:page])
end

yap!執行一下測試!

$ bundle exec rspec spec/

Partial refactoring

因為我們測試已經完成~我們可以來改寫一下程式碼

善用Rails的一些特性!

先來改寫app/views/users/index.html.erb

<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <%= render user %>
  <% end %>
</ul>

<%= will_paginate %>

因為把它改寫成render

Rails會自動去搜尋_user.html.erb

所以要新建這個檔案app/views/users/_user.html.erb(注意歐!不是在layout底下歐)並加上

<li>
  <%= gravatar_for user, size: 52 %>
  <%= link_to user.name, user %>
</li>

但是更酷的是! 你以為這樣就結束了?

其實可以再改寫app/views/users/index.html.erb

把這一句

<% @users.each do |user| %>
    <%= render user %>
<% end %>

改成這一句就好!

<%= render @users %>

因為Rails它會發現@usersUser物件的list,當呼叫出users的collection時,Rails會自動幫你做迭代!當你每個值都丟到_user.html.erb裡面酷吧!

改完code,當然要跑測試~看有沒有改錯!

$ bundle exec rspec spec/

刪除使用者

必須要是Administrator才可以刪除使用者

當然測試要驗證一下!

spec/models/user_spec.rb

在最前面補上

it { should respond_to(:admin) }

it { should_not be_admin }

describe "with admin attribute set to 'true'" do
    before { @user.toggle!(:admin) }

    it { should be_admin }
  end

這邊有個很酷的東西!

使用toggle!方法切換admin的屬性,不是true就是false,還有一個要注意!

it { should be_admin }

這代表了我們必須有admin?這個回傳True或是False的函式!

如同往常!

我們必須先加上admin這個屬性!然後他是boolean類型

$ rails generate migration add_admin_to_users admin:boolean

跑完之後~ 補上一些東西!

class AddAdminToUsers < ActiveRecord::Migration
  def change
    add_column :users, :admin, :boolean, default: false
  end
end

加完要記得

$ bundle exec rake db:migrate
$ bundle exec rake db:test:prepare

然後我們用console把其中一個user改成admin來看看我們剛剛寫的那些東東work不work~

$ rails console --sandbox
>> user = User.first
>> user.admin?
=> false
>> user.toggle!(:admin)
=> true
>> user.admin?
=> true

最後一步驟,我們要改一下我們自動產生user的

lib/tasks/sample_data.rake

namespace :db do
  desc "Fill database with sample data"
  task populate: :environment do
    admin = User.create!(name: "Example User",
                         email: "example@railstutorial.org",
                         password: "foobar",
                         password_confirmation: "foobar")
    admin.toggle!(:admin)
    .
    .
    .
  end
end

改了這個之後,我們要重跑一下資料庫!還記得哪些步驟嗎?

$ bundle exec rake db:reset
$ bundle exec rake db:populate
$ bundle exec rake db:test:prepare

重新看attr_accessible

你可能有注意到,利用toggle!(:admin),把使用者改成admin,那為甚麼我們不直接使用admin: true來初始化hash?

答案是這是不對的!

只有attr_accessible屬性可以被大量指派值(丟一個hash,Rails會自動幫你把值對應進去),然而admin屬性,並非accessible

(我們在app/models/user.rb並沒有在attr_accessible加入:admin)

明確的定義accessible對網站資安相當有助益!

假設我們使用attr_accessible把admin加進去的話,駭客就可使用這樣的方式來改你的資料庫!

put /users/17?admin=1

所以…可以嘗試測試,把所有model的屬性,就像:admin,測試並沒有被加到accessible

刪除動作

為了要寫刪除函數的測試,我們可以使用factorygirl建立管理者,請看下面

spec/factories.rb

FactoryGirl.define do
  factory :user do
    sequence(:name)  { |n| "Person #{n}" }
    sequence(:email) { |n| "person_#{n}@example.com"}   
    password "foobar"
    password_confirmation "foobar"

    factory :admin do
      admin true 
    end

  end
end

沒錯!就是加上

factory :admin do
   admin true 
end

OK~先來寫測試! 等一下再說為什麼要這樣寫

spec/requests/user_pages_spec.rb

describe “index” do區塊加上

describe "delete links" do

          it { should_not have_link('delete') }

          describe "as an admin user" do
            let(:admin) { FactoryGirl.create(:admin) }
            before do
              sign_in admin
              visit users_path
            end

            it { should have_link('delete', href: user_path(User.first)) }
            it "should be able to delete another user" do
              expect { click_link('delete') }.to change(User, :count).by(-1)
            end
            it { should_not have_link('delete', href: user_path(admin)) }
          end
 end

為什麼要這樣寫,一般使用者是看不到delete這個選項的!

it { should_not have_link('delete') }

再來,Admin是可以刪除的!所以才有那面那些行

it { should have_link('delete', href: user_path(User.first)) }
it "should be able to delete another user" do
  expect { click_link('delete') }.to change(User, :count).by(-1)
end
it { should_not have_link('delete', href: user_path(admin)) }

再來寫code通過測試吧!

app/views/users/_user.html.erb

<li>
  <%= gravatar_for user, size: 52 %>
  <%= link_to user.name, user %>
  <% if current_user.admin? && !current_user?(user) %>
    | <%= link_to "delete" , user , method: :delete , confirm: "Are you sure?" %>
<% end %>
</li>

因為瀏覽器無法送出DELETE的要求,所以Rails是利用javaScript來假

再來,為了要讓Delele連結work! 來寫

app/controllers/users_controller.rb

def destroy
    User.find(params[:id]).destroy
    flash[:success] = "User destroyed."
    redirect_to users_path
end

這邊有個資安的漏洞,經驗豐富的駭客~可以簡單的使用CLI來送出DELETE要求,來刪除使用者!所以我們必須對detroy作存取控制!

先來寫測試!

spec/requests/authentication_pages_spec.rb

describe “authorization” do加上

describe "as non-admin user" do
  let(:user) { FactoryGirl.create(:user) }
  let(:non_admin) { FactoryGirl.create(:user) }

  before { sign_in non_admin }

  describe "submitting a DELETE request to the Users#destroy action" do
    before { delete user_path(user) }
    specify { response.should redirect_to(root_path) }        
  end
end

這邊還是有些小的資安漏洞,那就是管理者可以刪除自己XD,可以寫看看摟~不過就先跳過,繼續下去啦!

app/controllers/users_controller.rb

在前頭加上

before_filter :admin_user,     only: :destroy

private後面加上

def admin_user
  redirect_to(root_path) unless current_user.admin?
end

跑測試!

$ bundle exec rspec spec/

yap~~All pass!

耶~終於完成CH9

$ git add .
$ git commit -m "Finish user edit, update, index, and destroy actions"
$ git checkout master
$ git merge updating-users
Comments

Comments

Google Analytics Alternative