![](/style/images/good.png)
![](/style/images/bad.png)
Rails7 的Zeitwerk模式解惑
source link: http://xfyuan.github.io/2022/11/rails7-zeitwerk-mode/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Rails7 的Zeitwerk模式解惑
![Rails7 的Zeitwerk模式解惑](https://gcore.jsdelivr.net/gh/xfyuan/ossimgs@master/uPic/image20221121.jpeg)
本文已获得原作者(Athira Kadampatta、Supriya Laxman Medankar)和 Kiprosh 授权许可进行翻译。原文详细讲述了 Rails 7 中新的 Zeitwerk 自动加载模式。
- 原文链接:Autoloading pitfalls fixed by Rails 7’s default Zeitwerk mode
- 作者:Athira Kadampatta、Supriya Laxman Medankar
- 站点:Kiprosh,一家印度的软件开发公司。
【正文如下】
Rails 中传统的 autoloader 很有帮助,但仍然有一些瑕疵造成自动加载偶尔会出毛病。为了解决这个问题, Xavier Noria 在 Rails 6 的这个 PR 中提出了 zeitwerk 模式并使其可配置使用。Rails 7 则更进一步,zeitwerk 完全替代了传统的 autoloader。
本文中,我们会看看传统的自动加载会碰到的问题,以及 Zeitwerk 模式如何解决的。(你可以阅读这篇文章 来理解 Rails 的 autoloader 是怎样工作的)。
How classic autoloading works?
起初,Rails 使用的是在 Active Support 中称作 Classic Autoloading 的实现来作为 autoloader,一直持续到 Rails 6。
Classic Autoloading 依赖的是 Ruby 的常量查找。要解析一个常量,会首先在所定义的类的词法域中查找,然后在其祖先链中查找。如果该常量未找到,const_missing
方法就会被 Ruby 调用。Rails 覆写了 Ruby 的const_missing
方法,并使用autoload_paths
根据惯例约定来解析常量。
How zeitwerk autoloading works?
新引入的 Zeitwerk Mode 则不依赖 Ruby 的常量查找。
相反,它利用的是 Ruby 的 Module#autoload
方法提前告知 Ruby 哪个文件将定义一个特定常量,而不需要立即加载该文件。
![https://blog.kiprosh.com/content/images/2022/09/Rails-autoload-how-it-works.png](https://blog.kiprosh.com/content/images/2022/09/Rails-autoload-how-it-works.png)
Common Problems resolved by zeitwerk mode
传统模式存在许多问题,但都已被 zeitwerk 模式解决了。这其中,我们会看看三个不同的陷阱,每个都带有示例。
1、When Constants aren’t Missed
假设我们有如下 model 结构:
# course.rb
class Course
def initialize
puts "From Course"
end
end
# mit_university/course.rb
module MitUniversity
class Course
def initialize
puts "From MitUniversity::Course"
end
end
end
# mit_university/engineering.rb
module MitUniversity
class Engineering
def initialize
@course = Course.new
end
end
end
With Classic Mode
Loading development environment (Rails 5.2.7.1)
2.7.5 :001 > Course.new
From Course
=> #<Course:0x0000563bfa029810>
2.7.5 :002 > MitUniversity::Engineering.new
From Course
=> #<MitUniversity::Engineering:0x0000563bf9e7ab40 @course=#<Course:0x0000563bf9e7aaf0>>
这里,由于我们在调用MitUniversity::Course
之前调用了Course
,Ruby 的常量查找就已经在内存中自动加载了Course
,所以如果我们想要为MitUniversity::Engineering
创建一个对象时,它就会引用到已在内存中被自动加载的Course
,而不去搜索MitUniversity::Course
了。这让自动加载依赖于常量被调用的顺序。
With Zeitwerk Mode
Loading development environment (Rails 7.0.3)
2.7.5 :001 > Course.new
From Course
=> #<Course:0x00005615a9707fa0>
2.7.5 :002 > MitUniversity::Engineering.new
From MitUniversity::Course
=> #<MitUniversity::Engineering:0x00005615ae41d290 @course=#<MitUniversity::Course:0x00005615ae40b270>>
2.7.5 :003 >
因为 zeitwerk 模式为所有常量定义了autoload_path
,它已经知道了到哪里去查找哪个常量。所以尽管首先初始化Course
,但在MitUniversity::Engineering
类中调用时,它仍然如期望的那样加载了MitUniversity::Course
。
2、Autoloading within Singleton Classes
这是一个关于 Singleton 类方法的类似问题,已经被 zeitwerk 模式所解决。例子如下所示:
# mit_university/course.rb
module MitUniversity
class Course
def initialize
puts "From MitUniversity::Course"
end
end
end
# mit_university/engineering.rb
module MitUniversity
class Engineering
class << self
def details
Course.new
end
end
end
end
With classic mode
如果我们在调用MitUniversity::Course
之前调用 MitUniversity::Engineering.details
,它将会抛出uninitialized constant Course
的错误。这是由于,当自动加载被触发时,Rails 只去检查顶层命名空间,因为 singleton 类是匿名的,所以 Rails 不会知道嵌套的MitUniversity
。
Loading development environment (Rails 5.2.7.1)
2.7.5 :001 > MitUniversity::Engineering.details
Traceback (most recent call last):
2: from (irb):3
1: from app/models/mit_university/engineering.rb:5:in `details'
NameError (uninitialized constant Course)
2.7.5 :002 > MitUniversity::Course
=> MitUniversity::Course
2.7.5 :003 > MitUniversity::Engineering.details
From MitUniversity::Course
=> #<MitUniversity::Course:0x0000560cabb27c18>
With zeitwerk mode
Zeitwerk 模式则不会抛出任何错误,并且即使之前没有自动加载它也能载入该常量。
Loading development environment (Rails 7.0.3)
2.7.5 :001 > MitUniversity::Engineering.details
From MitUniversity::Course
=> #<MitUniversity::Course:0x0000559ebba13dd0>
3、Autoloading and Single-table Inheritance (STI)
假设我们有如下 Single-table Inheritance (STI) 的 model 已定义:
class Polygon < ApplicationRecord
end
class Triangle < Polygon
end
class Rectangle < Polygon
end
class Square < Rectangle
end
Square
继承自 Rectangle
,所以当我们调用Rectangle.all
时,结果必须包含Polygon
类型的Square
以及Rectangle
。
With classic mode
然而,当我们调用Rectangle.all
时,并不能在结果中看到Square
记录。我们可以看到所生成的 SQL 查询中并未包含Square
。
Loading development environment (Rails 5.2.7.1)
2.7.5 :001 > Rectangle.all
Rectangle Load (0.4ms) SELECT `polygons`.* FROM `polygons` WHERE `polygons`.`type` = 'Rectangle' /* loading for inspect */ LIMIT 11
=> #<ActiveRecord::Relation [#<Rectangle id: 1, area: 100.0, type: "Rectangle", type_id: nil, created_at: "2022-08-28 14:00:50.705523000 +0000", updated_at: "2022-08-28 14:00:50.705523000 +0000">, #<Rectangle id: 2, area: 200.0, type: "Rectangle", type_id: nil, created_at: "2022-08-28 14:01:09.543825000 +0000", updated_at: "2022-08-28 14:01:09.543825000 +0000">]>
2.7.5 :002 > Square
=> Square(id: integer, area: float, type: string, type_id: integer, created_at: datetime, updated_at: datetime)
2.7.5 :003 > Rectangle.all
Rectangle Load (0.9ms) SELECT `polygons`.* FROM `polygons` WHERE `polygons`.`type` IN ('Rectangle', 'Square') /* loading for inspect */ LIMIT 11
=> #<ActiveRecord::Relation [#<Rectangle id: 1, area: 100.0, type: "Rectangle", type_id: nil, created_at: "2022-08-28 14:00:50.705523000 +0000", updated_at: "2022-08-28 14:00:50.705523000 +0000">, #<Rectangle id: 2, area: 200.0, type: "Rectangle", type_id: nil, created_at: "2022-08-28 14:01:09.543825000 +0000", updated_at: "2022-08-28 14:01:09.543825000 +0000">, #<Square id: 5, area: 250.0, type: "Square", type_id: nil, created_at: "2022-08-28 14:01:52.165141000 +0000", updated_at: "2022-08-28 14:01:52.165141000 +0000">]>
要解决这个问题,我们不得不在rectangle.rb
文件底部加上require_dependency 'square'
:
# app/models/rectangle.rb
class Rectangle < Polygon
end
require_dependency 'square'
With zeitwerk mode
由于在 zeitwerk 模式中,Square
已被自动加载进来,我们就无需添加require_dependency 'square'
这一行了:
Loading development environment (Rails 7.0.3)
2.7.5 :001 > Rectangle.all
Rectangle Load (0.9ms) SELECT `polygons`.* FROM `polygons` WHERE `polygons`.`type` IN ('Rectangle', 'Square') /* loading for inspect */ LIMIT 11
=> #<ActiveRecord::Relation [#<Rectangle id: 1, area: 100.0, type: "Rectangle", type_id: nil, created_at: "2022-08-28 14:00:50.705523000 +0000", updated_at: "2022-08-28 14:00:50.705523000 +0000">, #<Rectangle id: 2, area: 200.0, type: "Rectangle", type_id: nil, created_at: "2022-08-28 14:01:09.543825000 +0000", updated_at: "2022-08-28 14:01:09.543825000 +0000">, #<Square id: 5, area: 250.0, type: "Square", type_id: nil, created_at: "2022-08-28 14:01:52.165141000 +0000", updated_at: "2022-08-28 14:01:52.165141000 +0000">]>
Conclusion
对于 Rails 7,Zeitwerk 已经成为默认模式,而传统模式已不可用了。这是一个很有影响的变化,改进了 Rails 中常量自动加载的方式,解决了诸多如上所述的传统模式带来的问题。
References
Recommend
-
61
做了两年系统运维,觉得技术上有一定的瓶颈,主要是表现在平时工作仅仅是使用软件,是选择对增加软件精通程度,还是选择多用一些不同软件,大佬有何想法?还有,请大佬认为讲一下技术运维的提升方向。谢谢
-
10
Code Loaders in Ruby - Understanding Zeitwerk With Zeitwerk, you can streamline your programming knowing that classes and modules are available everywhere. What are Code Loaders? Code loaders let developers define...
-
3
Zeitwerk-based autoload and workarounds for single-file-many-classes problem Rails has deprecated its classic autoloader
-
4
Rails 7.0 Alpha 1: New JavaScript Answers, At-Work Encryption, Query Origin Logging, Zeitwerk ExclusivelyWelcome to the first alpha release of Rails 7. It brings some very exciting new answers to how we do JavaScript, an awesome approach to a...
-
2
1 What are classic and zeitwerk Modes?From the very beginning, and up to Rails...
-
2
Sunday, October 31, 2021 🎃 Halloween Edition: Zeitwerk migration guide, selenium-webdriver, and some Ruby 3.1 snacks Posted by zzak 🍭 Trick or treat,
-
7
rails_7 Published on 23 December 2021...
-
4
Starting with Rails 6, Rails shipped with a new and better way to autoload, which delegates loading to the Zeitwerk gem. This is the zeitwerk mode. Before Rails 7, it was still p...
-
6
rails_7 Published on 8 September 2022...
-
7
Introduction Rails engineers, what do you do when creating an admin panel or internal tool? One way is to use a RubyGem such as ActiveAdmin, but I’ve encountered a few problems with this approach. It's hard to customize with...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK