The Kai Way

Pragmaticly hacking

Rails Internal Hierarchy

| Comments

本文是Inspect Rails的一部分,Inspect Rails是由我正在编写的讲解Rails内部的实现与设计的一本书,欢迎阅读

Rails 内部有清晰的层级结构,以实现Rails应用程序和Rails插件的配置以及初始化。

如上图所示,所有Rails Application继承自Rails Engine,而Rails Engine继承自Railtie,这套继承体系的实现全部都封装在railties这个Rubygem里。值得一提的是,Railtie和Rails Engine的子类都是Singleton,Rails Application本身就是Singleton,所以在一个程序里Rails Application只有一个实例。

Railtie

我们先从Railtie说起,如果你翻查过一些Rails插件的源码,会发现它都继承了Railtie。Railtie位于层级里最低最底层的部分,它实现了配置和初始化这两大功能,其中的逻辑都组织在以下两个Modules中

  • Initializable, 实现位于rails/lib/rails/initializable
  • Configuration, 实现位于rails/lib/rails/railtie/configuration

Initializable 模块顾名思义就是负责初始化,常用的方法只有一个叫initializer的方法,它的方法签名如下,接受一个名字,一个可选的参数,一个代码块

1
initializer(name, opts = {}, &blk)

定义好的 Initializer 代码块会在Rails应用程序启动时执行,并且可以在参数里指定before或者after选项, 让其在某个已定义的Initializer执行之前或之后执行,这个功能是通过Ruby内置的TSort实现的。以下是ActiveRecord设置Logger的Initializer

1
2
3
initializer "active_record.logger" do
  ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
end

Configuration 是实现常见的config.xxx = yyy这一常见写法的源头,它使用了Ruby的method_missing实现了配置参数的属性访问和设置,全部的配置都放在一个名为@@options的类变量里。

1
2
3
4
5
6
7
8
9
def method_missing(name, *args, &blk)
  if name.to_s =~ /=$/
    @@options[$`.to_sym] = args.first
  elsif @@options.key?(name)
    @@options[name]
  else
    super
  end
end

Action Mailer, Action Controller, Action View和Active Record 它们集成到Rails框架的都是通过Railtie实现。

如果你去看现实中的各种插件写的Railtie中,基本上就是调用initializer方法配置初始化逻辑和通过config变量在添加自身相关的各种配置选项。而且Rails也使用这两个模块去设置各种框架本身的初始化和参数配置。

Railtie除此之外,它还负责rake tasks和generator等部分与Rails应用程序的集成,暂不讲。

Rails Engine

Rails Engine主要的设想就是把一些通用的Rails应用程序抽象出来并得到重用,也就是说每个Rails Engine几乎就是一个Rails应用程序,它拥有MVC结构,具有自己的路由,独立的Middleware Stack。社区里最广为人知的一个Rails Engine应该是devise

Rails Engine中实现Rails广为人知的“Convention over Configuration”特性,整套目录结构的加载就是在这里定义的。

Rails Engine是一个Rack Middleware,它实现了call方法,所以能Mount到其他Rails Engine或者Rails Application的路由上。

关于Rails的代码加载方式会在后续的章节详细讲解。

Rails Application

组织起Rails应用程序的启动流程,是Rails Application这个类最主要的事情。而Rails Application区别于Rails Engine在于需要管理很多外部的资源,比如以下的内容

  • Rails.logger
  • Rails.cache
  • Session 的存储机制
  • 维护完整Middleware Stack
  • 代码重新加载
  • 与Bundler的集成

关于Rails的启动流程和Middleware Stack等话题会在后续的章节中展开并详细讲解。

本节暂时到此。

Comments