require 'blankslate' require 'pathname' module DynamicConfiguration class << self def create(const_name, root_file_path) @config_paths.push(File.dirname(root_file_path)) @const_names.push(const_name) ConfigFactory.new.create_config(const_name, root_file_path) end def config_paths @config_paths end def const_names @const_names end end @const_names, @config_paths = [], [] private class ConfigFactory def create_config(const_name, root_file_path) detect_rails setup_config(const_name, root_file_path) load_main_configuration_files load_per_environment_configuration_files if @rails_loaded load_local_configuration_files finalize_config @config end private def detect_rails @rails_loaded = Object.const_defined?(:Rails) end def setup_config(const_name, root_file_path) @const_name = const_name @root_file_path = root_file_path @config_directory = Pathname.new(File.dirname(@root_file_path)) @config = Config.new(@const_name, @config_directory) Object.const_set(@const_name, @config) end def load_main_configuration_files @config_directory.entries.each do |mod_file| next if ["..", "."].include?(mod_file.basename.to_s) mod_file = @config_directory + mod_file next unless mod_file.file? && mod_file.to_s != @root_file_path @config.load_module(mod_file) end end def load_per_environment_configuration_files @config_directory.entries.each do |directory| next if ["..", "."].include?(directory.basename.to_s) directory = @config_directory + directory next unless directory.directory? && @rails_loaded && Rails.env == directory.basename.to_s directory.entries.each do |mod_file| mod_file = directory + mod_file next unless mod_file.file? @config.load_module(mod_file) end end end def load_local_configuration_files local_settings_exist = FileTest.directory?(@config_directory.to_s + "/local") rails_test_env = @rails_loaded && Rails.env == 'test' return if !local_settings_exist || rails_test_env local_mod_files_dir = @config_directory + Pathname.new("local/") local_mod_files_dir.entries.each do |mod_file| next if ["..", "."].include?(mod_file.basename.to_s) mod_file = local_mod_files_dir + mod_file next unless mod_file.file? @config.load_module(mod_file) end end def finalize_config @config.freeze end end class Config < ::BlankSlate reveal :freeze reveal :respond_to? def initialize(const_name, config_directory) @const_name, @config_directory = const_name, config_directory end def load_module(file_pathname) mod_name = file_pathname.basename.to_s[0..-4] @modules ||= {} @modules[mod_name.intern] ||= Group.new(@config_directory) @modules[mod_name.intern].instance_eval(::IO.read(file_pathname.to_s)) @settings ||= {} @settings[mod_name.intern] ||= Settings.new(@const_name, mod_name, @modules[mod_name.intern].settings) end def respond_to_missing?(name, include_private=false) @settings && @settings[name] end def method_missing(name, *args, &block) unless @settings && @settings[name] raise MissingGroupException.new("No configuration defined for a '#{name}' submodule") end @settings[name] end end class Group < ::BlankSlate attr_accessor :settings, :config_directory def initialize(config_directory) @config_directory = config_directory @settings = {} end def method_missing(name, value) @settings[name] = value @settings[name].freeze end end class Settings < ::BlankSlate reveal :send def initialize(const_name, module_name, settings) @const_name = const_name @module_name = module_name @settings = settings end def method_missing(name, *args) @settings.fetch(name) { raise MissingSettingException.new("Setting '#{@const_name}.#{@module_name}.#{name}' is not defined") } end end class MissingGroupException < StandardError; end class MissingSettingException < StandardError; end end require 'railtie' if defined?(Rails)