設定 attribute
預設值為容器型別,接著建立實體時不傳值,讓物件套用預設值,
接著對這個預設值物件進行操作,會污染到 class 原本設定的預設值。
不一定是容器型別,只要是任何可以修改裡面 value 的 object 都會有這情形。
下面來做個實驗:
class MyClass
include ActiveModel::Model
include ActiveModel::Attributes
attribute :body, default: []
# 設定一個 array 作為預設值
end
obj1 = MyClass.new
obj2 = MyClass.new
# obj1 往 body 裡面塞值,很正常的操作
obj1.body << 'head'
obj1.body
=> ["head"]
obj2.body
=> ["head"]
# 神奇的事發生了,我改了 obj1 的東西,但 obj2 也連帶被影響到
obj3 = MyClass.new
obj3.body
=> ["head"]
# 甚至連後來新建的物件也被影響
會發生這種事是因為,當我們在 class 設定預設值的時候,傳入了一個要作為預設值的物件(這邊是用[]
)。
那後續這個作為預設值的物件,就會在 class 建立實體的時候從 class 本體被拉出來。
且不管你 new
幾次實體,只要有使用到預設值,那個跑出來的預設值永遠都會是同一個 object,同一組 object_id,同樣的記憶體位置!
那讀者或許會想,但我還是想要空的 array 做為預設值啊,那該怎麼做呢?
你需要把 array 包在 block 裡面傳進去
For example:
class MyClass
include ActiveModel::Model
include ActiveModel::Attributes
attribute :body, default: -> { [] }
# 改用block 包住 array 作為預設值
end
obj1 = MyClass.new
obj2 = MyClass.new
# obj1 往 body 裡面塞值,一樣的操作
obj1.body << 'head'
obj1.body
=> ["head"]
obj2.body
=> []
# obj2 的預設值正常了
obj3 = MyClass.new
obj3.body
=> []
# 後來新建的物件的預設值也正常了
總結,如果你想要設定容器型別作為預設值,就要先用 block 包住,才能確保對預設值那個物件的操作不會回過頭來影響到 class 本身~。