diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 419911dce6af4..1839372ebe9cb 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,20 @@ +* Added `Railtie#server` hook called when Rails starts a server. + This is useful in case your application or a library needs to run + another process next to the Rails server. This is quite common in development + for instance to run the Webpack or the React server. + + It can be used like this: + + ```ruby + class MyRailtie < Rails::Railtie + server do + WebpackServer.run + end + end + ``` + + *Edouard Chin* + * Remove deprecated `rake dev:cache` tasks. *Rafael Mendonça França* @@ -94,7 +111,6 @@ *Eileen M. Uchitelle*, *John Crepezzi* - * Accept params from url to prepopulate the Inbound Emails form in Rails conductor. *Chris Oliver* diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 8738e4b93e130..48b812403b8f9 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -322,6 +322,12 @@ def generators(&blk) self.class.generators(&blk) end + # Sends any server called in the instance of a new application up + # to the +server+ method defined in Rails::Railtie. + def server(&blk) + self.class.server(&blk) + end + # Sends the +isolate_namespace+ method up to the class method. def isolate_namespace(mod) self.class.isolate_namespace(mod) @@ -536,6 +542,11 @@ def run_console_blocks(app) #:nodoc: super end + def run_server_blocks(app) #:nodoc: + railties.each { |r| r.run_server_blocks(app) } + super + end + # Returns the ordered railties for this application considering railties_order. def ordered_railties #:nodoc: @ordered_railties ||= begin diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 52d1943fea713..ed96b1c8b43d1 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -470,6 +470,13 @@ def load_generators(app = self) self end + # Invoke the server registered hooks. + # Check Rails::Railtie.server for more info. + def load_server(app = self) + run_server_blocks(app) + self + end + def eager_load! # Already done by Zeitwerk::Loader.eager_load_all. We need this guard to # easily provide a compatible API for both zeitwerk and classic modes. diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 6c99d54314944..8101d518b3547 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -143,6 +143,7 @@ def config_when_updating @config_target_version = Rails.application.config.loaded_config_version || "5.0" config + configru unless cookie_serializer_config_exist gsub_file "config/initializers/cookies_serializer.rb", /json(?!,)/, "marshal" diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru.tt b/railties/lib/rails/generators/rails/app/templates/config.ru.tt index 441e6ff0c3cd5..4a3c09a6889a9 100644 --- a/railties/lib/rails/generators/rails/app/templates/config.ru.tt +++ b/railties/lib/rails/generators/rails/app/templates/config.ru.tt @@ -3,3 +3,4 @@ require_relative "config/environment" run Rails.application +Rails.application.load_server diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index baf1c1674785f..1e74b22cfb000 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -108,6 +108,23 @@ module Rails # 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 @@ -152,6 +169,10 @@ 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 @@ -246,6 +267,10 @@ def run_tasks_blocks(app) #:nodoc: 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) diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb index 1aa7003906c68..9813cc62c4c75 100644 --- a/railties/test/application/server_test.rb +++ b/railties/test/application/server_test.rb @@ -42,6 +42,32 @@ def teardown end end + test "run +server+ blocks after the server starts" do + skip "PTY unavailable" unless available_pty? + + File.open("#{app_path}/config/boot.rb", "w") do |f| + f.puts "ENV['BUNDLE_GEMFILE'] = '#{Bundler.default_gemfile}'" + f.puts 'require "bundler/setup"' + end + + add_to_config(<<~CODE) + server do + puts 'Hello world' + end + CODE + + primary, replica = PTY.open + pid = nil + + Bundler.with_original_env do + pid = Process.spawn("bin/rails server -b localhost", chdir: app_path, in: replica, out: primary, err: replica) + assert_output("Hello world", primary) + assert_output("Listening", primary) + ensure + kill(pid) if pid + end + end + private def kill(pid) Process.kill("TERM", pid) diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index b9725ca0ad79c..845f500ce35cb 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -170,6 +170,22 @@ class MyTie < Rails::Railtie assert $ran_block end + test "server block is executed when MyApp.load_server is called" do + $ran_block = false + + class MyTie < Rails::Railtie + server do + $ran_block = true + end + end + + require "#{app_path}/config/environment" + + assert_not $ran_block + Rails.application.load_server + assert $ran_block + end + test "runner block is executed when MyApp.load_runner is called" do $ran_block = false