Stan Blog

Live well, love lots, and laugh often.

[Rails] Railtie

初探 Railtie

Railtie 為 Rails 與其他 component 提供了整合的接口, Rails::Application 繼承 Engine, Engine 又繼承 Railtie

Rails 透過 Railtie 讀取 configurations, 建立 application 以及連接各個不同的 component (如: Active Record, Action View, Action Controller, Action Mailer), 使他們可以建立自己的 Initializers 與 Configuration

來翻 Source code 吧!

Railtie 是抽象的, 無法 instantiate (實例化)

從 source code 可以看到, Rails::Railtie, Rails::Engine, Rails::Application 這三種去 call new 方法的話, 皆會回傳錯誤

ABSTRACT_RAILTIES = %w(Rails::Railtie Rails::Engine Rails::Application)

def initialize
  if self.class.abstract_railtie?
    raise "#{self.class.name} is abstract, you cannot instantiate it directly."
  end
end

def abstract_railtie?
  ABSTRACT_RAILTIES.include?(name)
end

螢幕快照 2019-04-10 下午3 58 34

class << self
  private :new
  delegate :config, to: :instance

且 Railtie 中 new 方法是 private 的, 所以使用的話不會是 Rails::Railtie.new

而是這樣使用

class MyNewRailtie < Rails::Railtie
  initializer "new_initialization_behavior" do
    puts "Hello!"
  end
end

建立 subClass, 將 Rails::Railtie 當成 baseClass 繼承. 在 subClass 內使用需要的 Rails::Railtie 方法

Railtie 裡的 initializer 跟 config/initializers 裡的 initialize 不一樣 Railtie 的 initializer 是一個特殊的 hook, 需要給他一個名字, 然後將要處理的邏輯寫在 block 裡

class MyRailtie < Rails::Railtie
  initializer "my_railtie.configure_rails_initialization" do
    # some initialization behavior
  end
end

會用到 Railtie 的情境

  1. 建立 initializers

    activerecord/lib/active_record/railtie.rb 裡面有一段是要設時區的 initializer

    initializer "active_record.initialize_timezone" do
      ActiveSupport.on_load(:active_record) do
        self.default_timezone = :utc
      end
    end
    def initializers
      @initializers ||= Collection.new
    end

    def initializer(name, opts = {}, &blk)
        raise ArgumentError, "A block must be passed when defining an initializer" unless blk
        opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] }
        initializers << Initializer.new(name, nil, opts, &blk)
    end
會在方法內 new 一個 instance, 塞進 @initializers 內
  1. 設定 generator

    如何建立一個 generator 可以參考 Rails - generators

  2. 新增 config.* keys 到 environment

  3. 使用 ActiveSupport::Notifications 設定 subscriber

  4. 新增 Rake tasks

    像我們常用的 rake db: 系列指令, 是在 activerecord/lib/active_record/railtie.rb 36~50 line 進行掛載的 load "active_record/railties/databases.rake" 這行會 load activerecord/lib/active_record/railties/databases.rake 檔案內的所有 task

Conclusion

平常在開發的時候比較少會去使用到 Railtie 如果想要在 Rails 啟動期間或啟動後進行 interact, 這時候就需要 Railtie

如果沒有要 hook 在 Rails lifecycle 中, 就不要使用 Railtie 建議使用 standard Ruby library, require 需要的部分再 override

Ref:

Comments

comments powered by Disqus