Ruby on Rails 網站程式設計基礎班

 

第九課:多形關聯

今天上課的程式碼...

 

Movie Time!

 

Flash Message

 

# 在 application.html.erb 裡加入:

<% flash.each do |key, value| %>
  <div class="alert alert-<%= key %>"><%= value %></div>
<% end %>
# 接下來就可在後端 Controller 把要顯示的字串放入 flash 這個 hash,就會顯示在前端
def create
  @order = Order.new(order_params)

  if @order.save # true, false
    flash[:success] = "已成功下訂!"
    redirect_to orders_path
  else
    flash[:danger] = "漏填資料囉,啾咪!"
    render :new
  end
end

來幫訂便當加上留言的功能吧...

 

class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :order
end
class Order < ApplicationRecord
  # 在 rails 的層面做資料驗證
  # 一筆訂單的訂購者應該要填名字,不然不給建立一筆訂單
  validates :name, presence: true
  # 訂購者的名字不應該只有一個字元
  validates :name, length: { minimum: 2 }
  validates :name, length: { maximum: 10 }
  # 訂購者的 email 是必填,不然無法聯絡
  validates :email, presence: true

  has_many :comments
end
class User < ApplicationRecord
  # rails 內建的使用者認證方法,需要搭配 bcrypt gem 使用
  has_secure_password validation: false

  has_many :comments
end
rails g model comment

執行:

來幫訂便當加上留言的功能吧...

 

來幫訂便當加上留言的功能吧...

 

class OrdersController < ApplicationController
  # GET /orders/1
  def show
    # 對映到顯示單筆資料的頁面
    # show 頁面的留言表單需要一個空物件
    @comment = Comment.new
  end
end

來幫訂便當加上留言的功能吧...

 

class CommentsController < ApplicationController
  def create
    # 找相關連的訂單
    @order = Order.find(params[:order_id])
    # build 與 new 的意思一樣
    @comment = @order.comments.build(params.require(:comment).permit(:content))
    # 綁定現在登入的使用者
    @comment.user = current_user

    if @comment.save
      redirect_to order_path(@order)
    else
      render 'orders/show'
    end
  end
end
rails g controller comments --no-assets --no-test-framework

產生 Comments Controller 的指令:

嵌套/巢狀路由

 

resources :orders do
  # 嵌套/巢狀路由,這樣可以產生出 orders/1/comments/ 的 url
  resources :comments, only: [:create]
end

來幫訂單列表加一個投票的功能吧...

 

來幫留言加一個投票的功能吧...

 

資料庫該如何設計...

 

欄位名稱: vote
 
order_id user_id
資料型別: boolean integer integer
class OrderVote < ApplicationRecord
  belongs_to :order
end

class Order < ApplicationRecord
  has_many :order_votes
end

但是...

 

欄位名稱: vote
 
comment_id user_id
資料型別: boolean integer integer
class OrderVote < ApplicationRecord
  belongs_to :order
end

class Post < ApplicationRecord
  has_many :order_votes
end

class CommentVote < ApplicationRecord
  belongs_to :comment
end

class Comment < ApplicationRecord
  has_many :comment_votes
end

今天我們若要也要幫 comments 加上投票功能...

order_votes 和 comment_votes兩個資料表的 schema 幾乎一樣,若每多一個 model 需要 voting 就增加一個資料表紀錄 votes,只會造成資料庫本身變得越來越複雜...

多型關連(Polymorphic Associations)

 

欄位名稱: vote
 
voteable_type voteable_id user_id
資料型別: boolean string integer integer

多型關連(Polymorphic Associations)可以讓一個 Model 不一定關連到某一個特定的 Model,秘訣在於除了整數的 _id 外部鍵之外,再加一個字串的 _type 欄位說明是哪一種Model

多型關連(Polymorphic Associations)

 

class CreateVotes < ActiveRecord::Migration[5.0]
  def change
    create_table :votes do |t|
      t.boolean :vote
      t.integer :user_id
      t.string  :voteable_type
      t.integer :voteable_id

      t.timestamps
    end
  end
end
rails g migration create_votes

先來寫一個 migration 檔...

多型關連(Polymorphic Associations)

 

class Vote < ApplicationRecord
  # 關聯的設定可以給別名,foreign key 和相關連的 model 需要自己設定  
  belongs_to :creator, foreign_key: :user_id, class_name: "User"

  belongs_to :voteable, polymorphic: true

  validates_uniqueness_of :creator, scope: [:voteable_type, :voteable_id]
end
class Order < ApplicationRecord
  has_many :votes, as: :voteable
end
class Comment < ApplicationRecord
  has_many :votes, as: :voteable
end

多型關連(Polymorphic Associations)

 

class Order < ApplicationRecord
  has_many :comments
  has_many :votes, as: :voteable

  def up_votes
    self.votes.where(vote: true).length
  end

  def down_votes
    self.votes.where(vote: false).length
  end

  def total_votes
    up_votes - down_votes
  end
end

加上一些投票相關的功能...

多型關連(Polymorphic Associations)

 

# 改寫 routes.rb...
  resources :orders do
    member do # 客製化連結
      post :vote
      # 這樣會產出 orders/1/vote
    end
    # 嵌套/巢狀路由,這樣可以產生出 orders/1/comments/ 的 url
    resources :comments, only: [:create] do
      member do
        post :vote
        # 產出 orders/1/comments/1/vote
      end
    end
  end

多型關連(Polymorphic Associations)

 

class PostsController < ApplicationController
  def vote
    @order = Order.find(params[:id])
    @vote = Vote.create(voteable: @order, creator: current_user, vote: params[:vote])

    redirect_to root_path
  end
end

多型關連(Polymorphic Associations)

 

<table class="table table-hover table-striped">
  <thead>
    <tr>
      <th>票數</th>
      <th>讚</th>
      <th>噁</th>
      <th>姓名</th>
      <th>Email</th>
      <th>訂單內容</th>
      <th>年齡</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @orders.each do |order| %>
      <tr>
        <td><%= order.total_votes %></td>
        <td>
          <%= link_to vote_order_path(order, vote: true), class: "btn btn-primary", method: 'post' do %>
          讚
          <% end %>
        </td>
        <td>
          <%= link_to vote_order_path(order, vote: false), class: "btn btn-danger", method: 'post' do %>
          噁
          <% end %>
        </td>

        <td><%= order.name %></td>
        <td><%= order.email %></td>
        <td><%= order.description %></td>
        <td><%= order.age %></td>
        <td><%= link_to '檢視訂單', order_path(order), class: 'btn btn-info' %></td>
        <% if logged_in? %>
          <td><%= link_to '編輯訂單', edit_order_path(order), class: 'btn btn-warning' %></td>
          <td><%= link_to '刪除訂單', order_path(order), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger' %></td>
        <% end %>
      </tr>
    <% end %>
  </tbody>
</table>
Made with Slides.com