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" } 被认为是错误的形式,因为意图非常不明确。