iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 13
0
Modern Web

向 Rails 致敬!30天寫一個網頁框架,再拿來做一個 Todo List系列 第 13

[DAY 13] 復刻 Rails - 進入 ORM 前,先了解 Migration

在實作 ORM 之前,我們可能要從 Migration 開始認識,Rails 有一個相當聰明的機制,可以方便管理資料庫的結構,這也是許多人剛接觸(像是我)一直會卡關的地方,總是不了解 migration 到底在做什麼,簡單的來說他就是在紀錄每一次資料庫變更的過程,不管是 新增一個資料表,或是 刪除一個欄位,搭配版本控管工具,可以確保在開發過程中,與其他一起開發的同事,資料庫長的一樣

我們就先從建立一個 Migration 來熟悉一下,接下來我們會用 sqlite3 這個套件來實作,sqlite3 也是 Rails 預設安裝好的套件,用這個套件可以方便我們跟 SQLite 做串接,至於什麼是 SQLite?想像它是一個輕型的資料庫,不用複雜的設定就可以直接使用,而且他還支援 SQL 語法

建立第一個 migration

一樣先在 Mavericks 安裝需要的 gem

# Mavericks/mavericks.gemspec

# .
# .
# (略)
spec.add_runtime_dependency "rack"
spec.add_runtime_dependency "erubi"
spec.add_runtime_dependency "multi_json"
# 加入這行
spec.add_runtime_dependency 'sqlite3'

安裝完後回到 just_do,讓我們先來建立第一個 Table

# just_do/mini_migration.rb

require 'sqlite3'

conn = SQLite3::Database.new "just_do.db"

conn.execute <<SQL
create table tasks(
id INTEGER PRIMARY KEY,
title TEXT,
content TEXT);
SQL

這裡我們用手動的方式建立了一個 Migration 檔案,就如同一開頭所說的,migration 是用來描述資料庫的狀態,我們在 Migration 裡面先 new 了一個新的 Database,叫 just_do.db,接著加上create table SQL 語法,然後我們來執行這段程式碼

$ bundle exec ruby mini_migration.rb

執行完後會在 just_do 的根目錄底下看到 just_do.db 的檔案,也就是 SQLite 的資料庫,其實跟我們之前練習用 JSON 來示範基本的 Model 很像,都是對本機的檔案做存取寫入,差別在於 SQLite 是一個小型的資料庫系統

查看 Table 的 Schema

建立完後,我們可以寫一個 method 來查看 tableschema,至於為什麼要查看 schema?有一個很重要的原因是 我們想要取得資料庫的欄位,透過這些欄位來建立物件的屬性,之後的實作部分會用到

回到 Mavericks,我們現在要做一個 SQLite 的 Model,所以要建立一個 sqlite_model.rb 的檔案(跟前幾天的 file_model.rb 有點類似)

# mavericks/lib/mavericks/sqlite_model.rb

require 'sqlite3'
require 'mavericks/support'

DB = SQLite3::Database.new 'just_do.db'

module Mavericks
  module Model
    class SQLite
      def self.table
        Mavericks.to_underscore name
      end

      def self.schema
        return @schema if @schema
        @schema = {}

        DB.table_info(table) do |row|
          @schema[row["name"]] = row["type"]
        end
        @schema
      end
    end
  end
end

我們現在還沒辦法讓開發者自己選擇資料庫,之後會再來實作這部分,這裡就先寫死在 Mavericks 裡面,接著寫一個 class method 來取得 table name,依照 Rails 的做法,基本上是一個 Model,也就是一個 Class 會對應到資料庫的一個表格,所以 class name 其實就等於table_name,只是我們需要借助 to_underscore,來轉換大小寫和底線

至於 schema 這個 class method,就是將欄位資訊轉換成 Hash 回傳,然後我們來寫一個簡單的 Ruby 程式碼來看看呼叫 shcnema

# just_do/sqlite_test.rb

require 'sqlite3'
require 'mavericks/sqlite_model'

class Tasks < Mavericks::Model::SQLite
end

puts Tasks.schema

最後執行

$ bundle exec ruby sqlite_test.rb
# {"id"=>"INTEGER", "title"=>"TEXT", "content"=>"TEXT"}

成功取得專案目前的 schema

單數或是複數?

細心的你可能會發現一個問題,我們一般表格是用複數,Model 名稱會用單數,例如像上面的範例中,應該是命名為 class Task 才對?當然你拿掉 s 以後,會發現撈到 {},你可能會好奇為什麼?Rails 不是都可以嗎?

那是因為 Rails 用 ActiveSupport::Inflector 來幫我們處理這件事情,我們也可以仿照 Rails 來處理單複數的問題,不過英文的單複數的處理規則比我們想像的還要複雜多,更何況還有許多的例外情況,所以這裡我採用最基本的規則來處理,有興趣的人可以再加上其他條件

複數規則

1.無特殊情形:加 s 
例如: book -> books 書

2.字尾是 s/x/z/sh/ch:加 es 
例如: box ➝ boxes 箱子

接著我們用正規表示法來檢查條件,程式碼如下

# mavericks/lib/mavericks/support.rb

module Mavericks

  # .
  # .
  # (略)
  
  def self.to_plural(string)
    pattern = /.*s$|x$|z$|sh$|ch$/
    pattern.match?(string) ? "#{string}es" : "#{string}s"
  end
end

我們加了一個 method 叫 to_plural 來處理複數轉換

接著在取得 table name 那裡也需要修正一下

def self.table
  table_name = Mavericks.to_underscore name
  # 多加了複數轉換
  Mavericks.to_plural table_name
end

最後回到 sqlite_test.rbs 拿掉測試看看結果

# just_do/sqlite_test.rb

require 'sqlite3'
require 'mavericks/sqlite_model'

class Task < Mavericks::Model::SQLite
end

puts Task.schema

最後執行

$ bundle exec ruby sqlite_test.rb
# {"id"=>"INTEGER", "title"=>"TEXT", "content"=>"TEXT"}

成功了!

邁向 ORM 一大步

我們有了第一個 migration,也學習 migration 的用處,也取得了 schema,利用 to_plural 也巧妙轉換了 Model 名稱 和 Table 名稱單複數的關係,看起來明天就可以進行更深入的實作了


上一篇
[DAY 12] 復刻 Rails - Request
下一篇
[DAY 14] 復刻 Rails - 實作 ORM 初體驗
系列文
向 Rails 致敬!30天寫一個網頁框架,再拿來做一個 Todo List30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言