Class: Rack::Unreloader
- Inherits:
-
Object
- Object
- Rack::Unreloader
- Defined in:
- lib/rack/unreloader.rb,
lib/rack/unreloader/reloader.rb,
lib/rack/unreloader/autoload_reloader.rb
Overview
Reloading application that unloads constants before reloading the relevant files, calling the new rack app if it gets reloaded.
Defined Under Namespace
Classes: AutoloadReloader, Reloader
Constant Summary collapse
- MUTEX =
Mutex used to synchronize reloads
Monitor.new
- File =
Reference to ::File as File may return Rack::File by default.
::File
- VALID_CONSTANT_NAME_REGEXP =
Regexp for valid constant names, to prevent code execution.
/\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.freeze
Instance Attribute Summary collapse
-
#reloader ⇒ Object
readonly
The Rack::Unreloader::Reloader instead related to this instance, if one.
Class Method Summary collapse
-
.autoload_constants(objs, file, logger) ⇒ Object
Autoload the file for the given objects.
-
.expand_directory_paths(paths) ⇒ Object
Given the list of paths, find all matching files, or matching ruby files in subdirecories if given a directory, and return an array of expanded paths.
-
.expand_paths(paths) ⇒ Object
Given the path glob or array of path globs, find all matching files or directories, and return an array of expanded paths.
-
.ruby_files(dir) ⇒ Object
The .rb files in the given directory or any subdirectory.
-
.split_autoload(mod_string) ⇒ Object
Split the given string into an array.
Instance Method Summary collapse
-
#autoload(paths, opts = {}, &block) ⇒ Object
Add a file glob or array of file global to autoload and monitor for changes.
-
#autoload? ⇒ Boolean
Whether the unreloader is setup for autoloading.
-
#call(env) ⇒ Object
If the cooldown time has been passed, reload any application files that have changed.
-
#initialize(opts = {}, &block) ⇒ Unreloader
constructor
Setup the reloader.
-
#record_dependency(dependency, *files) ⇒ Object
Records that each path in
files
depends ondependency
. -
#record_split_class(main_file, *files) ⇒ Object
Record that a class is split into multiple files.
-
#reload! ⇒ Object
Reload the application, checking for changed files and reloading them.
-
#reload? ⇒ Boolean
Whether the unreloader is setup for reloading.
-
#require(paths, opts = {}, &block) ⇒ Object
Add a file glob or array of file globs to monitor for changes.
Constructor Details
#initialize(opts = {}, &block) ⇒ Unreloader
Setup the reloader. Options:
- :autoload
-
Whether to allow autoloading. If not set to true, calls to autoload will eagerly require the related files instead of autoloading.
- :cooldown
-
The number of seconds to wait between checks for changed files. Defaults to 1. Set to nil/false to not check for changed files.
- :handle_reload_errors
-
Whether reload to handle reload errors by returning a 500 plain text response with the backtrace.
- :reload
-
Set to false to not setup a reloader, and just have require work directly. Should be set to false in production mode.
- :logger
-
A Logger instance which will log information related to reloading.
- :subclasses
-
A string or array of strings of class names that should be unloaded. Any classes that are not subclasses of these classes will not be unloaded. This also handles modules, but module names given must match exactly, since modules don’t have superclasses.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/rack/unreloader.rb', line 103 def initialize(opts={}, &block) @app_block = block @autoload = opts[:autoload] @logger = opts[:logger] if opts.fetch(:reload, true) @cooldown = opts.fetch(:cooldown, 1) @handle_reload_errors = opts[:handle_reload_errors] @last = Time.at(0) if @autoload require_relative('unreloader/autoload_reloader') @reloader = AutoloadReloader.new(opts) else require_relative('unreloader/reloader') @reloader = Reloader.new(opts) end reload! else @reloader = @cooldown = @handle_reload_errors = false end end |
Instance Attribute Details
#reloader ⇒ Object (readonly)
The Rack::Unreloader::Reloader instead related to this instance, if one.
86 87 88 |
# File 'lib/rack/unreloader.rb', line 86 def reloader @reloader end |
Class Method Details
.autoload_constants(objs, file, logger) ⇒ Object
Autoload the file for the given objects. objs should be a string, symbol, or array of them holding a Ruby constant name. Access to the constant will load the related file. A non-nil logger will have output logged to it.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/rack/unreloader.rb', line 50 def self.autoload_constants(objs, file, logger) strings = Array(objs).map(&:to_s) if strings.empty? # Remove file from $LOADED_FEATURES if there are no constants to autoload. # In general that is because the file is part of another class that will # handle loading the file separately, and if that class is reloaded, we # want to remove the loaded feature so the file can get loaded again. $LOADED_FEATURES.delete(file) else logger.info("Setting up autoload for #{file}: #{strings.join(' ')}") if logger strings.each do |s| obj, mod = split_autoload(s) if obj obj.autoload(mod, file) elsif logger logger.info("Invalid constant name: #{s}") end end end end |
.expand_directory_paths(paths) ⇒ Object
Given the list of paths, find all matching files, or matching ruby files in subdirecories if given a directory, and return an array of expanded paths.
20 21 22 23 24 25 |
# File 'lib/rack/unreloader.rb', line 20 def self.(paths) paths = (paths) paths.map!{|f| File.directory?(f) ? ruby_files(f) : f} paths.flatten! paths end |
.expand_paths(paths) ⇒ Object
Given the path glob or array of path globs, find all matching files or directories, and return an array of expanded paths.
29 30 31 32 33 34 35 36 |
# File 'lib/rack/unreloader.rb', line 29 def self.(paths) paths = Array(paths).flatten paths.map!{|path| Dir.glob(path).sort_by!{|filename| filename.count('/')}} paths.flatten! paths.map!{|path| File.(path)} paths.uniq! paths end |
.ruby_files(dir) ⇒ Object
The .rb files in the given directory or any subdirectory.
39 40 41 42 43 44 45 |
# File 'lib/rack/unreloader.rb', line 39 def self.ruby_files(dir) files = [] Find.find(dir) do |f| files << f if f =~ /\.rb\z/ end files.sort end |
.split_autoload(mod_string) ⇒ Object
Split the given string into an array. The first is a module/class to add the autoload to, and the second is the name of the constant to be autoloaded.
74 75 76 77 78 79 80 81 82 83 |
# File 'lib/rack/unreloader.rb', line 74 def self.split_autoload(mod_string) if m = VALID_CONSTANT_NAME_REGEXP.match(mod_string) ns, sep, mod = m[1].rpartition('::') if sep.empty? [Object, mod] else [Object.module_eval("::#{ns}", __FILE__, __LINE__), mod] end end end |
Instance Method Details
#autoload(paths, opts = {}, &block) ⇒ Object
Add a file glob or array of file global to autoload and monitor for changes. A block is required. It will be called with the path to be autoloaded, and should return the symbol for the constant name to autoload. Accepts the same options as #require.
168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/rack/unreloader.rb', line 168 def autoload(paths, opts={}, &block) raise ArgumentError, "block required" unless block if @autoload if @reloader @reloader.autoload_dependencies(paths, opts, &block) else Unreloader.(paths).each{|f| Unreloader.autoload_constants(yield(f), f, @logger)} end else require(paths, opts, &block) end end |
#autoload? ⇒ Boolean
Whether the unreloader is setup for autoloading. If false, autoloads are treated as requires.
148 149 150 |
# File 'lib/rack/unreloader.rb', line 148 def autoload? !!@autoload end |
#call(env) ⇒ Object
If the cooldown time has been passed, reload any application files that have changed. Call the app with the environment.
126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/rack/unreloader.rb', line 126 def call(env) if @cooldown && Time.now > @last + @cooldown begin MUTEX.synchronize{reload!} rescue StandardError, ScriptError => e raise unless @handle_reload_errors content = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}" return [500, {'Content-Type' => 'text/plain', 'Content-Length' => content.bytesize.to_s}, [content]] end @last = Time.now end @app_block.call.call(env) end |
#record_dependency(dependency, *files) ⇒ Object
Records that each path in files
depends on dependency
. If there is a modification to dependency
, all related files will be reloaded after dependency
is reloaded. Both dependency
and each entry in files
can be an array of path globs.
186 187 188 189 190 191 192 193 |
# File 'lib/rack/unreloader.rb', line 186 def record_dependency(dependency, *files) if @reloader files = Unreloader.(files) Unreloader.(dependency).each do |path| @reloader.record_dependency(path, files) end end end |
#record_split_class(main_file, *files) ⇒ Object
Record that a class is split into multiple files. main_file
should be the main file for the class, which should require all of the other files. files
should be a list of all other files that make up the class.
198 199 200 201 202 203 204 205 206 |
# File 'lib/rack/unreloader.rb', line 198 def record_split_class(main_file, *files) if @reloader files = Unreloader.(files) files.each do |file| record_dependency(file, main_file) end @reloader.skip_reload(files) end end |
#reload! ⇒ Object
Reload the application, checking for changed files and reloading them.
209 210 211 |
# File 'lib/rack/unreloader.rb', line 209 def reload! @reloader.reload! if @reloader end |
#reload? ⇒ Boolean
Whether the unreloader is setup for reloading. If false, no reloading is done after the initial require.
142 143 144 |
# File 'lib/rack/unreloader.rb', line 142 def reload? !!@reloader end |
#require(paths, opts = {}, &block) ⇒ Object
Add a file glob or array of file globs to monitor for changes. Options:
- :delete_hook
-
When a file being monitored is deleted, call this hook with the path of the deleted file.
156 157 158 159 160 161 162 |
# File 'lib/rack/unreloader.rb', line 156 def require(paths, opts={}, &block) if @reloader @reloader.require_dependencies(paths, opts, &block) else Unreloader.(paths).each{|f| super(f)} end end |