iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 15
0

昨天我們現在實作了 create,當然只有 create 是遠遠不夠的,今天我們要把其他的功能也補上,其實大部分都在 file_model.rb 實作過,只是將他改寫為用 SQL 語法來執行而已,今天就來看看其他 ORM 的部分吧

關於搜尋的部分 find & all

要實作 find,我們一樣要借助 SQL 的條件語法 WHERE,大概的用法就像是這樣

SELECT column1, column2, ...
FROM table_name
WHERE condition;

所以要做出 find method,可以這樣寫

# mavericks/lib/mavericks/sqlite_model.rb

def self.find(id)
  row = DB.execute <<-SQL
    select #{schema.keys.join ","} from #{table}
    where id = #{id};
  SQL

  data = Hash[schema.keys.zip row[0]]
  self.new data
end

我們一樣加在 class method 底下,取出資料後一樣 new 一個物件做回傳

那 all 呢?做法跟 find 很像,差別在於我們把 WHERE 條件語法拿掉,並且回傳的是一個陣列

# mavericks/lib/mavericks/sqlite_model.rb

def self.all
  row = DB.execute <<-SQL
    select #{schema.keys.join ","} from #{table}
  SQL

  row.map do |attr|
    data = Hash[schema.keys.zip attr]
    self.new data
  end
end

如果你把程式碼中的 row 印出來看,會看到資料結構長的像是這樣

[[1, "鐵人30", "每天一篇文章"], 
[2, "週末聚餐", "跟朋友喝一杯"], ...]

透過 map 搭配昨天提過的 zip, 來做 key 和 value 的 merge

建立方法來取得 attribute

就跟 file_model.rb 一樣,我們目前還沒有 method 可以取得資料欄位裡面的內容,需要實作出 getter 以及 setter,這裡我們一樣先用 [] 來實作這一塊

# mavericks/lib/mavericks/sqlite_model.rb

def [](name)
  @hash[name.to_s]
end

def []=(name, value)
  @hash[name.to_s] = value
end

細心的你應該會發現,這時候我們是做 instance method,而不是 class method,相信有跟著做過 file_model.rb 的讀者應該不會陌生,接著回到 just_do 修改一下測試的程式碼

# just_do/sqlite_test.rb

require 'sqlite3'
require 'mavericks/sqlite_model'

class Task < Mavericks::Model::SQLite
end

Task.create("title": "鐵人30", "content": "每天一篇文章")
puts "Count: #{Task.count}"

Task.all.each { |task| puts task['title'] }

puts Task.find(1)['content']

接著執行看一下結果

$ bundle exec ruby sqlite_test.rb

如果沒意外的話,應該可以看到 Count: 數量 這個資訊,因為裡面包含一個執行 create 的程式碼,所以每執行一次 sqlite_test.rb 數量就會加1,另外我們也可以透過 Task.all 來找出所有的 Task,並且利用 each 來印出每一個 tasktitle,最後透過 find 來尋找特定 ID 的資料,最後透過 task['content'] 來取得特定欄位的值

把 Save 也補上吧

file_model.rb 裡面,我們沒有實作修改的部分,我們需要能像 Rails 一樣,透過修改物件屬性,搭配 save 來修改或新增資料,例如像是

# 修改
task['title'] = 'IT邦幫忙鐵人賽'
task.save

# 或是 new 一個 Object 來新增資料
task = Task.new('title': '鐵人30', 'content': '每天一篇文章')
task.save!

接下來就來實作吧!

# mavericks/lib/mavericks/sqlite_model.rb

def save!
  unless @hash["id"]
    # 如果沒有 ID 就當作是要新增新的資料
    self.class.create(@hash)
    return true
  end

  fields = @hash.map { |key, value| "#{key}=#{self.class.to_sql(value)}" }.join ","
  # 如果帶有 ID,代表要對特定資料做修改
  DB.execute <<-SQL
    UPDATE #{self.class.table}
    SET #{fields}
    WHERE id = #{@hash["id"]}
  SQL

  true
end

def save
  self.save! rescue false
end

這裡比較特別的是,我們實作了兩個 method,一個是 save,另一個是 save!,寫過 Rails 的人應該對這兩個的差別不陌生,其實就是對應到 Rails 處理 Exception 的方式,如果呼叫的是 save,就算過程中出錯,我們也會利用 Ruby 的 rescue 來回傳 false,那如果呼叫的 save!,就會拋出 Exception

另外值得注意的是,前面所提到的,save 代表了兩種意義,,一個是新增資料的 save,一個是修改資料的 save,比較直覺的做法是透過檢查 @hash['id'],來判斷要執行那個動作,也可以用 SQL 語法的角度來思考,如果沒有 ID ,其實也無法針對特定資料做修改的動作,所以我們假設只要不存在 ID,就當作 save 要新增資料

一樣回到 sqlite_test.rb 測試看看結果

# just_do/sqlite_test.rb

require 'sqlite3'
require 'mavericks/sqlite_model'

class Task < Mavericks::Model::SQLite
end

task = Task.find(1)
task['title'] = '鐵人40'
task.save
puts Task.find(1)['title']

接著執行看一下結果

$ bundle exec ruby sqlite_test.rb

沒意外的話,最後印出來的應該會是 鐵人40

至於那個刪除...

其實做法都是類似,相信聰明的讀者大大們應該都可以做得出來(其實是懶),我就不再多花篇幅介紹了,那接下來還有什麼好繼續寫的?當然有的!有發現到我們一直用 [] method 來代替 . 的用法來取得物件的 Attribute 嗎?應該是要 task.title 而不是 task['title'],明天我們就來探討實作上的做法吧!


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

尚未有邦友留言

立即登入留言