module Padrino::Reloader

High performance source code reloader middleware

Public Instance Methods

changed?() click to toggle source

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
clear!() click to toggle source

Remove files and classes loaded with stat

# File lib/padrino-core/reloader.rb, line 58
def clear!
  MTIMES.clear
  Storage.clear!
end
exclude() click to toggle source

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
exclude_constants() click to toggle source

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
include_constants() click to toggle source

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
lock!() click to toggle source

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!() click to toggle source

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
remove_constant(const) click to toggle source

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_feature(file) click to toggle source

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
safe_load(file, options={}) click to toggle source

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
special_files() click to toggle source

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
special_files=(files) click to toggle source

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
unknown() click to toggle source

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

constant_excluded?(const) click to toggle source

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
constant_name(constant) click to toggle source
# File lib/padrino-core/reloader.rb, line 314
def constant_name(constant)
  constant._orig_klass_name
rescue NoMethodError
  constant.name
end
external_constant?(const) click to toggle source

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
feature_excluded?(file) click to toggle source

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
figure_path(file) click to toggle source

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
file_changed?(file) click to toggle source

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
file_new?(file) click to toggle source

Returns true if the file is new.

# File lib/padrino-core/reloader.rb, line 203
def file_new?(file)
  MTIMES[file].nil?
end
files_for_rotation() click to toggle source

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
mounted_apps_of(file) click to toggle source

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
object_sources(target) click to toggle source

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
reload_regular(file) click to toggle source

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
reload_special(file) click to toggle source

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
reloadable_apps() click to toggle source

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
rotation() { |file| ... } click to toggle source

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
update_modification_time(file) click to toggle source

Macro for mtime update.

# File lib/padrino-core/reloader.rb, line 189
def update_modification_time(file)
  MTIMES[file] = File.mtime(file)
end
with_silence() { || ... } click to toggle source

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