在rails里面打开某个gem的类,补充一下等等,也是常见的,虽然不太欢迎这么做。
为何有时候不生效?
如果gem里面已经登记了autoload :ConstName "some_path"则ruby就可以找到了,不会走rails的机制了。(原因看上一篇里的解释)
在rails代码里,手动require也不起作用?
ruby的autoload只是登记作用,并不加载登记的文件。
只有当使用这个常量的时候,才加载该文件。
如果已经使用了该常量并促发了加载该文件,则再次require的时候,因为名字是一样的,由require的机制可以得知不会再执行了。
如何才能生效?
知道了原理是最好的,也就知道了不能起作用的一万种原因。
如何才能打开gem中使用了autoload机制的类。(如果gem没有使用autoload,则不会有问题,想想为何?提示:还是require的机制)
#一般我们的目的是改写,而不是完全重写,所以需要起一个不同的名字,否则require将不起作用
但这样很不好,会破坏rails的规则。
参考的这篇http://blog.yorkxin.org/posts/2014/02/10/autoload-in-ruby-autoload-paths-in-rails-and-module-reopening/文章说了一个方法:“如果該 class / module 已經在 Gem 裡面載入,則要在 Rails 裡面 reopen 它,就必須放在 autoload_paths
以外的地方,並且手動 require 之”
这个说法其实还是有很多问题的,虽然这么做是可以实现结果的。
为什么必须放在autoload_paths
以外的地方?其实放在以内也是可以的,只要换个文件名字,但缺点也说了,会破坏rails的命名和规则。
如果命名规则符合rails的规范,放在了autoload_paths
以外的地方以外的地方,就一定行么?
不一定。如果别的地方正好在ruby的load_paths里也不一定行。只不过一般在rails项目里,rails已经做过了处理,把autoload_paths放入了ruby的load_paths了。
我们的类会优先执行么?也就是担心改写名弄成反而把原来的类给改了,导致很多莫名其妙的问题
不会的。
看看这个代码,猜猜执行结果。
#autoload_lib/user.rb class User puts "====autoload_lib/user loaded" end
#autoload_lib2/user.rb class User puts "====autoload_lib2/user loaded" end
#main autoload :User, "autoload_lib/user" require "autoload_lib2/user" user = User.new puts "END"====autoload_lib/user loaded
上面的运行结果,可以得知,autoload在没有改写的情况下,只要碰到了这个常量,就去加载对应的文件了。不错啊,ruby这个机制还是很科学滴。
能不能改写autoload呢?
居然能,我的天,这个可别乱用吧。
看代码:
autoload :User, "autoload_lib/user" autoload :User, "autoload_lib2/user" # require "autoload_lib2/user" #没作用了 user = User.new puts "END"
运行结果:
====autoload_lib2/user loaded
END
居然真改写了。你觉得呢?
团队都这么用,管技术的就累死了。
prefer:http://blog.yorkxin.org/posts/2014/02/10/autoload-in-ruby-autoload-paths-in-rails-and-module-reopening/ 有改动和补充
为了防止丢失,复制到这里吧。
關於 Ruby 的 autoload 與 Rails 的 autoload_paths 以及 reopen module / class
最近在實作一個特別的需求,做了一個 gem 搞這種事:
- 在 Gem 裡面,
lib/models/post.rb
定義Post < ActiveRecord::Base
- 在 App 裡面,
app/models/post.rb
打開class Post
多寫一些 app-specific methods
然後就搞了三天搞不定。
具體的現象是:
- 在 Gem 裡面,不論是使用
Kernel#autoload
還是 Rails 的config.autoload_paths <<
來做到自動載入,都無法在 App 改寫 Post class 。 - 如果在 Gem 裡面不做 autoloading ,則 Rails 會去抓 App 裡面的
app/models/post.rb
, which is not inherited from ActiveRecord::Base 。
之後試了繼承(很難搞)和 module ,最後是用 ActiveSupport::Concern 包了 module ,把 association 之類的東西寫在 included do
裡面,解決。
今天讀到這篇文章 Rails autoloading — how it works, and when it doesn't ,對於 Ruby 和 Rails 的 "autoload" 有粗略的瞭解了。簡單整理如下:
- Ruby 的
Kernel#autoload
是告訴 Ruby runtime 「要找某個 constant 的時候,可以載入某檔案」,比較像是「登記」,在登記之後, Ruby runtime 若發現程式裡面有要用某個 const ,但沒有定義,就會載入該檔案,這是發生在「第一次使用」的時候,用第二次就不會觸發。 - Rails 的 autoloading 跟 Ruby 的
Kernel#autoload
完全不一樣,實作方式是用Module#const_missing
:抓不到(const 在 runtime 沒定義)的時候才自動根據 constant 找檔名,例如Taiwan::Taipei::SungShan
就是會找taiwan/taipei/sung_shan.rb
。 - 承上,「要去哪裡找檔案」這件事,是在
config.autoload_paths
設定的,這個 array 就是「要自動從檔案載入缺失的 const 的時候,就去依序搜尋哪些路徑」,類似 shell 的$PATH
。如果檔案不存在,就會 raise NameError ;如果檔案存在,但 const name 跟所要找的不同,就會出現「Expected app/models/user.rb to define User」這種錯誤。 - 承上,第一次載入完成以後,就可以在 Runtime 裡面找到,所以不會再度觸發
const_missing
來自動搜尋。
所以:
-
Kernel#autoload
不應跟 Rails 的autoload_paths
混淆,要視為兩個完全不同的功能 - 誰第一次載入誰算數, Rails 只在找不到該 const 的時候才會去
autoload_paths
搜尋 - 所以,如果某個 const (class / module) 已經在 runtime 裡面定義了,那麼要在 Rails 裡面 reopen 它,就必須確定它一定會執行,例如 initializers 裡面,或是手動 require 它。如果是放在某個 autoload paths 裡面,例如
app/models/
,則 Rails 並不會執行之,因為同名的 const 已經在 Runtime 裡面了。
這也就是為什麼會有「在 gem 和在 app 裡面,同名的 model class 是 mutually-exclusive,除非手動 require 才能改寫其內容」。也就是說,想要在 gem 裡面定義一個 model ,然後在 rails app 裡面 reopen 它,是不可能的,必須要手動載入它的 reopening。
說得更 general 一點就是:如果該 class / module 已經在 Gem 裡面載入,則要在 Rails 裡面 reopen 它,就必須放在 autoload_paths
以外的地方,並且手動 require 之。
該文很推薦一讀,除了詳細說明了 Ruby 和 Rails 的 autloading 機制,還提到一些陷阱,例如說 Rails 的 autoloading 其實不會理 Module.nesting (lexical context of current line) ,這樣子某些情況下會變成「第一次可以成功 autoload ,但第二次卻說 NameError 找不到 const」這種問題。
+
+
+
=
-
-
-