attribute 這個 class method 其實有限定 options key word ,只能傳入
[:precision, :limit, :scale, :default]
那這個 options 定義背後會經過很多層的傳遞,其中,:default
會在途中被 delete 去掉。
最後,這包 options 會傳到繼承 ActiveModel::Type::Value
的一系列資料轉換器的 initialize
。
前述限定 key 的部分也是在這邊去限定的。
那根據前篇,如果我想要我的 active model class 可以在 attribute 導入自定義參數,
好讓我的 form builder 可以在 render_by_type
階段藉由讀取 type object 去讀到我的自定義參數,進而做一些進階的 render 判斷,我該怎麼做?
hack 掉 ActiveModel::Type::Value#initialize
:
# in config/initializers
class ActiveModel::Type::Value
# 把原生的 initialize 保留起來
alias_method :regular_initialize, :initialize
# 建立一個 options 作為儲存自訂參數的地方
attr_reader :options
def initialize(options = {})
# hash.slice! 這方法除了會篩 hash 自身之外,還會額外 return 回被篩掉的部分,一個 method 兩個用途,很方便
@options = options.slice!(:precision, :limit, :scale)
regular_initialize(options)
end
end
接著引用如前一篇的示例,我們加一點不一樣的參數在 attribute
裡面,看看 attribute_types
裡面的結構會變怎樣:
class MyCls
include ActiveModel::Model
include ActiveModel::Attributes
attribute :name, :string, default: 'blabla', foo: 'bar'
attribute :accept_privacy_policy, :boolean, default: false, may: 'day'
attribute :age, :integer, default: 13
end
pp MyCls.attribute_types
{"name"=>
#<ActiveModel::Type::String:0x00007f904332c550
@limit=nil,
@options={:foo=>"bar"},
@precision=nil,
@scale=nil>,
"accept_privacy_policy"=>
#<ActiveModel::Type::Boolean:0x00007f9043319ef0
@limit=nil,
@options={:may=>"day"},
@precision=nil,
@scale=nil>,
"age"=>
#<ActiveModel::Type::Integer:0x00007f9043333d78
@limit=nil,
@options={},
@precision=nil,
@range=-2147483648...2147483648,
@scale=nil>}
# 自定義參數都被存在 @options 裡了!
這樣一來,就可以把您針對您的 attribute 內傳入的自訂參數給一起傳到 type object,再傳到 form builder 裡了!