第五課: ActiveRecord 關聯
1. 產生&編輯 migration 檔案
2. 執行 rake db:migrate 改變資料庫
3. 建立和該 table 相對應的 model 檔
3. 寫出 Controller 檔案,宣告 action
4. 在 routes.rb 裡設定路由
5. 產生 view 所需要的 html.erb 檔案
資料庫的藍圖
紀錄每個資料表的欄位名稱
紀錄每個資料表與其他資料表的關聯性
若要寫 SQL 指令去建立資料表...很噁心
CREATE TABLE Order
(Name char(50),
Phone char(50),
description char(256),
created_at datetime,
updated_at datetime);
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 檔都是一個繼承於 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 的版本控制
寫完 migration 檔之後,需要執行 rake db:migrate,實際的 database 才會發生變動
$rake db:migrate
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方法'
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
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
class User < ApplicationRecord
end
在 app/models 底下建立一個新的檔案,記得檔案名稱必須是相對應 table 的名字,並且是單數,Class 名稱也必須是單數
# 在 app/controller 底下建立一個新的檔案,記得命名是 model 名字、複數加上 controller
class UsersController < ApplicationController
def index
@users = User.all
end
end
在 app/controllers 底下建立一個新的檔案,記得檔案名稱必須是相對應 model 的名字,並且是復數,加上 controller
Model名稱 + s + Controller
# 在 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>
用官方的 rails console 來看資料十分痛苦...
為了能讓 Rails console 裡的資料變得更淺顯易讀,我們來安裝 awesome_print gem:
# 在 gemfile 裡面輸入:
gem 'awesome_print'
# 然後執行 bundle install
接下來在 Rails Console 內,若要印出任何複雜的AcitiveRecord 物件,在程式碼前方加上 'ap' 即可
用 Ruby 編寫任務腳本(.rake 檔),執行一些經常會需要用到的工作
可用 rake -T 查看 rails 內建的 rake 任務
$rake -T
資料庫是網路應用的基礎
打造好的動態網頁就要從設計完善的DB開始
所以我們剛才實作的 migration 檔
其實就是在紀錄 schema 的改變
每一個 model 檔也是繼承於 Active Record 的 class
class Order < ApplicationRecord
end
一個 Model 的 class 是對映到一個資料庫裡的資料表
由 Model class 產生出來的物件是對映到資料表裡的其中一筆資料
不同資料表之間的關聯要如何設定?
假設今天我有兩個資料表需要做關聯:
User - 代表使用者
Order - 代表訂單
1. 一個使用者可以有多筆訂單
2. 但是一筆訂單只能屬於一個使用者
所以 User 與 Order 為一對多的關係
建立 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
建立 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
設定兩者的關聯:
class Order < ApplicationRecord
belongs_to :user
end
class User < ApplicationRecord
has_many :orders
end
驗證一對多的關係:
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
若今天兩個資料表的關係是多對多關係:
醫生與病人是多對多的關係
(一個醫生有很多病人,病人也可有很多個醫師)
這時我們需要一個中繼表 (join table) 來記錄兩個 table 的多對多關係
Rails 的多對多關聯有兩種:
1. has_many :through
2. has_and_belongs_to_many
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
另一種多對多關聯:
has_and_belongs_to_many
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
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
has_many :through
v.s.
has_and_belongs_to_many
has_many :through #需要為中介表宣告一個 model,不過彈性也比較大
has_and_belongs_to_many #不用為中介表宣告一個 model,但是彈性比較小
#個人習慣用 has_many :through,多打幾個字不算什麼壞處...
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 官方文件:
# 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_ 只會回傳符合條件的第一筆資料,請小心使用
需要的資料表:
Post - 用來記錄貼文的資料
Comment - 用來記錄留言的資料
Category - 用來記錄貼文類別的資料
User - 用來記錄使用者的資料
假設說今天一個專案裡有兩個名字一模一樣的 class 或是 module,放在同一個專案就會出問題,所以 Ruby 語言有 Namespace 的機制來解決這個問題
Class Order < ActiveRecord::Base
end
module Utility
Class Flyable
def fly
# ....
end
end
end
一個 module 裡面可以包另一個 module或是class,class 裡面也可包另一個 module...
今天若要使用 Flyable 類別:
class Bat < Mammal
include Utility::Flyable
end
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: