我喜歡冰糕界面功能!
并且在冰糕檔案中有一段使單例方法變得抽象。這似乎是反序列化和遷移(向上轉換)的一個很好的功能。
我的想法是將Typed Struct的序列化版本存盤在資料庫中。因為結構會隨著時間的推移而演變,我還想提供一些功能來將結構的舊序列化版本轉換為當前版本。
實作這一點的方法是將類的名稱、資料和版本保存到資料庫中。假設這個結構
class MyStruct < T::Struct
const :v1_field, String
const :v2_field, String
def self.version
2
end
end
資料存盤中的舊序列化版本可能如下所示:
| 班級 | 資料 | 版本 |
|---|---|---|
MyStruct |
{"v1_field": "v1 value"} |
1 |
我不能只是反序列化資料,因為它缺少必填欄位v2_field。所以我的想法是為遷移提供單例方法。
module VersionedStruct
module ClassMethods
abstract!
sig { abstract.returns(Integer) }
def version; end
sig { abstract.params(payload: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
def migrate(payload); end
end
mixes_in_class_methods(ClassMethods)
end
class MyStruct < T::Struct
include VersionedStruct
const :v1_field, String
const :v2_field, String
sig { override.returns(Integer) }
def self.version
2
end
sig { override.params(payload: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
def self.migrate(data)
return if data[:v2_field]
data.merge(v2_field: "default value")
end
end
注意:我意識到 struct 欄位有一個默認選項,但是有些遷移無法用它建模(例如重命名欄位名稱)。不幸的是,這些單例方法介面的行為方式與我期望介面作業的方式不同:
class DataDeserializer
sig { params(data_class: String, data_version: Integer, data: T::Hash[Symbol, T.untyped]).returns(T.any(MyStruct, MyOtherStruct, ...)) }
def load(data_class, data_version, data)
struct_class = Object.const_get(data_class)
migrated_data = if struct_class.include?(VersionedStruct) # This seems to be the only check that actually returns true for all classes that include the interface
migrate(data_version, T.cast(struct_class, VersionedStruct), data)
else
data # fallback if the persistent data model never changed
end
struct_class.new(migrated_data)
end
private
sig { params(data_version: Integer, struct: VersionedStruct, data: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
def migrate(data_version, struct, data)
return data if data_version == struct.version # serialized data is up to date
struct.migrate(data)
end
end
此代碼(或此代碼的變體)將不起作用,因為冰糕會引發錯誤,說明:
Method `version` does not exist on `VersionedStruct`
Method `migrate` does not exist on `VersionedStruct`
將簽名更改為T.class_of(VersionedStruct)將引發相同的錯誤:
Method `version` does not exist on `T.class_of(VersionedStruct)`
Method `migrate` does not exist on `T.class_of(VersionedStruct)`
即使方法是在類級別定義的。我不在實體級別包含方法的主要原因是因為我無法在沒有正確資料的情況下實體化結構。
uj5u.com熱心網友回復:
我認為您想要extend VersionedStruct而不是嘗試在類方法技巧中進行魔術混合。這真的很好用:
# typed: true
module VersionedStruct
extend T::Sig
extend T::Helpers
abstract!
sig { abstract.returns(Integer) }
def version; end
sig { abstract.params(payload: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
def migrate(payload); end
end
class MyStruct < T::Struct
extend T::Sig
extend VersionedStruct
const :v1_field, String
const :v2_field, String
sig { override.returns(Integer) }
def self.version
2
end
sig { override.params(data: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
def self.migrate(data)
return {} if data[:v2_field]
data.merge(v2_field: "default value")
end
end
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/338979.html
