diff --git a/.env.sample b/.env.sample index bdfe117..c02fd80 100644 --- a/.env.sample +++ b/.env.sample @@ -13,9 +13,9 @@ LOG_ACTIVE_RECORD=false LOG_ACTION_VIEW=false # with default value, type gets inferred from -# INTEGER=1 -# STRING=anything -# BOOLEAN=true +INTEGER=1 +STRING=456 +BOOLEAN=true # with no default value (evaluated to nil), type must be defined expressively otherwise it reverts to a string type # INTEGER=:integer diff --git a/lib/hot_constants/hot_constants.rb b/lib/hot_constants/hot_constants.rb index 80e7227..8a8a460 100644 --- a/lib/hot_constants/hot_constants.rb +++ b/lib/hot_constants/hot_constants.rb @@ -4,24 +4,57 @@ require 'dotenv' module HotConstants HOTENV = {} # rubocop:disable Style/MutableConstant LISTENERS = {} # rubocop:disable Style/MutableConstant - LOGGER = SemanticLogger[to_s] + LOGGER = SemanticLogger[self] class << self def initialize - env_sample = Dotenv.parse('.env.sample') # TODO: able to detect add/remove, then either call define_method or undef_method! - env_sample.each do |key, value| - dkey = key.downcase - method = method_from_value(value) - singleton_class.define_method(dkey) { method.call(key, value) } - LOGGER.info("create method <#{dkey}> performing <#{method.name}> with default value <#{value}> ") + @old_definitions = {} + @hot_definitions = {} + + set_definitions + load_values + end + + def set_definitions + LOGGER.info('--set_definitions') + + new_definitions = Dotenv.parse('.env.sample') + + definitions_to_actualize = {} + @old_definitions.merge(new_definitions) do |key, old_value, new_value| + definitions_to_actualize[key] = new_value if new_value != old_value end + definitions_to_actualize.each { |key, value| define_method(:actualize, key, value) } + + @old_definitions = new_definitions + + definitions_to_remove = @hot_definitions.except(*new_definitions.keys) + definitions_to_remove.each_pair do |key, dkey| + LOGGER.info("remove method <#{dkey}>") + singleton_class.undef_method(dkey) + @hot_definitions.delete(key) + end + + definitions_to_add = new_definitions.except(*@hot_definitions.keys) + definitions_to_add.each { |key, value| define_method(:add, key, value) } end - def reload! - HOTENV.replace Dotenv.parse # TODO: detect true changes before processing block below (or yield) - LISTENERS.each_pair do |key, block| - perform_change(key, block) + def load_values + LOGGER.info('--load_values') + + new_env = Dotenv.parse + constants_to_delete = HOTENV.except(*new_env.keys) + constants_to_delete.each do |constant| + LOGGER.info("constant <#{constant}> reverts to default value <#{}>") + end + + constants_to_add = new_env.except(*HOTENV.keys) + constants_to_add.each do |constant| + LOGGER.info("constant to add <#{constant}>") end + + HOTENV.replace new_env + # LISTENERS.each_pair { |k, b| perform_change(k, b) } end def on_change(key, &block) @@ -31,6 +64,17 @@ module HotConstants private + def define_method(mode, key, value) + dkey = key.downcase + method = infer_method_from_value(value) + + inferred_type = method.name.to_s.split('_')[1] + LOGGER.info("#{mode} method <#{dkey}> of type <#{inferred_type}> with default value <#{value}> ") + + singleton_class.define_method(dkey) { method.call(key, value) } + @hot_definitions.store(key, dkey) + end + def perform_change(key, block) old_value = nil # TODO: remember last previous value new_value = method(key).call @@ -41,7 +85,7 @@ module HotConstants def load_integer(key, default) = HOTENV.fetch(key, default).to_i def load_string(key, default) = HOTENV.fetch(key, default) - def method_from_value(value) + def infer_method_from_value(value) case value.downcase when /^(true|false)$/ then method(:load_boolean) when /^\d+$/ then method(:load_integer) @@ -50,6 +94,5 @@ module HotConstants end end - # initialize on first require - initialize + initialize # done once on first require, cause this is just a module (not a class!) end diff --git a/lib/monkey_patches/rails_live_reload/watcher.rb b/lib/monkey_patches/rails_live_reload/watcher.rb index eedc6d3..ac9b038 100644 --- a/lib/monkey_patches/rails_live_reload/watcher.rb +++ b/lib/monkey_patches/rails_live_reload/watcher.rb @@ -15,8 +15,8 @@ module RailsLiveReload private def before_reload(files) - perform_when_change(files, ENV_SAMPLE_FILE) { HotConstants.initialize } - perform_when_change(files, ENV_FILE) { HotConstants.reload! } + perform_when_change(files, ENV_SAMPLE_FILE) { HotConstants.set_definitions } + perform_when_change(files, ENV_FILE) { HotConstants.load_values } perform_when_change(files, INITIALIZER) { load Rails.root.join('config', 'initializers', 'hot_changes.rb') } end