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

第五課: ActiveRecord 關聯

手動打造一個 Action

1. 產生&編輯 migration 檔案

2. 執行 rake db:migrate 改變資料庫

3. 建立和該 table 相對應的 model 檔

3. 寫出 Controller 檔案,宣告 action

4. 在 routes.rb 裡設定路由

5. 產生 view 所需要的 html.erb 檔案

 

DB Schema

資料庫的藍圖

紀錄每個資料表的欄位名稱

紀錄每個資料表與其他資料表的關聯性

 

在資料庫建立資料表

若要寫 SQL 指令去建立資料表...很噁心

CREATE TABLE Order
(Name char(50),
Phone char(50),
description char(256),
created_at datetime,
updated_at datetime);

產生 Migration (資料遷移) 檔案

migration 檔讓開發者可以不用寫 SQL 語言,直接用 ruby 語法即可在資料庫裡修改 schema

class AddUsersTable < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email
      t.integer :age

      t.timestamps null: false
    end
  end
end
$rails generate migration add_users_table

產生 Migration (資料遷移) 檔案

每一個 migration 檔都是一個繼承於 ActiveRecord::Migration[5.0] 的 class

 

class AddUsersTable < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email
      t.integer :age

      t.timestamps null: false
    end
  end
end

因為 migration 檔詳細記錄了開發者對 db schema 做出的改變,可以把 migration 檔看成是 db schema 的版本控制

執行 rake db:migrate

寫完 migration 檔之後,需要執行 rake db:migrate,實際的 database 才會發生變動

 

 

$rake db:migrate

在 routes.rb 裡設定路由

Rails.application.routes.draw do
  # 宣告單一路徑,指定 users/ 這個 url 會對應到 users_controller 的 index method
  get 'users' => 'users#index'
end

手動打造路由的規則:

             get   'users'  => 'users#index'

 

e.g. http動詞 'url路徑' => 'controller名稱#controller方法'

在 routes.rb 裡設定路由

Rails.application.routes.draw do
  get 'users' => 'users#index'
  post 'users' => 'users#create'
  get 'users' => 'users#new', as: :new_user
  get 'user/:id/edit' => 'user#edit', as: :edit_user
  get 'users/:id' => 'users#show', as: :user
  put 'user/:id' => 'user#update'
  patch 'user/:id' => 'user#update'
  delete 'users/:id' => 'user#destroy'
end

所以今天若是要手動產生整組支援 CRUD 的 url...

更多關於路由的設定:定:http://rails.ruby.tw/routing.html

在 routes.rb 裡設定路由

Rails.application.routes.draw do
  resources :users

  get '/users' => 'users#index'
  post '/users' => 'users#create'
  get '/users/new' => 'users#new'
  get '/users/:id' => 'users#show', as: :new_user
  get '/users/:id/edit' => 'users#edit', as: :edit_user
  put '/user/:id' => 'user#update'
  patch '/user/:id' => 'user#update'
  delete '/users/:id' => 'user#destroy'
end

可用 resources 關鍵字自動產出整組支援 CRUD 的 url

建立和該 table 相對應的 model 檔

class User < ApplicationRecord
  
end

在 app/models 底下建立一個新的檔案,記得檔案名稱必須是相對應 table 的名字,並且是單數,Class 名稱也必須是單數

寫出 Controller 檔案,宣告 action

# 在 app/controller 底下建立一個新的檔案,記得命名是 model 名字、複數加上 controller

class UsersController < ApplicationController
  def index
    @users = User.all
  end
end

在 app/controllers 底下建立一個新的檔案,記得檔案名稱必須是相對應 model 的名字,並且是復數,加上 controller


Model名稱 + s + Controller

產生 view 所需要的 html.erb 檔案

# 在 app/views/ 底下建立一個新的資料夾,命名是 model 的複數
# 然後再建立和 action 名字相同的 html.erb 檔

<h1>Hello World!</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Phone</th>
      <th>Age</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <%= @users.each do |user| %>
      <tr>
        <td><%= user.name %></td>
        <td><%= user.phone %></td>
        <td><%= user.age %></td>
      </tr>
    <% end %>
  </tbody>
</table>

 awesome_print gem

用官方的 rails console 來看資料十分痛苦...

 awesome_print gem

為了能讓 Rails console 裡的資料變得更淺顯易讀,我們來安裝 awesome_print gem:

 

https://github.com/awesome-print/awesome_print

# 在 gemfile 裡面輸入:

gem 'awesome_print'

# 然後執行 bundle install

 awesome_print gem

接下來在 Rails Console 內,若要印出任何複雜的AcitiveRecord 物件,在程式碼前方加上 'ap' 即可

What is rake?

用 Ruby 編寫任務腳本(.rake 檔),執行一些經常會需要用到的工作

 

可用 rake -T 查看 rails 內建的 rake 任務

 

 

 

$rake -T

MVC 就從 M 開始

資料庫是網路應用的基礎

打造好的動態網頁就要從設計完善的DB開始

 

DB Schema

所以我們剛才實作的 migration 檔

其實就是在紀錄 schema 的改變

 

ActiveRecord

每一個 model 檔也是繼承於 Active Record 的 class

 

class Order < ApplicationRecord
end

一個 Model 的 class 是對映到一個資料庫裡的資料表

由 Model class 產生出來的物件是對映到資料表裡的其中一筆資料

Association (關聯)

不同資料表之間的關聯要如何設定?

 

假設今天我有兩個資料表需要做關聯:

 

User - 代表使用者

Order - 代表訂單

 

