當我們開始製作頁面的時候,很快會發現一件事情,沒有資料庫的網站終究只是個靜態頁面,但當我們想要結合資料庫時,接踵而來的問題就開始產生,例如怎麼連到資料庫?怎麼查詢資料?怎麼把資料丟到頁面?尤其是光想到要面對那些 SQL 語法就很頭痛
但也這就是 Rails Model 厲害的地方,你剛剛所想的問題都幫你處理好了,你只要有物件導向的觀念,會用所謂的 ORM(Object-relational mapping),就可以省下很多開發時間,讓你可以更專注在開發產品上面
也因為 Model 是如此重要,而且不容易了解,所以接下來會花比較多的篇幅來介紹 Model 的部分,為了循序漸進,我們先從透過操作 JSON 格式檔案來模擬資料庫,熟悉一下 Model 在做些什麼事情
在開始之前,要先在 Mavericks 安裝 multi_json,我們將會用它做 JSON 轉成 Hash 的處理
# mavericks/mavericks.gemspec
.
.
(略)
spec.add_runtime_dependency "rack"
spec.add_runtime_dependency "erubi"
spec.add_runtime_dependency "multi_json"
這裡會建立一個叫 file_model 的 Class,之後會分段說明程式碼
# mavericks/lib/mavericks/file_model.rb
require "multi_json"
module Mavericks
  module Model
    class FileModel
      def initialize(file)
        @file = file
        basename = File.split(file)[-1]
        @id = File.basename(basename, ".json").to_i
        obj = File.read(file)
        @hash = MultiJson.load(obj)
      end
      def [](name)
        @hash[name.to_s]
      end
      def []=(name, value)
        @hash[name.to_s] = value
      end
      def self.find(id)
        begin
         FileModel.new("db/tasks/#{id}.json")
        rescue
         return nil
        end
      end
    end
  end
end
別忘了要記得 require
# mavericks/lib/mavericks.rb
# .
# .
# (略)
require "mavericks/file_Model"
module Mavericks
  class Error < StandardError; end
  class Application
  # .
  # .
  # (略)
先看 initialize,這段程式碼要做的事情是,從檔名中取得 ID
# mavericks/lib/mavericks/file_model.rb
def initialize(file)
  @file = file
  basename = File.split(file)[-1]
  @id = File.basename(basename, ".json").to_i
  obj = File.read(file)
  @hash = MultiJson.load(obj)
end
File.split 會在 'just_do/db/tasks/1.json' 的字串中取得 1.json,接著透過 File.basename 去掉 .json 的附檔名,就可以取得 id
接著再建立兩個 method,一個叫 [],另一個是 []=,用來對物件的 attribute 做 set 和 get
# mavericks/lib/mavericks/file_model.rb
def [](name)
  @hash[name.to_s]
end
def []=(name, value)
  @hash[name.to_s] = value
end
最後建立一個 class method 叫 find,透過 id 來尋找單筆資料,就如同 ActiveRecord 的方式一樣
def self.find(id)
  begin
   FileModel.new("db/tasks/#{id}.json")
  rescue
   return nil
  end
end
回到 just_do 的專案,來試試看 Mavericks 的 Model,前面提到,會用 JSON 格式檔案來模擬資料庫,就跟 Rails 一樣先建立一個 db/ 資料夾,但不一樣的是我們運用 資料夾 + JSON 格式檔案 當作「資料庫」來使用,在 db/ 底下再建立一個 tasks/ 資料夾,這個 tasks/ 可以看作是一個 table,接著在 tasks/ 裡面建立一個 1.json 可以看作是一筆資料,如下面所示
# just_do/db/tasks/1.json
{
  "title": "完成30天鐵人賽",
  "content": "每天寫一篇,連續30天"
}
然後在 Controller 的 show 裡面建立一個 Model 物件,用 #find 來尋找資料
# just_do/app/controllers/tasks_controller.rb
class TasksController < Mavericks::Controller
  def index
  end
  def show
    @task = Mavericks::Model::FileModel.find(1)
  end
end
增加 show.html.erb 頁面
# just_do/app/views/tasks/show.html.erb
<div class="row">
  <div class="col-6">
    <div class="card">
      <div class="card-header">
        任務細節
      </div>
      <div class="card-body">
        <div class="row">
          <label class="col" for="task_name">任務名稱</label>
          <div class="col">
             <%= @task['title']  %>
          </div>
        </div>
        <div class="row">
          <label class="col" for="task_name">任務內容</label>
          <div class="col">
            <%= @task['content']  %>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
重新整理一下畫面,如果取得檔案內容,代表我們成功了,恭喜完成第一個 Model!
眼尖的讀者會發現,Mavericks::Model::FileModel.find(1) 這段寫起來落落長,應該有更好的方式來處理才對,這裡我們可以用 include 來解決這個問題,還記得 include 和 extend 的差別嗎?前者是繼承 module 的 method 做為 instance method,後者是繼承 module 做為 class method
# mavericks/lib/mavericks/controller.rb
require 'erubi'
# 記得要 require
require "mavericks/file_model"
module Mavericks
  class Controller
    # include method 進來
    include Mavericks::Model
.
.
(略)
在 controller.rb 裡面 require file_model,然後在 class Controller 裡面 include Mavericks::Model
所以在 jsut_do 那邊,就可以改寫成這樣
# just_do/app/controllers/tasks_controller.rb
class TasksController < Mavericks::Controller
  def index
  end
  def show
    @task = FileModel.find(1)
  end
end
重新整理看看結果有沒有改變?正常來講應該會一樣,這樣是不是減少了很多冗長的程式碼?
我知道開發者想說什麼,光只有 #find 是不夠的,今天只是 Model 初體驗,明天會在補上其他的 method,讓我們的 file_model 越來越強大也越來越完整