mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
510d761f45
Until now they were implicitly ordered by load order because of `DescendantTracker#descendants` register them through the `inherited` callback. But now that on Ruby 3.1 we use the native `Class#descendants`, the ordering is rather random, or at least can't be relied on. So we add a counter and assign an index on every Railtie subclass to be able to sort them explictly later.
297 lines
9.4 KiB
Ruby
297 lines
9.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "rails/initializable"
|
|
require "active_support/descendants_tracker"
|
|
require "active_support/inflector"
|
|
require "active_support/core_ext/module/introspection"
|
|
require "active_support/core_ext/module/delegation"
|
|
|
|
module Rails
|
|
# <tt>Rails::Railtie</tt> is the core of the Rails framework and provides
|
|
# several hooks to extend Rails and/or modify the initialization process.
|
|
#
|
|
# Every major component of Rails (Action Mailer, Action Controller, Active
|
|
# Record, etc.) implements a railtie. Each of them is responsible for their
|
|
# own initialization. This makes Rails itself absent of any component hooks,
|
|
# allowing other components to be used in place of any of the Rails defaults.
|
|
#
|
|
# Developing a Rails extension does _not_ require implementing a railtie, but
|
|
# if you need to interact with the Rails framework during or after boot, then
|
|
# a railtie is needed.
|
|
#
|
|
# For example, an extension doing any of the following would need a railtie:
|
|
#
|
|
# * creating initializers
|
|
# * configuring a Rails framework for the application, like setting a generator
|
|
# * adding <tt>config.*</tt> keys to the environment
|
|
# * setting up a subscriber with <tt>ActiveSupport::Notifications</tt>
|
|
# * adding Rake tasks
|
|
#
|
|
# == Creating a Railtie
|
|
#
|
|
# To extend Rails using a railtie, create a subclass of <tt>Rails::Railtie</tt>.
|
|
# This class must be loaded during the Rails boot process, and is conventionally
|
|
# called <tt>MyNamespace::Railtie</tt>.
|
|
#
|
|
# The following example demonstrates an extension which can be used with or
|
|
# without Rails.
|
|
#
|
|
# # lib/my_gem/railtie.rb
|
|
# module MyGem
|
|
# class Railtie < Rails::Railtie
|
|
# end
|
|
# end
|
|
#
|
|
# # lib/my_gem.rb
|
|
# require "my_gem/railtie" if defined?(Rails::Railtie)
|
|
#
|
|
# == Initializers
|
|
#
|
|
# To add an initialization step to the Rails boot process from your railtie, just
|
|
# define the initialization code with the +initializer+ macro:
|
|
#
|
|
# class MyRailtie < Rails::Railtie
|
|
# initializer "my_railtie.configure_rails_initialization" do
|
|
# # some initialization behavior
|
|
# end
|
|
# end
|
|
#
|
|
# If specified, the block can also receive the application object, in case you
|
|
# need to access some application-specific configuration, like middleware:
|
|
#
|
|
# class MyRailtie < Rails::Railtie
|
|
# initializer "my_railtie.configure_rails_initialization" do |app|
|
|
# app.middleware.use MyRailtie::Middleware
|
|
# end
|
|
# end
|
|
#
|
|
# Finally, you can also pass <tt>:before</tt> and <tt>:after</tt> as options to
|
|
# +initializer+, in case you want to couple it with a specific step in the
|
|
# initialization process.
|
|
#
|
|
# == Configuration
|
|
#
|
|
# Railties can access a config object which contains configuration shared by all
|
|
# railties and the application:
|
|
#
|
|
# class MyRailtie < Rails::Railtie
|
|
# # Customize the ORM
|
|
# config.app_generators.orm :my_railtie_orm
|
|
#
|
|
# # Add a to_prepare block which is executed once in production
|
|
# # and before each request in development.
|
|
# config.to_prepare do
|
|
# MyRailtie.setup!
|
|
# end
|
|
# end
|
|
#
|
|
# == Loading Rake Tasks and Generators
|
|
#
|
|
# If your railtie has Rake tasks, you can tell Rails to load them through the method
|
|
# +rake_tasks+:
|
|
#
|
|
# class MyRailtie < Rails::Railtie
|
|
# rake_tasks do
|
|
# load "path/to/my_railtie.tasks"
|
|
# end
|
|
# end
|
|
#
|
|
# By default, Rails loads generators from your load path. However, if you want to place
|
|
# your generators at a different location, you can specify in your railtie a block which
|
|
# will load them during normal generators lookup:
|
|
#
|
|
# class MyRailtie < Rails::Railtie
|
|
# generators do
|
|
# require "path/to/my_railtie_generator"
|
|
# end
|
|
# end
|
|
#
|
|
# Since filenames on the load path are shared across gems, be sure that files you load
|
|
# through a railtie have unique names.
|
|
#
|
|
# == Run another program when the Rails server starts
|
|
#
|
|
# In development, it's very usual to have to run another process next to the Rails Server. In example
|
|
# you might want to start the Webpack or React server. Or maybe you need to run your job scheduler process
|
|
# like Sidekiq. This is usually done by opening a new shell and running the program from here.
|
|
#
|
|
# Rails allow you to specify a +server+ block which will get called when a Rails server starts.
|
|
# This way, your users don't need to remember to have to open a new shell and run another program, making
|
|
# this less confusing for everyone.
|
|
# It can be used like this:
|
|
#
|
|
# class MyRailtie < Rails::Railtie
|
|
# server do
|
|
# WebpackServer.start
|
|
# end
|
|
# end
|
|
#
|
|
# == Application and Engine
|
|
#
|
|
# An engine is nothing more than a railtie with some initializers already set. And since
|
|
# <tt>Rails::Application</tt> is an engine, the same configuration described here can be
|
|
# used in both.
|
|
#
|
|
# Be sure to look at the documentation of those specific classes for more information.
|
|
class Railtie
|
|
autoload :Configuration, "rails/railtie/configuration"
|
|
|
|
extend ActiveSupport::DescendantsTracker
|
|
include Initializable
|
|
|
|
ABSTRACT_RAILTIES = %w(Rails::Railtie Rails::Engine Rails::Application)
|
|
|
|
class << self
|
|
private :new
|
|
delegate :config, to: :instance
|
|
|
|
def subclasses
|
|
super.reject(&:abstract_railtie?).sort
|
|
end
|
|
|
|
def rake_tasks(&blk)
|
|
register_block_for(:rake_tasks, &blk)
|
|
end
|
|
|
|
def console(&blk)
|
|
register_block_for(:load_console, &blk)
|
|
end
|
|
|
|
def runner(&blk)
|
|
register_block_for(:runner, &blk)
|
|
end
|
|
|
|
def generators(&blk)
|
|
register_block_for(:generators, &blk)
|
|
end
|
|
|
|
def server(&blk)
|
|
register_block_for(:server, &blk)
|
|
end
|
|
|
|
def abstract_railtie?
|
|
ABSTRACT_RAILTIES.include?(name)
|
|
end
|
|
|
|
def railtie_name(name = nil)
|
|
@railtie_name = name.to_s if name
|
|
@railtie_name ||= generate_railtie_name(self.name)
|
|
end
|
|
|
|
# Since Rails::Railtie cannot be instantiated, any methods that call
|
|
# +instance+ are intended to be called only on subclasses of a Railtie.
|
|
def instance
|
|
@instance ||= new
|
|
end
|
|
|
|
# Allows you to configure the railtie. This is the same method seen in
|
|
# Railtie::Configurable, but this module is no longer required for all
|
|
# subclasses of Railtie so we provide the class method here.
|
|
def configure(&block)
|
|
instance.configure(&block)
|
|
end
|
|
|
|
def <=>(other) # :nodoc:
|
|
load_index <=> other.load_index
|
|
end
|
|
|
|
def inherited(subclass)
|
|
subclass.increment_load_index
|
|
super
|
|
end
|
|
|
|
protected
|
|
attr_reader :load_index
|
|
|
|
def increment_load_index
|
|
@@load_counter ||= 0
|
|
@load_index = (@@load_counter += 1)
|
|
end
|
|
|
|
private
|
|
def generate_railtie_name(string)
|
|
ActiveSupport::Inflector.underscore(string).tr("/", "_")
|
|
end
|
|
|
|
def respond_to_missing?(name, _)
|
|
instance.respond_to?(name) || super
|
|
end
|
|
|
|
# If the class method does not have a method, then send the method call
|
|
# to the Railtie instance.
|
|
def method_missing(name, *args, &block)
|
|
if instance.respond_to?(name)
|
|
instance.public_send(name, *args, &block)
|
|
else
|
|
super
|
|
end
|
|
end
|
|
ruby2_keywords(:method_missing)
|
|
|
|
# receives an instance variable identifier, set the variable value if is
|
|
# blank and append given block to value, which will be used later in
|
|
# `#each_registered_block(type, &block)`
|
|
def register_block_for(type, &blk)
|
|
var_name = "@#{type}"
|
|
blocks = instance_variable_defined?(var_name) ? instance_variable_get(var_name) : instance_variable_set(var_name, [])
|
|
blocks << blk if blk
|
|
blocks
|
|
end
|
|
end
|
|
|
|
delegate :railtie_name, to: :class
|
|
|
|
def initialize # :nodoc:
|
|
if self.class.abstract_railtie?
|
|
raise "#{self.class.name} is abstract, you cannot instantiate it directly."
|
|
end
|
|
end
|
|
|
|
def configure(&block) # :nodoc:
|
|
instance_eval(&block)
|
|
end
|
|
|
|
# This is used to create the <tt>config</tt> object on Railties, an instance of
|
|
# Railtie::Configuration, that is used by Railties and Application to store
|
|
# related configuration.
|
|
def config
|
|
@config ||= Railtie::Configuration.new
|
|
end
|
|
|
|
def railtie_namespace # :nodoc:
|
|
@railtie_namespace ||= self.class.module_parents.detect { |n| n.respond_to?(:railtie_namespace) }
|
|
end
|
|
|
|
protected
|
|
def run_console_blocks(app) # :nodoc:
|
|
each_registered_block(:console) { |block| block.call(app) }
|
|
end
|
|
|
|
def run_generators_blocks(app) # :nodoc:
|
|
each_registered_block(:generators) { |block| block.call(app) }
|
|
end
|
|
|
|
def run_runner_blocks(app) # :nodoc:
|
|
each_registered_block(:runner) { |block| block.call(app) }
|
|
end
|
|
|
|
def run_tasks_blocks(app) # :nodoc:
|
|
extend Rake::DSL
|
|
each_registered_block(:rake_tasks) { |block| instance_exec(app, &block) }
|
|
end
|
|
|
|
def run_server_blocks(app) # :nodoc:
|
|
each_registered_block(:server) { |block| block.call(app) }
|
|
end
|
|
|
|
private
|
|
# run `&block` in every registered block in `#register_block_for`
|
|
def each_registered_block(type, &block)
|
|
klass = self.class
|
|
while klass.respond_to?(type)
|
|
klass.public_send(type).each(&block)
|
|
klass = klass.superclass
|
|
end
|
|
end
|
|
end
|
|
end
|