iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 14
0
Modern Web

Ruby礦工的Rails地圖系列 第 14

淺談delegate -- 探囊取物

每天在上班之中尋找靈感是一個不錯的方法
就決定來聊一下整理model時很常用到的delegate

之前在第二天的時候講MTI時其實已經有使用到
但那個時候沒有講解來龍去脈,
相信新手也只是很神奇的複製貼上、會動就好XD
delegate與前幾天聊到的scope一樣
都是在model階段使用,而且可以讓程式碼更簡潔的好工具

如同我下的副標題,這個作用是協助穿越model取值,
比如說User有許多Book

class User < ApplicationRecord
    has_one :book
end

class Book < ApplicationRecord
    belongs_to :user
end

Book有一個欄位叫title
當User要取得書本title時,可以這樣寫

User.find(1).book.title
=> "book title"

這樣當然沒有錯,但假如常常需要在中間夾book
會覺得相當麻煩,有沒有更好的做法
delegate就是你更好的做法:

class User < ApplicationRecord
    has_one :book
    delegate :title, to: :book
end

當User Model增加了上述的delegate以後
剛剛的程式碼就可以簡化如下:

User.find(1).title
=> "book title"

可以得到一樣的結果,但如果你仔細觀察SQL
其實是一樣的,只是rails偷偷在背景幫你執行了
有沒有很方便、很貼心呢?

而且更神奇的來了,不只是取值,給值或方法一樣可以透過delegate設定
例如說Book有pages這個方法

class Book < ApplicationRecord
    belongs_to :user
    def pages
        #...
    end
end

class User < ApplicationRecord
    has_one :book
    delegate :title, :title=, :pages, to: :book
end

複數以上用逗號分格,有了以上的設定後
就可以使用下面的方法

user = User.first
user.title = "new title"
=> "new title"
user.pages
=> 18 #可以正常運作

假設很不巧的,你想取的欄位剛好有同名欄位
譬如說User與Book都有name這個欄位,
那當我使用user.name到底是user的name還是book的name呢?
別擔心,這個問題rails已經幫你設想好了
你可以使用前綴的功能

class User < ApplicationRecord
    has_one :book
    delegate :name, to: :book, prefix: true
end

那剛剛的方法就會變成:

user.book_name
=> "book name"
user.name
=> "user name"

如果你希望前綴的不是Model名稱,前綴也可以自訂,例如:

class User < ApplicationRecord
    has_one :book
    delegate :name, to: :book, prefix: "papaer"
    # 取用法就會變成user.papaer_name
end

如果你擔心因為User找不到Book而跳錯,也可以增加allow_nil屬性

    delegate :name, to: :book, prefix: "papaer", allow_nil: true

如此就不需擔心NoMethodError了
如果找不到,會回傳nil

神奇的是,其實delegate不只可以設定一階,可以無限多階

delegate :title, to: "book.store.company", prefix: "company"
# user.company_title

就會很神奇的一層一層透過book,store,company去找
最後回傳title
是不是很方便呢?
祝大家model整理得越來越乾淨囉。


上一篇
如何編寫一個自己的Gem
下一篇
淺談validation -- 資料的守門人
系列文
Ruby礦工的Rails地圖30

尚未有邦友留言

立即登入留言