module Padrino::Reloader
High performance source code reloader middleware
Public Instance Methods
Returns true if any file changes are detected.
# File lib/padrino-core/reloader.rb, line 66 def changed? rotation do |file| break true if file_changed?(file) end end
Remove files and classes loaded with stat
# File lib/padrino-core/reloader.rb, line 58 def clear! MTIMES.clear Storage.clear! 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 26 def exclude @_exclude ||= Set.new %w(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 33 def exclude_constants @_exclude_constants ||= Set.new end
Specified constants can be configured to be reloaded on every request. Default included constants are: [none]
# File lib/padrino-core/reloader.rb, line 41 def include_constants @_include_constants ||= Set.new end
We lock dependencies sets to prevent reloading of protected constants
# File lib/padrino-core/reloader.rb, line 75 def lock! klasses = Storage.send(:object_classes) do |klass| original_klass_name = constant_name(klass) original_klass_name.split('::').first if original_klass_name end klasses |= Padrino.mounted_apps.map(&:app_class) exclude_constants.merge(klasses) end
Reload apps and files with changes detected.
# File lib/padrino-core/reloader.rb, line 48 def reload! rotation do |file| next unless file_changed?(file) reload_special(file) || reload_regular(file) end end
Removes the specified class and constant.
# File lib/padrino-core/reloader.rb, line 112 def remove_constant(const) return if constant_excluded?(const) base, _, object = const.to_s.rpartition('::') base = base.empty? ? Object : base.constantize base.send :remove_const, object logger.devel "Removed constant #{const} from #{base}" rescue NameError end
Remove a feature from $LOADED_FEATURES so it can be required again.
# File lib/padrino-core/reloader.rb, line 124 def remove_feature(file) $LOADED_FEATURES.delete(file) unless feature_excluded?(file) end
A safe Kernel::require which issues the necessary hooks depending on results
# File lib/padrino-core/reloader.rb, line 87 def safe_load(file, options={}) began_at = Time.now file = figure_path(file) return unless options[:force] || file_changed?(file) return require(file) if feature_excluded?(file) Storage.prepare(file) # might call #safe_load recursively logger.devel(file_new?(file) ? :loading : :reload, began_at, file) begin with_silence{ require(file) } Storage.commit(file) update_modification_time(file) rescue Exception => exception unless options[:cyclic] logger.exception exception, :short logger.error "Failed to load #{file}; removing partially defined constants" end Storage.rollback(file) raise end end
Returns the list of special tracked files for Reloader
.
# File lib/padrino-core/reloader.rb, line 131 def special_files @special_files ||= Set.new end
Sets the list of special tracked files for Reloader
.
# File lib/padrino-core/reloader.rb, line 138 def special_files=(files) @special_files = Set.new(files) end
This reloader is suited for use in a many environments because each file will only be checked once and only one system call to stat(2) is made.
Please note that this will not reload files in the background, and does so only when explicitly invoked.
# File lib/padrino-core/reloader.rb, line 17 extend self
Private Instance Methods
Tells if a constant should be excluded from Reloader
routines.
# File lib/padrino-core/reloader.rb, line 246 def constant_excluded?(const) external_constant?(const) || (exclude_constants - include_constants).any?{ |excluded_constant| constant_name(const).start_with?(excluded_constant) } end
# File lib/padrino-core/reloader.rb, line 314 def constant_name(constant) constant._orig_klass_name rescue NoMethodError constant.name end
Tells if a constant is defined only outside of Padrino
project path. If a constant has any methods defined inside of the project path it's considered internal and will be included in further testing.
# File lib/padrino-core/reloader.rb, line 255 def external_constant?(const) sources = object_sources(const) # consider methodless constants not external return false if sources.empty? !sources.any?{ |source| source.start_with?(Padrino.root) } end
Tells if a feature should be excluded from Reloader
tracking.
# File lib/padrino-core/reloader.rb, line 239 def feature_excluded?(file) !file.start_with?(Padrino.root) || exclude.any?{ |excluded_path| file.start_with?(excluded_path) } end
Returns absolute path of the file.
# File lib/padrino-core/reloader.rb, line 147 def figure_path(file) return file if Pathname.new(file).absolute? $LOAD_PATH.each do |path| found = File.join(path, file) return File.expand_path(found) if File.file?(found) end file end
Returns true if the file is new or it's modification time changed.
# File lib/padrino-core/reloader.rb, line 196 def file_changed?(file) file_new?(file) || File.mtime(file) > MTIMES[file] end
Returns true if the file is new.
# File lib/padrino-core/reloader.rb, line 203 def file_new?(file) MTIMES[file].nil? end
Creates an array of paths for use in rotation
.
# File lib/padrino-core/reloader.rb, line 223 def files_for_rotation files = Set.new Padrino.dependency_paths.each do |path| files += Dir.glob(path) end reloadable_apps.each do |app| files << app.app_file files += Dir.glob(app.app_obj.prerequisites) files += app.app_obj.dependencies end files + special_files end
Return the mounted_apps providing the app location. Can be an array because in one app.rb we can define multiple Padrino::Application
.
# File lib/padrino-core/reloader.rb, line 290 def mounted_apps_of(file) Padrino.mounted_apps.select { |app| File.identical?(file, app.app_file) } end
Gets all the sources in which target class is defined.
Note: Method#source_location is for Ruby 1.9.3+ only.
# File lib/padrino-core/reloader.rb, line 267 def object_sources(target) sources = Set.new target.methods.each do |method_name| next unless method_name.kind_of?(Symbol) method_object = target.method(method_name) if method_object.owner == target.singleton_class sources << method_object.source_location.first end end target.instance_methods.each do |method_name| next unless method_name.kind_of?(Symbol) method_object = target.instance_method(method_name) if method_object.owner == target sources << method_object.source_location.first end end sources end
Reloads ruby file and applications dependent on it.
# File lib/padrino-core/reloader.rb, line 173 def reload_regular(file) apps = mounted_apps_of(file) if apps.empty? reloadable_apps.each do |app| app.app_obj.reload! if app.app_obj.dependencies.include?(file) end safe_load(file) else apps.each { |app| app.app_obj.reload! } update_modification_time(file) end end
Reloads the file if it's special. For now it's only I18n locale files.
# File lib/padrino-core/reloader.rb, line 159 def reload_special(file) return unless special_files.any?{ |special_file| File.identical?(special_file, file) } if defined?(I18n) began_at = Time.now I18n.reload! update_modification_time(file) logger.devel :reload, began_at, file end true end
Return the apps that allow reloading.
# File lib/padrino-core/reloader.rb, line 297 def reloadable_apps Padrino.mounted_apps.select do |app| next unless app.app_file.start_with?(Padrino.root) app.app_obj.respond_to?(:reload) && app.app_obj.reload? end end
Searches Ruby files in your Padrino.load_paths
, Padrino::Application.load_paths and monitors them for any changes.
# File lib/padrino-core/reloader.rb, line 211 def rotation files_for_rotation.each do |file| file = File.expand_path(file) next if Reloader.exclude.any? { |base| file.start_with?(base) } || !File.file?(file) yield file end nil end
Macro for mtime update.
# File lib/padrino-core/reloader.rb, line 189 def update_modification_time(file) MTIMES[file] = File.mtime(file) end
Disables output, yields block, switches output back.
# File lib/padrino-core/reloader.rb, line 307 def with_silence verbosity_level, $-v = $-v, nil yield ensure $-v = verbosity_level end