<commit_before>---
layout: post
title: How Rails Auto-Reloading
---

Rails 开发环境可以延迟加载类或模块, 还支持修改文件后重新加载, 前者提高了启动速度, 而后者方便了使用者调试.

#### 重新加载的工作原理

当请求到达 ActionDispatch::Reloader 时, 它会通过 Rails Application 的 3 个 reloader 检查是否有文件被修改, 他们分别是:

  1. 用户自定义的类或者模块, 如 app/models/user.rb.

  2. 路由文件, 如 config/routes.rb.

  3. i18n 文件, 如 config/locales/en.yml.

ActiveSupport::FileUpdateChecker 用于检查文件是否被修改, 其原理是 `File.mtime` 随着文件的保存而发生变化. 当发现有文件被修改时, 会触发预设的函数, 用于重新加载数据, 例如:

```ruby
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/i18n_railtie.rb#L56

def initialize_i18n
  ...
  reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
  app.reloaders << reloader
  ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
  reloader.execute
  ...
end
```

#### 自动加载的工作原理

程序第一次调用 User 类时, 大致流程如下:

  1. 由于还没有加载过定义该类的文件所以会抛出 NameError 异常, 但是 ActiveSupport::Dependencies 重新实现了 `Module#const_missing` 方法, 避免了直接抛出异常.

  2. 根据 **Convention Over Configuration** 原则, 在 ActiveSupport::Dependencies.autoload_paths (如 app/models, app/controllers 等) 中遍历, 尝试找到对应的 user.rb 文件.

  3. 在找到对应的文件之后, 使用 `Kernel#load` 方法加载该文件, 否则报异常. 之所以使用 load 方法, 而不是常用的 require 方法, 是因为 require 只能加载一次, 而 load 可以重复加载.

当然 ActiveSupport::Dependencies 所做的远不止这些, 它处理了更多复杂的情况, 若希望知道里面的实现细节, 可以仔细看看[这个文件](https://github.com/rails/rails/blob/08754f12e65a9ec79633a605e986d0f1ffa4b251/activesupport/lib/active_support/dependencies.rb).

Rails 还提供一系列配置可以扩展自动加载的路径, 使其使用更加灵活:

```ruby
config.autoload_paths += %w(#{config.root}/lib)

config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]

config.paths['config/routes.rb'] += Dir['config/routes/**/*.rb']<commit_msg>update post<commit_after>---
layout: post
title: How Rails Auto-Reloading
---

Rails 开发环境可以延迟加载类或模块, 还支持修改文件后重新加载, 前者提高了启动速度, 而后者方便了使用者调试.

#### 重新加载的工作原理

当请求到达 ActionDispatch::Reloader 时, 它会通过 Rails Application 的 3 个 reloader 检查是否有文件被修改, 他们分别是:

  1. 用户自定义的类或者模块, 如 app/models/user.rb.

  2. 路由文件, 如 config/routes.rb.

  3. i18n 文件, 如 config/locales/en.yml.

ActiveSupport::FileUpdateChecker 用于检查文件是否被修改并执行重新加载, 大致流程:

1. 修改文件会改变其 `File.mtime`, 这是此功能的前提.

2. 找到被监控的文件中 mtime 最大的时间记为 current\_updated\_at.

3. 将该时间与用于保存之前最后一次修改时间的实例变量 @last\_update\_at 进行比较, 若前者大于后者则认为有文件被修改过.

4. 若发现有文件被修改过, 则将 current\_updated\_at 赋值给 @last\_update\_at 用于下次比较, 并且执行预设的函数, 清空旧数据, 导入新数据.

#### 自动加载的工作原理

程序第一次调用 User 类时, 大致流程如下:

  1. 由于还没有加载过定义该类的文件所以会抛出 NameError 异常, 但是 ActiveSupport::Dependencies 重新实现了 `Module#const_missing` 方法, 避免了直接抛出异常.

  2. 根据 **Convention Over Configuration** 原则, 在 ActiveSupport::Dependencies.autoload\_paths (如 app/models, app/controllers 等) 中遍历, 尝试找到对应的 user.rb 文件.

  3. 在找到对应的文件之后, 使用 `Kernel#load` 方法加载该文件, 否则报异常. 之所以使用 load 方法, 而不是常用的 require 方法, 是因为 require 只能加载一次, 而 load 可以重复加载.

当然 ActiveSupport::Dependencies 所做的远不止这些, 它处理了更多复杂的情况, 若希望知道里面的实现细节, 可以仔细看看[这个文件](https://github.com/rails/rails/blob/08754f12e65a9ec79633a605e986d0f1ffa4b251/activesupport/lib/active_support/dependencies.rb).

Rails 还提供一系列配置可以扩展自动加载的路径, 使其使用更加灵活:

```ruby
config.autoload_paths += %w(#{config.root}/lib)

config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]

config.paths['config/routes.rb'] += Dir['config/routes/**/*.rb']
{'fraction_non_alphanumeric': 0.1310070400979492, 'fraction_numerical': 0.027548209366391185, 'mean_word_length': 5.212927756653992, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '967350', 'n_tokens_mistral': 1895, 'n_tokens_neox': 1959, 'n_words': 259}