1. 一個使用者可以有多筆訂單

2. 但是一筆訂單只能屬於一個使用者
所以 User 與 Order 為
一對多的關係

Association (關聯)

建立 users 資料表:

rails g migration create_post
class AddUsersTable < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email
      t.integer :age

      t.timestamps null: false
    end
  end
end
app/models/user.rb

class User < ApplicationRecord
end

Association (關聯)

建立 orders 資料表:

rails g migration create_order
class CreateOrder < ActiveRecord::Migration
  def change
    create_table :orders do |t|
      t.text    :description
      t.integer :user_id

      t.timestamps
    end
  end
end
app/models/order.rb

class Order < ActiveRecord::Base
end

在一對多的關係裡,多的一方為了關聯,必須要加一個外鍵 (foreign key),rails 命名外鍵的慣例為:
 

單數model名稱 + _id

Association (關聯)

設定兩者的關聯:

class Order < ApplicationRecord
  belongs_to :user
end

class User < ApplicationRecord
  has_many :orders
end

Association (關聯)

驗證一對多的關係:

rails console

創立一個 user:
user = User.create(name: "Bob", phone: "0987654321")

創立一個 order:
order = Order.create(description: "big mac deal", user_id: user.id)

我可以透過 user 搜尋到 orders
user.orders

也可以透過 order 搜尋到 user
order.user

Association (關聯)

若今天兩個資料表的關係是多對多關係

 

醫生與病人是多對多的關係

(一個醫生有很多病人,病人也可有很多個醫師)

這時我們需要一個中繼表 (join table) 來記錄兩個 table 的多對多關係

 

Association (關聯)

Association (關聯)

Rails 的多對多關聯有兩種:

1. has_many :through

2. has_and_belongs_to_many

Association (關聯)

has_many :through

 

class Physician < ApplicationRecord
  has_many :appointments
  has_many :patients, through: :appointments
end
 
class Appointment < ApplicationRecord
  belongs_to :physician
  belongs_to :patient
end
 
# Appointment 是關聯 physicians 和 patients 這兩個表
# 的 join table 的 model

class Patient < ApplicationRecord
  has_many :appointments
  has_many :physicians, through: :appointments
end

Association (關聯)

另一種多對多關聯:

has_and_belongs_to_many

 

Association (關聯)

has_and_belongs_to_many

 

class CreateAssembliesAndParts < ActiveRecord::Migration[5.0]
  def change
    create_table :assemblies do |t|
      t.string :name
      t.timestamps null: false
    end
 
    create_table :parts do |t|
      t.string :part_number
      t.timestamps null: false
    end
 
    create_table :assemblies_parts, id: false do |t|
      t.belongs_to :assembly, index: true
      t.belongs_to :part, index: true
    end
  end
end

Association (關聯)

has_and_belongs_to_many

 

class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end
 
class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end

Association (關聯)

has_many :through

v.s.

has_and_belongs_to_many

has_many :through #需要為中介表宣告一個 model,不過彈性也比較大
has_and_belongs_to_many #不用為中介表宣告一個 model,但是彈性比較小

#個人習慣用 has_many :through,多打幾個字不算什麼壞處...

Association (關聯)

Rails 的一些 association:

 

# 一對一

has_one

# 一對多

belongs_to
has_many

# 多對多

has_many :through
has_and_belongs_to_many :user

官方文件

Association 官方文件:

http://rails.ruby.tw/association_basics.html

 

Migration 官方文件:

http://rails.ruby.tw/active_record_migrations.html

 

Active Record 官方文件:

http://rails.ruby.tw/active_record_basics.html

常用的 ActiveRecord 查詢法

# find 是透過資料的 id 搜尋

user = User.find(1) #尋找 users 資料表裡 id 為 1 的 record

# where 是透過給訂的條件搜尋

result = User.where(name: "bob") # 會回傳一個有一或多筆 user 的陣列

# ActiveRecord 會有內建的搜尋方法,find_by_

bob = User.find_by_name("bob")

# 問題是,find_by_ 只會回傳符合條件的第一筆資料,請小心使用

Project: 留言板 App

Project: 留言板 App

需要的資料表:

 

Post - 用來記錄貼文的資料

 

Comment - 用來記錄留言的資料

 

Category - 用來記錄貼文類別的資料

 

User - 用來記錄使用者的資料

兩個冒號 :: 是什麼意思?

假設說今天一個專案裡有兩個名字一模一樣的 class 或是 module,放在同一個專案就會出問題,所以 Ruby 語言有 Namespace 的機制來解決這個問題

Class Order < ActiveRecord::Base
end

module 和 class 的巢狀結構

module Utility
  Class Flyable
    def fly
      # ....
    end
  end
end

一個 module 裡面可以包另一個 module或是class,class 裡面也可包另一個 module...

今天若要使用 Flyable 類別:

class Bat < Mammal
  include Utility::Flyable
end

module 和 class 的巢狀結構

module ActiveRecord
  # .....
  # 中略
  # .....
  class Base
    extend ActiveModel::Naming
    extend ActiveSupport::Benchmarkable
    extend ActiveSupport::DescendantsTracker

    extend ConnectionHandling
    extend QueryCache::ClassMethods
    # ... 略
  end
end

我們看一下 ActiveRecord 的原始碼...

所以 ActiveRecord::Base 代表放在 ActiveRecrod 這個模組裡的 Base Class

功課

打造留言板所需要的 database:

 

寫好 migration 檔

建立 routing 和 model 檔

 

留言板 demo:

https://postit-eugene-chang.herokuapp.com/

Made with Slides.com