搭配 Ruby 使用 PostgreSQL

這部分 Bookshelf 應用程式教學課程說明範例應用程式如何將永久資料儲存在 PostgreSQL 資料庫中。

這個範例使用在 Compute Engine 虛擬機器執行個體上執行的 PostgreSQL 伺服器儲存永久資料。如果您想使用其他 PostgreSQL 伺服器,可以將這個範例應用程式部署至 Google Cloud Platform (GCP),然後將其設定為使用您所選的任何 PostgreSQL 伺服器。

完整的教學課程內容涵蓋了數個頁面的篇幅,本頁面屬於其中一個單元。如要從頭開始並閱讀設定操作說明,請前往 Ruby Bookshelf 應用程式頁面。

在 Compute Engine 上執行 PostgreSQL

  1. 使用在 Google Cloud Platform Marketplace 中由 Bitnami 提供的 PostgreSQL 映像檔
  2. 按一下 [Launch on Compute Engine] (在 Compute Engine 上啟動)。建立執行個體及部署 PostgreSQL 需要幾分鐘的時間。

  3. 部署作業完成後,請記下 PostgreSQL 資料庫的「Admin user」(管理員使用者) 和「Admin password」(管理員密碼)

  4. 按一下執行個體連結,前往 VM 執行個體詳細資料頁面,記下執行個體的「External IP」(外部 IP) 位址。

    VM 執行個體的螢幕擷取畫面

  5. 如要編輯執行個體,請按一下 [Edit] (編輯) create。在「Network tags」(網路標記) 欄位中,輸入 postgres-bitnami,然後按一下 [Save] (儲存)

  6. 建立防火牆規則,允許流量送至具有 postgres-bitnami 網路標記的執行個體上的 PostgreSQL。

    gcloud compute firewall-rules create default-allow-postgresql \
        --allow tcp:5432 \
        --source-ranges 0.0.0.0/0 \
        --target-tags postgres-bitnami \
        --description "Allow access to PostgreSQL from all IPs"
    

設定

  1. 前往 getting-started-ruby/2-postgresql 目錄,並複製範例 database.yml 檔案:

    cp config/database.example.yml config/database.yml
    
  2. 如要設定資料庫,請編輯 config/database.yml 檔案。將 database 的值設定為 bookshelf,並將 [YOUR_POSTGRES_*] 預留位置改成您的 PostgreSQL 執行個體和資料庫的特定值。

    例如,假設您的 IPv4 位址為 173.194.230.44,使用者名稱為 postgres,密碼為 secret123。這樣您 database.yml 檔案的 postgresql_settings 部分會如下所示:

    postgresql_settings: &postgresql_settings
      adapter: postgresql
      encoding: unicode
      pool: 5
      username: postgres
      password: secret123
      host: 173.194.230.44
      database: bookshelf
    

安裝依附元件

2-postgresql 目錄中輸入下列指令:

bundle install

建立資料庫和資料表

  1. 建立資料庫和必要的資料表。

    bundle exec rake db:create
    bundle exec rake db:migrate
    

在本機電腦執行應用程式

  1. 啟動本機網路伺服器。

    bundle exec rails server
    
  2. 在網路瀏覽器中,輸入下列網址:

    http://localhost:3000

您現在可以瀏覽應用程式的網頁,並新增、編輯及刪除書籍。

如要離開本機網路伺服器,請按下 Control+C 鍵。

將應用程式部署至 App Engine 彈性環境

  1. 編譯 JavaScript 資產以用於實際工作環境。

    RAILS_ENV=production bundle exec rake assets:precompile
    
  2. 部署範例應用程式。

    gcloud app deploy
    
  3. 在網路瀏覽器中,輸入下列網址。

    https://[YOUR_PROJECT_ID].appspot.com
    

若您更新了應用程式,您可以輸入第一次部署應用程式時使用的指令來部署更新版本。新部署會為您的應用程式建立新版本,並將這個新版本晉升為預設版本。應用程式的較舊版本和相關聯的 VM 執行個體都會保留下來。請注意,這些應用程式版本和 VM 執行個體全部都是計費資源。

您可以刪除應用程式的非預設版本來降低費用。

如何刪除應用程式版本:

  1. 前往 GCP 主控台的「App Engine Versions」(App Engine 版本) 頁面。

    前往版本頁面

  2. 找到您要刪除的非預設應用程式版本,然後點選旁邊的核取方塊。
  3. 按一下頁面頂部的 [刪除] 按鈕, 刪除應用程式版本。

如需有關清除計費資源的完整資訊,請參閱本教學課程最後一個步驟的清除所用資源一節。

應用程式結構

下圖顯示應用程式的元件,以及這些元件彼此之間的連結方式。

驗證範例結構

瞭解程式碼

這部分的內容會逐步引導您瞭解應用程式的程式碼,並說明其運作方式。

列出書籍

