在 Rails 启动期间创建 class 的实例

Creating instances of a class during Rails startup

我试图在 Rails 加载时创建 class 的多个实例,并使这些实例可用。 (我正在从 YAML 文件加载数据,但我已经抽象出这个问题的细节。)我有一个非 ActiveRecord 模型,它带有加载数据的 instantiate class 方法。当我从自定义初始化程序中从 config.after_initialize and/or 调用 instantiate 时,实例已创建,但是当 rails console 完成加载时,它们已经消失了。我在哪里可以实例化数据,以便它在 rails 控制台(和服务器)内可用?

# app/models/test.rb
class Test
  include ActiveModel::Model
  attr_accessor :name

  class << self
    include Enumerable

    def each
      ObjectSpace.each_object(self).each do |object|
        yield object
      end
      self
    end

    def find_by_name(input)
      find { |object| object.name.to_s == input.to_s }
    end

    def instantiate
      new(name: 'Alice')
      new(name: 'Bob')
    end
  end

  def initialize(*parameters)
    super(*parameters)
    freeze
  end

  delegate :to_s, to: :name
end


# config/application.rb
module MyApp
  class Application < Rails::Application
    config.after_initialize do
      p "Test instances before after_initialize: #{Test.count}"
      Test.instantiate
      p "Test instances after after_initialize: #{Test.count}"
    end
  end
end


# config/initializer/test_initializer.rb
p "Test instances before test_initializer: #{Test.count}"
Test.instantiate
p "Test instances after test_initializer: #{Test.count}"


$ rails console
"Test instances before test_initializer: 2"
"Test instances after test_initializer: 4"
"Test instances before after_initialize: 0"
"Test instances after after_initialize: 2"
Loading development environment (Rails 4.2.6)
irb(main):001:0> Test.count
=> 0
irb(main):002:0> Test.instantiate
=> #<Test:0x007fad2204d8b0 @name="Bob">
irb(main):003:0> Test.count
=> 2

Ruby 垃圾收集器从内存中删除实例。不存在引用新创建的实例的直接链接,因此 Ruby 认为它们是不必要的。

您可以尝试添加:

module MyApp
  class Application < Rails::Application
    config.after_initialize do
      GC.disable # Disable garbage collector
      p "Test instances before after_initialize: #{Test.count}"
      Test.instantiate
      p "Test instances after after_initialize: #{Test.count}"
    end
  end
end

但是禁用垃圾收集器并不是一个好主意。如果您以某种方式引用实例,它们将不会被清除,它们将在启动后通过引用可用。

[重写 / 更新代码]

@Laura Paakkinen 非常感谢您的回答。您是正确的,根本问题是对未引用的实例进行垃圾回收。而且,你也是正确的,因为我的 class 正在进行实例化,它可以简单地保留一个实例变量来跟踪 class.

的所有成员

与您下面的方法不同,我决定通过 all 方法添加记忆。特别是,我发现在 rails 控制台的 运行 reload! 上,条件 class 的内容将被删除。我的解决方案是在 all 中添加一个测试,并在 memoized 实例变量为空或 nil 时实例化。这也意味着不再需要初始化器,因为 Class 现在在第一次使用时实例化。请注意,each 调用 all,并且通过 Enumerable mixin,所有其他 class 方法调用 each.

再次感谢您的周到回复,让我得到了更好的答案。

class Test
  include ActiveModel::Model
  attr_accessor :name, :value

  class << self
    include Enumerable

    def [](key)
      instantiate if @test.blank?
      @test[key.to_sym]
    end

    def all
      instantiate if @test.blank?
      @test.values
    end

    def each
      all.each do |object|
        yield object
      end
      self
    end

    def keys
      map(&:name)
    end

    def instantiate
      @test = {}
      @test[:Alice] = new(name: 'Alice', value: 1)
      @test[:Bob] = new(name: 'Bob', value: 2)
    end
  end

  # Instance Methods

  def initialize(*parameters)
    super(*parameters)
    freeze
  end

  delegate :to_s, to: :name
end

参考您发布的解决方案。由于您可以控制实例化过程,因此根本不需要使用 ObjectSpace。如果你想让 Test class 只记住在 #instantiate 方法中创建的两个实例,你可以这样:

class Test
  @objects = []

  include ActiveModel::Model
  attr_accessor :name

  class << self
    include Enumerable

    def each
      if block_given?
        objects.each { |o| yield o }
      else
        objects.to_enum
      end
    end

    def find_by_name(input)
      find { |object| object.name.to_s == input.to_s }
    end

    def instantiate
      objects << new(name: 'alice')
      objects << new(name: 'Bob')
    end

    private

    attr_accessor :objects
  end

  def initialize(*parameters)
    super(*parameters)
    freeze
  end

  delegate :to_s, to: :name
end

如果你想让Testclass记住所有被实例化的对象。您可以更改行:

...
    def instantiate
      new(name: 'alice')
      new(name: 'Bob')
    end
  end

  def initialize(*parameters)
    super(*parameters)
    freeze
    self.class.objects << self
  end
...