如何在rails里打开gem里面的类

Python029

如何在rails里打开gem里面的类,第1张

在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_lib2/user loaded

END

上面的运行结果,可以得知,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

February 10, 2014 · 4 Comments

最近在实作一个特别的需求,做了一个 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」这种问题。

尽管如此,有时候语言本身的差异未必能在一个具体程序里体现出来,比如一个XML流解析程序,用各种脚本语言来做,不应该有太多性能差异,原因是各种脚本语言底层的XML解析库,实际都是C写的实现,最终的parser很可能是libxml;这样一来,语言本身更多是一个wrapper,实际跑的核心代码是C code;所以性能的问题,失去了具体场景单讨论语言本身未必有多大意义,还得看项目本身对语言的使用情况。ruby开发的程序员成本真的比其他语言比如python,php更低吗?一个熟练ruby程序员和一个熟练python程序员再一个熟练php程序员开发同一组web功能,各自选择熟练的框架,ruby程序员所使用的man hour一定最低吗?我表示怀疑,在熟练的情况下,遵照MVC的原则进行开发,PHP和python本身的开发效率不认为一定低于RoR;原因是现代的开发框架,设计理念上都是你中有我,我中有你;比如就ORM来说,PHP, Python都有成熟的实现,routing,templating这种必须的piece,所有的框架都设计得足够好,很难说任何一个超越其余的一个层次;花多少man hour来实现一个项目,团队熟练程度决定大部分,语言本身和框架本身的边际效用,不一定有想像的那么大。另外,我觉得ruby不是好学的语言,至少不比python更好学,比php我觉得要难学得多;概念多,选择多,对学习者的干扰也多;这个因素应该已经被一些公司和团队意识到了,国内把ruby作为主项目语言的,或者乐意做这种切换的,也只是局限在一个很小的圈子里,真正要考量的时候,PHP或者java是更实际的选择;个人觉得有两个方面ruby很特别开放的Object系统,所有的对象(包括“类”对象)都可以在runtime修改扩充,这让在ruby下做一些事成为自然的可能,而在其他语言则不得不做各种work around;ruby的OO可元编程能力特别强大;ruby的括号是可选的,虽然这看起来只像个语法糖,但要写DSL的时候,ruby可能是最最自然的语言openfreezerliftelephantputelephantintofreezershutfreezer这样极致的可读可懂性,别的语言很难做到不露痕迹。事实上Rake就是一组DSL,RoR的ActiveRecord是一个DSL实现;Sinatra的API也是一组DSL;很多templating language也被实现成DSL;

你应该是没有正确安装mysql2的gem包。在windows下最好将database.yml中的adapter配置由mysql2改为mysql,然后再到应用目录下面执行bundle install就好了。windows下也可以装mysql2。但是只能装一个特定版本,很不爽。

据我使用mysql2一年多的经验来看。windows下mysql2没多大效率提升。

另外,我建议你用Linux系统来学习ruby。搞个ubuntu虚拟机都可以。这个不是追求高富帅,而是linux对ruby支持更好。很多gem包可以正确安装。