當您造訪應用程式的首頁時,系統會將您轉送到 BooksController 類別的 index 動作。這個動作是在 config/routes.rb 檔案中設定的。

Rails.application.routes.draw do

  # Route root of application to BooksController#index action
  root "books#index"

  # Restful routes for BooksController
  resources :books

end

BookController#index 動作會從 Cloud SQL 資料庫擷取書籍清單。應用程式在每個網頁中會列出最多 10 本書,因此清單內容視使用者當下查看的頁面而定。例如,假設資料庫中有 26 本書,而使用者在第三頁 (/?page=3),在這個情況下,params[:page] 等於 3,而這個值會指派給 page_number 變數。接著,系統會擷取一份包含 6 本書的清單 (從第 21 本書開始),並將其指派給 @books

class BooksController < ApplicationController

  PER_PAGE = 10

  def index
    page_number = params[:page] ? params[:page].to_i : 1
    book_offset = PER_PAGE * (page_number - 1)
    @books      = Book.limit(PER_PAGE).offset(book_offset)
    @next_page  = page_number + 1 if @books.count == PER_PAGE
  end

Book 類別是簡易的 ActiveRecord 模型,代表書籍資料表中的個別書籍。

class Book < ActiveRecord::Base
  validates :title, presence: true
end

routes.rb 檔案中,resources :books 呼叫會設定符合 REST 樣式的路徑來建立、讀取、更新和刪除書籍,將書籍轉送至 BooksController 類別中的對應動作。

BooksController.index 擷取書籍清單後,books/index.html.erb 中嵌入的 Ruby 程式碼會轉譯清單。

<% @books.each do |book| %>
  <div class="media">
    <%= link_to book_path(book) do %>
      <div class="media-body">
        <h4><%= book.title %></h4>
        <p><%= book.author %></p>
      </div>
    <% end %>
  </div>
<% end %>

<% if @next_page %>
  <nav>
    <ul class="pager">
      <li><%= link_to "More", books_path(page: @next_page) %></li>
    </ul>
  </nav>
<% end %>

顯示書籍詳細資料

當您在網頁上按一下個別書籍時,BookController#show 動作會從書籍資料表中擷取指定 ID 的書籍。

def show
  @book = Book.find params[:id]
end

接著,show.html.erb 檔案中嵌入的 Ruby 程式碼會顯示書籍的詳細資料。

<div class="media">
  <div class="media-body">
    <h4><%= @book.title %> | &nbsp; <small><%= @book.published_on %></small></h4>
    <h5>By <%= @book.author || "unknown" %></h5>
    <p><%= @book.description %></p>
  </div>
</div>

建立書籍

當您在網頁上按一下 [Add book] (新增書籍) 時,BooksController#new 動作會建立新書籍。new.html.erb 檔案中嵌入的 Ruby 程式碼會指向 _form.html.erb,後者會顯示用來新增書籍的表單。

<%= form_for @book do |f| %>
  <div class="form-group">
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
  <div class="form-group">
    <%= f.label :author %>
    <%= f.text_field :author %>
  </div>
  <div class="form-group">
    <%= f.label :published_on, "Date Published" %>
    <%= f.date_field :published_on %>
  </div>
  <div class="form-group">
    <%= f.label :description %>
    <%= f.text_area :description %>
  </div>
  <button class="btn btn-success" type="submit">Save</button>
<% end %>

當您提交表單時,BooksController#create 動作會將書籍儲存在資料庫中。如果新書籍儲存成功,就會顯示該書籍的網頁。如果儲存失敗,表單會再次顯示,且您會看到錯誤訊息。book_params 方法使用強效參數來指定允許的表單欄位。在這個範例中,只有書名、作者、出版日期和說明是允許的欄位。

def create
  @book = Book.new book_params

  if @book.save
    flash[:success] = "Added Book"
    redirect_to book_path(@book)
  else
    render :new
  end
end

private

def book_params
  params.require(:book).permit(:title, :author, :published_on, :description)
end

編輯書籍

當您在網頁上按一下 [Edit book] (編輯書籍) 時,BooksController#update 動作會從資料庫擷取書籍。edit.html.erb 檔案中嵌入的 Ruby 程式碼會指向 _form.html.erb,後者會顯示用來編輯書籍的表單。

def update
  @book = Book.find params[:id]

  if @book.update book_params
    flash[:success] = "Updated Book"
    redirect_to book_path(@book)
  else
    render :edit
  end
end

當您提交表單時,BooksController#update 動作會將書籍儲存在資料庫中。如果新書籍儲存成功,就會顯示該書籍的網頁。如果儲存失敗,表單會再次顯示,且您會看到錯誤訊息。

刪除書籍

當您在網頁上按一下 [Delete Book] (刪除書籍) 時,BooksController#destroy 動作會從資料庫刪除書籍,然後顯示書籍清單。

def destroy
  @book = Book.find params[:id]
  @book.destroy
  redirect_to books_path
end
本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