Class: ProcessSettings::AbstractMonitor

Inherits:
Object
  • Object
show all
Defined in:
lib/process_settings/abstract_monitor.rb

Direct Known Subclasses

FileMonitor, Testing::Monitor

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(logger:) ⇒ AbstractMonitor

Returns a new instance of AbstractMonitor.



18
19
20
21
22
23
24
# File 'lib/process_settings/abstract_monitor.rb', line 18

def initialize(logger:)
  @logger = logger or raise ArgumentError, "logger must be not be nil"
  @on_change_callbacks = []
  @when_updated_blocks = Set.new
  @static_context = {}
  @full_context_cache = {}
end

Instance Attribute Details

#full_context_cacheObject (readonly)

Returns the value of attribute full_context_cache.



16
17
18
# File 'lib/process_settings/abstract_monitor.rb', line 16

def full_context_cache
  @full_context_cache
end

#loggerObject (readonly)

Returns the value of attribute logger.



15
16
17
# File 'lib/process_settings/abstract_monitor.rb', line 15

def logger
  @logger
end

#min_polling_secondsObject (readonly)

Returns the value of attribute min_polling_seconds.



15
16
17
# File 'lib/process_settings/abstract_monitor.rb', line 15

def min_polling_seconds
  @min_polling_seconds
end

#static_contextObject

Returns the value of attribute static_context.



16
17
18
# File 'lib/process_settings/abstract_monitor.rb', line 16

def static_context
  @static_context
end

#statically_targeted_settingsObject (readonly)

Returns the value of attribute statically_targeted_settings.



16
17
18
# File 'lib/process_settings/abstract_monitor.rb', line 16

def statically_targeted_settings
  @statically_targeted_settings
end

Class Method Details

.ensure_no_symbols(value) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/process_settings/abstract_monitor.rb', line 153

def ensure_no_symbols(value)
  case value
  when Symbol
    raise ArgumentError, "symbol value #{value.inspect} found--should be String"
  when Hash
    value.each do |k, v|
      k.is_a?(Symbol) and raise ArgumentError, "symbol key #{k.inspect} found--should be String"
      ensure_no_symbols(v)
    end
  when Array
    value.each { |v| ensure_no_symbols(v) }
  end
end

Instance Method Details

#[](*path, dynamic_context: {}, required: true) ⇒ Object

This is the main entry point for looking up settings on the Monitor instance.

‘path’, ‘to’, ‘setting’

will return 42 in this example settings YAML: code

path:
  to:
    setting:
      42

code

Parameters:

  • path (Array(String))

    The path of one or more strings.

  • dynamic_context (Hash) (defaults to: {})

    Optional dynamic context hash. It will be merged with the static context.

  • required (boolean) (defaults to: true)

    If true (default) will raise ‘SettingsPathNotFound` if not found; otherwise returns `nil` if not found.

Returns:

  • setting value



47
48
49
# File 'lib/process_settings/abstract_monitor.rb', line 47

def [](*path, dynamic_context: {}, required: true)
  targeted_value(*path, dynamic_context: dynamic_context, required: required)
end

#cancel_when_updated(handle) ⇒ Object

removes the given when_updated block identified by the handle returned from when_updated



69
70
71
# File 'lib/process_settings/abstract_monitor.rb', line 69

def cancel_when_updated(handle)
  @when_updated_blocks.delete_if { |callback| callback.eql?(handle) }
end

#full_context_from_cache(dynamic_context) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
# File 'lib/process_settings/abstract_monitor.rb', line 138

def full_context_from_cache(dynamic_context)
  if (full_context = full_context_cache[dynamic_context])
    full_context
  else
    dynamic_context.deep_merge(static_context).tap do |full_context|
      if full_context_cache.size <= 1000
        full_context_cache[dynamic_context] = full_context
      end
    end
  end
end

#on_change(&callback) ⇒ Object

Deprecated.

Registers the given callback block to be called when settings change. These are run using the shared thread that monitors for changes so be courteous and don’t monopolize it!



76
77
78
# File 'lib/process_settings/abstract_monitor.rb', line 76

def on_change(&callback)
  @on_change_callbacks << callback
end

#targeted_value(*path, dynamic_context:, required: true) ⇒ Object

Returns the process settings value at the given ‘path` using the given `dynamic_context`. (It is assumed that the static context was already set through static_context=.) If nothing set at the given `path`:

if required, raises SettingsPathNotFound
else returns nil


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/process_settings/abstract_monitor.rb', line 96

def targeted_value(*path, dynamic_context:, required: true)
  # Merging the static context in is necessary to make sure that the static context isn't shifting
  # this can be rather costly to do every time if the dynamic context is not changing

  # Warn in the case where dynamic context was attempting to change a static value
  changes = dynamic_context.each_with_object({}) do |(key, dynamic_value), result|
    if static_context.has_key?(key)
      static_value = static_context[key]
      if static_value != dynamic_value
        result[key] = [static_value, dynamic_value]
      end
    end
  end

  changes.empty? or warn("WARNING: static context overwritten by dynamic!\n#{changes.inspect}")

  full_context = full_context_from_cache(dynamic_context)
  result = statically_targeted_settings.reduce(:not_found) do |latest_result, target_and_settings|
    # find last value from matching targets
    if target_and_settings.target.target_key_matches?(full_context)
      if (value = target_and_settings.settings.json_doc.mine(*path, not_found_value: :not_found)) != :not_found
        latest_result = if latest_result.is_a?(Hash) && value.is_a?(Hash)
                          latest_result.deep_merge(value)
                        else
                          value
                        end
      end
    end
    latest_result
  end

  if result == :not_found
    if required
      raise SettingsPathNotFound, "no settings found for path #{path.inspect}"
    else
      nil
    end
  else
    result
  end
end

#when_updated(initial_update: true, &block) ⇒ Object

Idempotently adds the given block to the when_updated collection calls the block first unless initial_update: false is passed returns a handle (the block itself) which can later be passed into cancel_when_updated



54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/process_settings/abstract_monitor.rb', line 54

def when_updated(initial_update: true, &block)
  if @when_updated_blocks.add?(block)
    if initial_update
      begin
        block.call(self)
      rescue => ex
        logger.error("ProcessSettings::Monitor#when_updated rescued exception during initialization:\n#{ex.class}: #{ex.message}")
      end
    end
  end

  block
end