rails 中,initializer 里 new 一个 Clazz 对象加入数组(全局变量): clazz = Clazz.new $arr = [clazz]
rails 启动后,再判断$arr 中的 clazz 对象是否为 Clazz: Clazz === $arr.first
得到的结果竟然是 false?!
查看$arr.first.class,确实是 Clazz,当然它也可以正常执行 Clazz 类的任何实例方法。
所以这是为什么呢?请教。
我 new 了一个空白项目,只增加 2 行代码,复现了这个问题。
方法如下:
默认的初始项目,rails 版本是最新的,ruby 版本是 3.0.1,不过 3.0.2 也试过一样。
好像传不了附件,那我就把项目打包放到这个网址了,想复现的可以下载:
做出来了,rails new 一个空白项目,两行代码重现。明天发上来供大家围观,不会真的是 rails 的 bug 吧。。
好像只有在 controller、model 中调用全局变量才会这样,如果在 global_vars 直接判断是正常的。is_a、instance_of、kind_of 我全试过了都一样
我找到原因了。我首先是去 github 上翻找 ISSUE,找到了 2012 年的这个, 原因在于 development 模式下,app 目录下的代码会重新加载。
有两种方法可以说明这个问题:
bundle exec rails s -e production
require File.expand_path("../../../lib/clazz.rb", __FILE__)
,测试结果是预期的。感谢大佬,这么快就找到原因了 能否再请指教一下,应该怎样写这个代码呢?我只是想在 controller/model 使用全局变量而已,如果 dev 和 pro 模式的行为不一样,难道要写两种方法来判断吗?
我调试了一下,开发环境下在 initializer 里面打印 Clazz 的 object_id 和在 rails console 里面打印的 object_id 是不同的,不仅如此,每次修改 Clazz 的代码,它的 object_id 都会变化。但是生产模式不会有这个问题,所以我猜想是 Rails 开发模式的 auto reload 造成的。
我查文档证实了这个想法 https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#reloading
为了实现开发过程修改源码后立即反应到已经启动的应用里面,实际上 Rails auto reload 会移除旧的 class 常量,然后用同一个名字新建一个 class。class 已经不同了,所以用 ===
检查是 false。详情可以看上面的连接。
结论是,在 auto reload 开启的环境下,不要 cache 可以 reload 的 class/module。
我需要在运行时记录一个状态值,运行过程中值会变化,多个不同的方法都需要用到这个值,而且多个 workers 需要能取到。当然不用全局变量肯定也有别的办法,比如 redis,但全局变量是我能想到的最简单的方法了。
试了下,确实把 config.cache_classes 设为 true 就正常了。但这样的话 dev 模式下就不能改了代码直接生效了,还有什么别的解法吗
把类定义放在 auto load path 以外的地方,例如 `lib'。
全局变量在多进程模式是不会共享的,在一个进程改了另一个进程还是旧的。比较推荐 redis。
“记录一个状态值”,具体是什么值呢?不同的数据有不同的处理方法,有持久化的,动态缓存的,或是只有启动时更改的配置。不同的数据有不同的处理方法。要说具体需求才能给出合适的解答。
建议看看 X-Y Problem https://coolshell.cn/articles/10804.html
用全局变量确实不好,但就这个 load 的多次的情况是有解的:
# ActiveSupport.on_load(:action_controller) do |base|
ActiveSupport.on_load(:active_record) do |base|
$clazz = Clazz.new
p Clazz === $clazz
end
(两个 callback 都可以)
不确定你具体需求,如果只需要这个全局变量存活于一个 request 周期内,那 Rails 提供了一个https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html