在 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
如果你想让Test
class记住所有被实例化的对象。您可以更改行:
...
def instantiate
new(name: 'alice')
new(name: 'Bob')
end
end
def initialize(*parameters)
super(*parameters)
freeze
self.class.objects << self
end
...
我试图在 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
如果你想让Test
class记住所有被实例化的对象。您可以更改行:
...
def instantiate
new(name: 'alice')
new(name: 'Bob')
end
end
def initialize(*parameters)
super(*parameters)
freeze
self.class.objects << self
end
...