Ruby:如何在模块加载到其父 class 之前预先加载 class 内容
Ruby: How to eager load class contents before a module gets loaded in its parent class
我有一些 class 常量 SCHEMA
class Consumable::ProbeDesign < Consumable
SCHEMA = {
"type": "object",
"properties": { },
"required": []
}
end
class DataModule::WaterDeprivationLog < DataModule
SCHEMA = {
"type": "object",
"properties": {
"water_amount": {"type": "decimal"},
"notes": {"type": "string"}
},
"required": []
}
end
它们是 STI 方案中基 class 的子代
class Consumable < ApplicationRecord
include SingleTableInheritable
end
class DataModule < ApplicationRecord
include SingleTableInheritable
end
然后我有一个模块需要为所有从 classes 继承的 classes 动态访问该常量,其中包括模块
module SingleTableInheritable
extend ActiveSupport::Concern
included do
def self.inherited(subclass)
subclass.class_eval do
schema = subclass::SCHEMA # NameError: uninitialized constant #<Class:0x0000560848920be8>::SCHEMA
# then do some validations that rely on that schema value
end
super
end
end
end
但是在执行时以及在调用它的上下文中它找不到模块并且 returns NameError: uninitialized constant #<Class:0x0000560848920be8>::SCHEMA
请注意 subclass.const_get("SCHEMA")
也失败了
编辑: 这是加载顺序问题。在 class 上运行之后,常量可用,因为随后加载了 class。但是通过尝试预先加载这个 class,模块在预先加载时从父 class 继承,并且模块代码仍然在设置常量之前运行。
是否有某种类似于继承但允许预加载所有内容的挂钩?
这里的问题实际上是 Module#included
在子 class 的主体被评估之前总是 运行。但是 Module#included
并不是添加验证或回调的唯一方法。
您可以定义自己的 "hook":
module SingleTableInheritance
extend ActiveSupport::Concern
class_methods do
def define_schema(hash)
class_eval do
const_set(:SCHEMA, hash)
if self::SCHEMA["type"] == "object"
validates :something ...
end
end
end
end
end
define_schema
只是一个普通的旧 class 方法,它打开了 eigenclass。这与 Rails 和 Ruby 中普遍使用的相同模式,用于从生成 setter 和 getter 到验证甚至回调的所有内容。
用法:
class DataModule::WaterDeprivationLog < DataModule
define_schema({
type: "object",
properties: {
water_amount: { type: "decimal"},
notes: { type: "string"}
},
required: []
})
end
您还应注意,您使用的 "short" 哈希语法将键强制转换为符号:
irb(main):033:0> {"foo": "bar" }
=> {:foo=>"bar"}
如果您想将字符串作为键,请改用 hashrockets =>
。 {"foo": "bar" }
被认为是错误的形式,因为意图非常不明确。
我有一些 class 常量 SCHEMA
class Consumable::ProbeDesign < Consumable
SCHEMA = {
"type": "object",
"properties": { },
"required": []
}
end
class DataModule::WaterDeprivationLog < DataModule
SCHEMA = {
"type": "object",
"properties": {
"water_amount": {"type": "decimal"},
"notes": {"type": "string"}
},
"required": []
}
end
它们是 STI 方案中基 class 的子代
class Consumable < ApplicationRecord
include SingleTableInheritable
end
class DataModule < ApplicationRecord
include SingleTableInheritable
end
然后我有一个模块需要为所有从 classes 继承的 classes 动态访问该常量,其中包括模块
module SingleTableInheritable
extend ActiveSupport::Concern
included do
def self.inherited(subclass)
subclass.class_eval do
schema = subclass::SCHEMA # NameError: uninitialized constant #<Class:0x0000560848920be8>::SCHEMA
# then do some validations that rely on that schema value
end
super
end
end
end
但是在执行时以及在调用它的上下文中它找不到模块并且 returns NameError: uninitialized constant #<Class:0x0000560848920be8>::SCHEMA
请注意 subclass.const_get("SCHEMA")
也失败了
编辑: 这是加载顺序问题。在 class 上运行之后,常量可用,因为随后加载了 class。但是通过尝试预先加载这个 class,模块在预先加载时从父 class 继承,并且模块代码仍然在设置常量之前运行。
是否有某种类似于继承但允许预加载所有内容的挂钩?
这里的问题实际上是 Module#included
在子 class 的主体被评估之前总是 运行。但是 Module#included
并不是添加验证或回调的唯一方法。
您可以定义自己的 "hook":
module SingleTableInheritance
extend ActiveSupport::Concern
class_methods do
def define_schema(hash)
class_eval do
const_set(:SCHEMA, hash)
if self::SCHEMA["type"] == "object"
validates :something ...
end
end
end
end
end
define_schema
只是一个普通的旧 class 方法,它打开了 eigenclass。这与 Rails 和 Ruby 中普遍使用的相同模式,用于从生成 setter 和 getter 到验证甚至回调的所有内容。
用法:
class DataModule::WaterDeprivationLog < DataModule
define_schema({
type: "object",
properties: {
water_amount: { type: "decimal"},
notes: { type: "string"}
},
required: []
})
end
您还应注意,您使用的 "short" 哈希语法将键强制转换为符号:
irb(main):033:0> {"foo": "bar" }
=> {:foo=>"bar"}
如果您想将字符串作为键,请改用 hashrockets =>
。 {"foo": "bar" }
被认为是错误的形式,因为意图非常不明确。