Allow a new `server` Railtie block:

- This is similar to other railties blocks (such as `console`,
  `tasks` ...). The goal of this block is to allow the application
  or a railtie to load code after the server start.

  The use case can be to fire the webpack or react server in
  development or start some job worker like sidekiq or resque.

  Right now, all these tasks needs to be done in a separate
  shell and gem maintainer needs to add documentation on
  how to run their libraries if another program needs to run
  next to the Rails server.

  This feature can be used like this:

  ```ruby
    class SuperRailtie < Rails::Railtie
      server do
        WebpackServer.run
      end
    end
  ```
This commit is contained in:
Edouard Chin 2020-07-30 14:15:18 +02:00 committed by Rafael Mendonça França
parent 4dd61b4318
commit 868866c1fd
No known key found for this signature in database
GPG Key ID: FC23B6D0F1EEE948
8 changed files with 104 additions and 1 deletions

View File

@ -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*

View File

@ -322,6 +322,12 @@ module Rails
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 @@ module Rails
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

View File

@ -470,6 +470,13 @@ module Rails
self
end
# Invoke the server registered hooks.
# Check <tt>Rails::Railtie.server</tt> 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.

View File

@ -143,6 +143,7 @@ module Rails
@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"

View File

@ -3,3 +3,4 @@
require_relative "config/environment"
run Rails.application
Rails.application.load_server

View File

@ -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 @@ module Rails
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 @@ module Rails
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)

View File

@ -42,6 +42,32 @@ module ApplicationTests
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)

View File

@ -170,6 +170,22 @@ module RailtiesTest
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