High performance source code reloader middleware
The list of object constants and classes loaded as part of the project.
The list of files loaded as part of a project.
The modification times for every file in a project.
Returns true if any file changes are detected and populates the MTIMES cache
# File lib/padrino-core/reloader.rb, line 91 def changed? changed = false rotation do |file, mtime| new_file = MTIMES[file].nil? previous_mtime = MTIMES[file] ||= mtime changed = true if new_file || mtime > previous_mtime end changed end
Remove files and classes loaded with stat
# File lib/padrino-core/reloader.rb, line 76 def clear! MTIMES.clear LOADED_CLASSES.each do |file, klasses| klasses.each { |klass| remove_constant(klass) } LOADED_CLASSES.delete(file) end LOADED_FILES.each do |file, dependencies| dependencies.each { |dependency| $LOADED_FEATURES.delete(dependency) } $LOADED_FEATURES.delete(file) end end
Specified folders can be excluded from the code reload detection process. Default excluded directories at Padrino.root are: test, spec, features, tmp, config, db and public
# File lib/padrino-core/reloader.rb, line 28 def exclude @_exclude ||= %(test spec tmp features config public db).map { |path| Padrino.root(path) } end
Specified constants can be excluded from the code unloading process.
# File lib/padrino-core/reloader.rb, line 35 def exclude_constants @_exclude_constants ||= [] end
Returns true if the file is defined in our padrino root
# File lib/padrino-core/reloader.rb, line 173 def figure_path(file) return file if Pathname.new(file).absolute? $:.each do |path| found = File.join(path, file) return File.expand_path(found) if File.exist?(found) end file end
Specified constants can be configured to be reloaded on every request. Default included constants are: [none]
# File lib/padrino-core/reloader.rb, line 43 def include_constants @_include_constants ||= [] end
We lock dependencies sets to prevent reloading of protected constants
# File lib/padrino-core/reloader.rb, line 105 def lock! klasses = ObjectSpace.classes.map { |klass| klass.name.to_s.split("::")[0] }.uniq klasses = klasses | Padrino.mounted_apps.map { |app| app.app_class } Padrino::Reloader.exclude_constants.concat(klasses) end
Reload all files with changes detected.
# File lib/padrino-core/reloader.rb, line 50 def reload! # Detect changed files rotation do |file, mtime| # Retrive the last modified time new_file = MTIMES[file].nil? previous_mtime = MTIMES[file] ||= mtime logger.devel "Detected a new file #{file}" if new_file # We skip to next file if it is not new and not modified next unless new_file || mtime > previous_mtime # Now we can reload our file apps = mounted_apps_of(file) if apps.present? apps.each { |app| app.app_obj.reload! } else safe_load(file, :force => new_file) # Reload also apps Padrino.mounted_apps.each do |app| app.app_obj.reload! if app.app_obj.dependencies.include?(file) end end end end
Removes the specified class and constant.
# File lib/padrino-core/reloader.rb, line 185 def remove_constant(const) return if exclude_constants.compact.uniq.any? { |c| (const.to_s =~ %{^#{Regexp.escape(c)}}) } && !include_constants.compact.uniq.any? { |c| (const.to_s =~ %{^#{Regexp.escape(c)}}) } begin parts = const.to_s.split("::") base = parts.size == 1 ? Object : parts[0..-2].join("::").constantize object = parts[-1].to_s base.send(:remove_const, object) logger.devel "Removed constant: #{const}" rescue NameError; end end
A safe Kernel::require which issues the necessary hooks depending on results
# File lib/padrino-core/reloader.rb, line 114 def safe_load(file, options={}) began_at = Time.now force, file = options[:force], figure_path(file) # Check if file was changed or if force a reload reload = MTIMES[file] && File.mtime(file) > MTIMES[file] return if !force && !reload && MTIMES[file] # Removes all classes declared in the specified file if klasses = LOADED_CLASSES.delete(file) klasses.each { |klass| remove_constant(klass) } end # Remove all loaded fatures with our file if features = LOADED_FILES[file] features.each { |feature| $LOADED_FEATURES.delete(feature) } end # Duplicate objects and loaded features before load file klasses = ObjectSpace.classes.dup files = $LOADED_FEATURES.dup # Now we can reload dependencies of our file if features = LOADED_FILES.delete(file) features.each { |feature| safe_load(feature, :force => true) } end # And finally load the specified file begin logger.devel :loading, began_at, file if !reload logger.debug :reload, began_at, file if reload $LOADED_FEATURES.delete(file) verbosity_was, $-v = $-v, nil loaded = false require(file) loaded = true MTIMES[file] = File.mtime(file) rescue SyntaxError => e logger.error "Cannot require #{file} due to a syntax error: #{e.message}" ensure $-v = verbosity_was new_constants = (ObjectSpace.classes - klasses).uniq if loaded # Store the file details LOADED_CLASSES[file] = new_constants LOADED_FILES[file] = ($LOADED_FEATURES - files - [file]).uniq # Track only features in our Padrino.root LOADED_FILES[file].delete_if { |feature| !in_root?(feature) } else logger.devel "Failed to load #{file}; removing partially defined constants" new_constants.each { |klass| remove_constant(klass) } end end end
Generated with the Darkfish Rdoc Generator 2.