process_settings 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c35c37914363571825848c95ec6d1f3d15811b11b0bea5671f28744a4108129
4
- data.tar.gz: 6c252be7fb7d924f50fd9a6cdbbb35bcfa292da595cb79c3e644a8f9f3da08d1
3
+ metadata.gz: 7ace753f727c3aea91291d906951ff86da6750d6aa4b0db1022ad6155dce42dc
4
+ data.tar.gz: 1081a5ba75c69f17f6ad308125a748751d381b7289b27e63a0e9130fd96db0bd
5
5
  SHA512:
6
- metadata.gz: f07d59b5f034ce85b5e20b117fe731d3b3860b6422c0ba2c8fcdaf4c39d88c5af70ed6636952cd9385c25ad9e320cfd1784671689406dae71ac051fdf1daa709
7
- data.tar.gz: 3a74f7e775d7c4688a20b0c3b93918992ef5acd3163aa581f411b87fa326ef7cc0eaea0f090915464f3b20f45740112c681543b3a7c8c5c58eba3dca8b0ba6f2
6
+ metadata.gz: dbfbe4d945aa9d67b02862be7f5905afd36b0c0c1c5fd1f2054d6b263f6ccdaf878bbc6521361c4454183c9d00f882947572933aa6c52850a8c10e3b32839510
7
+ data.tar.gz: 6e2341fca852024f9d0a518159c6367264376fb760d3efde1904546cc75a40042272ad1c0c9e075b7e68f23891c5812911253f98a66f514f72992aad2324dd7b
data/README.md CHANGED
@@ -5,6 +5,10 @@ Settings are managed in a git repo, in separate YAML files for each concern (for
5
5
 
6
6
  The context can be either static to the process (for example, `service_name` or `datacenter`) or dynamic (for example, the current web request `domain`).
7
7
 
8
+ ## Dependencies
9
+ * Ruby >= 2.6
10
+ * ActiveSupport >= 4.2, < 7
11
+
8
12
  ## Installation
9
13
  To install this gem directly on your machine from rubygems, run the following:
10
14
  ```ruby
@@ -160,18 +164,56 @@ This will be applied in any process that has (`service_name == "frontend"` OR `s
160
164
  The settings YAML files are always combined in alphabetical order by file path. Later settings take precedence over the earlier ones.
161
165
 
162
166
  ### Testing
163
- For testing, it is often necessary to set a specific hash for the process_settings values to use in that test case.
164
- The `ProcessSettings::Testing::MonitorStub` class is provided for this purpose. It can be initialized with a hash and assigned to `ProcessSettings::Monitor.instance`. After the test runs, make sure to call `clear_instance`.
165
- Note that it has no `targeting` or `settings` keys; it is stubbing the resulting settings hash _after_ targeting has been applied.
166
- Here is an example using `rspec` conventions:
167
- ```
168
- before do
169
- settings = { "honeypot" => { "answer_odds" => 100 } }
170
- ProcessSettings::Monitor.instance = ProcessSettings::Testing::MonitorStub.new(settings)
167
+ For testing, it is often necessary to set a specific override hash for the process_settings values to use in
168
+ that use case. The `ProcessSettings::Testing::Helpers` module is provided for this purpose. It can be used to
169
+ override a specific hash of process settings, while leving the rest in tact, and resetting back to the defaults
170
+ after the test case is over. Here are some examples using various testing frameworks:
171
+
172
+ #### RSpec
173
+ ##### `spec_helper.rb`
174
+ ```ruby
175
+ require 'process_settings/testing/helpers'
176
+
177
+ RSpec.configure do |config|
178
+ # ...
179
+
180
+ include ProcessSettings::Testing::Helpers
181
+
182
+ after do
183
+ reset_process_settings
184
+ end
185
+
186
+ # ...
187
+ end
188
+ ```
189
+
190
+ ##### `process_settings_spec.rb`
191
+ ```ruby
192
+ require 'spec_helper'
193
+
194
+ RSpec.describe SomeClass do
195
+ before do
196
+ stub_process_settings(honeypot: { answer_odds: 100 })
197
+ end
198
+
199
+ # ...
171
200
  end
201
+ ```
202
+
203
+ #### Test::Unit / Minitest
204
+ ```ruby
205
+ require 'process_settings/testing/helpers'
206
+
207
+ context SomeClass do
208
+ setup do
209
+ stub_process_settings(honeypot: { answer_odds: 100 })
210
+ end
211
+
212
+ teardown do
213
+ reset_process_settings
214
+ end
172
215
 
173
- after do
174
- ProcessSettings::Monitor.clear_instance
216
+ # ...
175
217
  end
176
218
  ```
177
219
 
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ require 'process_settings/targeted_settings'
6
+ require 'process_settings/hash_path'
7
+
8
+ module ProcessSettings
9
+ class SettingsPathNotFound < StandardError; end
10
+
11
+ OnChangeDeprecation = ActiveSupport::Deprecation.new('1.0', 'ProcessSettings::Monitor')
12
+
13
+ class AbstractMonitor
14
+ attr_reader :min_polling_seconds, :logger
15
+ attr_reader :static_context, :statically_targeted_settings
16
+
17
+ def initialize(logger:)
18
+ @logger = logger
19
+ @on_change_callbacks = []
20
+ @when_updated_blocks = Set.new
21
+ @static_context = {}
22
+ end
23
+
24
+ # This is the main entry point for looking up settings on the Monitor instance.
25
+ #
26
+ # @example
27
+ #
28
+ # ['path', 'to', 'setting']
29
+ #
30
+ # will return 42 in this example settings YAML:
31
+ # +code+
32
+ # path:
33
+ # to:
34
+ # setting:
35
+ # 42
36
+ # +code+
37
+ #
38
+ # @param [Array(String)] path The path of one or more strings.
39
+ #
40
+ # @param [Hash] dynamic_context Optional dynamic context hash. It will be merged with the static context.
41
+ #
42
+ # @param [boolean] required If true (default) will raise `SettingsPathNotFound` if not found; otherwise returns `nil` if not found.
43
+ #
44
+ # @return setting value
45
+ def [](*path, dynamic_context: {}, required: true)
46
+ targeted_value(*path, dynamic_context: dynamic_context, required: required)
47
+ end
48
+
49
+ # Idempotently adds the given block to the when_updated collection
50
+ # calls the block first unless initial_update: false is passed
51
+ # returns a handle (the block itself) which can later be passed into cancel_when_updated
52
+ def when_updated(initial_update: true, &block)
53
+ if @when_updated_blocks.add?(block)
54
+ if initial_update
55
+ begin
56
+ block.call(self)
57
+ rescue => ex
58
+ logger.error("ProcessSettings::Monitor#when_updated rescued exception during initialization:\n#{ex.class}: #{ex.message}")
59
+ end
60
+ end
61
+ end
62
+
63
+ block
64
+ end
65
+
66
+ # removes the given when_updated block identified by the handle returned from when_updated
67
+ def cancel_when_updated(handle)
68
+ @when_updated_blocks.delete_if { |callback| callback.eql?(handle) }
69
+ end
70
+
71
+ # Registers the given callback block to be called when settings change.
72
+ # These are run using the shared thread that monitors for changes so be courteous and don't monopolize it!
73
+ # @deprecated
74
+ def on_change(&callback)
75
+ @on_change_callbacks << callback
76
+ end
77
+ deprecate on_change: :when_updated, deprecator: OnChangeDeprecation
78
+
79
+ # Assigns a new static context. Recomputes statically_targeted_settings.
80
+ # Keys must be strings or integers. No symbols.
81
+ def static_context=(context)
82
+ self.class.ensure_no_symbols(context)
83
+
84
+ @static_context = context
85
+
86
+ load_statically_targeted_settings(force_retarget: true)
87
+ end
88
+
89
+ # Returns the process settings value at the given `path` using the given `dynamic_context`.
90
+ # (It is assumed that the static context was already set through static_context=.)
91
+ # If nothing set at the given `path`:
92
+ # if required, raises SettingsPathNotFound
93
+ # else returns nil
94
+ def targeted_value(*path, dynamic_context:, required: true)
95
+ # Merging the static context in is necessary to make sure that the static context isn't shifting
96
+ # this can be rather costly to do every time if the dynamic context is not changing
97
+ # TODO: Warn in the case where dynamic context was attempting to change a static value
98
+ # TODO: Cache the last used dynamic context as a potential optimization to avoid unnecessary deep merges
99
+ # TECH-4402 was created to address these todos
100
+ full_context = dynamic_context.deep_merge(static_context)
101
+ result = statically_targeted_settings.reduce(:not_found) do |latest_result, target_and_settings|
102
+ # find last value from matching targets
103
+ if target_and_settings.target.target_key_matches?(full_context)
104
+ if (value = target_and_settings.settings.json_doc.mine(*path, not_found_value: :not_found)) != :not_found
105
+ latest_result = value
106
+ end
107
+ end
108
+ latest_result
109
+ end
110
+
111
+ if result == :not_found
112
+ if required
113
+ raise SettingsPathNotFound, "no settings found for path #{path.inspect}"
114
+ else
115
+ nil
116
+ end
117
+ else
118
+ result
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ class << self
125
+ def ensure_no_symbols(value)
126
+ case value
127
+ when Symbol
128
+ raise ArgumentError, "symbol value #{value.inspect} found--should be String"
129
+ when Hash
130
+ value.each do |k, v|
131
+ k.is_a?(Symbol) and raise ArgumentError, "symbol key #{k.inspect} found--should be String"
132
+ ensure_no_symbols(v)
133
+ end
134
+ when Array
135
+ value.each { |v| ensure_no_symbols(v) }
136
+ end
137
+ end
138
+ end
139
+
140
+ # Calls all registered on_change callbacks. Rescues any exceptions they may raise.
141
+ # Note: this method can be re-entrant to the class; the on_change callbacks may call right back into these methods.
142
+ # Therefore it's critical to finish all transitions and release any resources before calling this method.
143
+ def notify_on_change
144
+ @on_change_callbacks.each do |callback|
145
+ begin
146
+ callback.call(self)
147
+ rescue => ex
148
+ logger.error("ProcessSettings::Monitor#notify_on_change rescued exception:\n#{ex.class}: #{ex.message}")
149
+ end
150
+ end
151
+ end
152
+
153
+ def call_when_updated_blocks
154
+ @when_updated_blocks.each do |block|
155
+ begin
156
+ block.call(self)
157
+ rescue => ex
158
+ logger.error("ProcessSettings::Monitor#call_when_updated_blocks rescued exception:\n#{ex.class}: #{ex.message}")
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'listen'
5
+ require 'psych'
6
+
7
+ require 'process_settings/abstract_monitor'
8
+ require 'process_settings/targeted_settings'
9
+ require 'process_settings/hash_path'
10
+
11
+ module ProcessSettings
12
+ class FileMonitor < AbstractMonitor
13
+ attr_reader :file_path, :untargeted_settings
14
+
15
+ def initialize(file_path, logger:)
16
+ super(logger: logger)
17
+
18
+ @file_path = File.expand_path(file_path)
19
+ @last_statically_targetted_settings = nil
20
+ @untargeted_settings = nil
21
+ @last_untargetted_settings = nil
22
+
23
+ start
24
+ end
25
+
26
+ # starts listening for changes
27
+ # Note: This method creates a new thread that will be monitoring for changes
28
+ # do to the nature of how the Listen gem works, there is no record of
29
+ # existing threads, calling this mutliple times will result in spinning off
30
+ # multiple listen threads and will have unknow effects
31
+ def start
32
+ path = File.dirname(file_path)
33
+
34
+ # to eliminate any race condition:
35
+ # 1. set up file watcher
36
+ # 2. start it (this should trigger if any changes have been made since (1))
37
+ # 3. load the file
38
+
39
+ @listener = file_change_notifier.to(path) do |modified, added, _removed|
40
+ if modified.include?(file_path) || added.include?(file_path)
41
+ logger.info("ProcessSettings::Monitor file #{file_path} changed. Reloading.")
42
+ load_untargeted_settings
43
+
44
+ load_statically_targeted_settings
45
+ end
46
+ end
47
+
48
+ unless ENV['DISABLE_LISTEN_CHANGE_MONITORING']
49
+ @listener.start
50
+ end
51
+
52
+ load_untargeted_settings
53
+ load_statically_targeted_settings
54
+ end
55
+
56
+ # stops listening for changes
57
+ def stop
58
+ @listener&.stop
59
+ end
60
+
61
+ private
62
+
63
+ # Loads the most recent settings from disk
64
+ def load_untargeted_settings
65
+ new_untargeted_settings = load_file(file_path)
66
+ old_version = @untargeted_settings&.version
67
+ new_version = new_untargeted_settings.version
68
+ @untargeted_settings = new_untargeted_settings
69
+ logger.info("ProcessSettings::Monitor#load_untargeted_settings loaded version #{new_version}#{" to replace version #{old_version}" if old_version}")
70
+ end
71
+
72
+ # Loads the latest untargeted settings from disk. Returns the current process settings as a TargetAndProcessSettings given
73
+ # by applying the static context to the current untargeted settings from disk.
74
+ # If these have changed, borrows this thread to call notify_on_change and call_when_updated_blocks.
75
+ def load_statically_targeted_settings(force_retarget: false)
76
+ if force_retarget || @last_untargetted_settings != @untargeted_settings
77
+ @last_untargetted_settings = @untargeted_settings
78
+ @statically_targeted_settings = @untargeted_settings.with_static_context(@static_context)
79
+ if @last_statically_targetted_settings != @statically_targeted_settings
80
+ @last_statically_targetted_settings = @statically_targeted_settings
81
+
82
+ notify_on_change
83
+ call_when_updated_blocks
84
+ end
85
+ end
86
+ end
87
+
88
+ def load_file(file_path)
89
+ TargetedSettings.from_file(file_path)
90
+ end
91
+
92
+ def file_change_notifier
93
+ Listen
94
+ end
95
+ end
96
+ end
@@ -1,264 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'targeted_settings'
4
- require_relative 'hash_path'
5
- require 'psych'
6
- require 'listen'
7
3
  require 'active_support'
4
+ require 'active_support/deprecation'
8
5
 
9
- module ProcessSettings
10
- class SettingsPathNotFound < StandardError; end
11
-
12
- OnChangeDeprecation = ActiveSupport::Deprecation.new('1.0', 'ProcessSettings::Monitor')
13
-
14
- class Monitor
15
- attr_reader :file_path, :min_polling_seconds, :logger
16
- attr_reader :static_context, :untargeted_settings, :statically_targeted_settings
17
-
18
- DEFAULT_MIN_POLLING_SECONDS = 5
19
-
20
- def initialize(file_path, logger:)
21
- @file_path = File.expand_path(file_path)
22
- @logger = logger
23
- @on_change_callbacks = []
24
- @when_updated_blocks = Set.new
25
- @static_context = {}
26
- @last_statically_targetted_settings = nil
27
- @untargeted_settings = nil
28
- @last_untargetted_settings = nil
29
- @last_untargetted_settings = nil
30
-
31
- start
32
- end
33
-
34
- # []
35
- #
36
- # This is the main entry point for looking up settings on the Monitor instance.
37
- #
38
- # @example
39
- #
40
- # ['path', 'to', 'setting']
41
- #
42
- # will return 42 in this example settings YAML:
43
- # +code+
44
- # path:
45
- # to:
46
- # setting:
47
- # 42
48
- # +code+
49
- #
50
- # @param [Array(String)] path The path of one or more strings.
51
- #
52
- # @param [Hash] dynamic_context Optional dynamic context hash. It will be merged with the static context.
53
- #
54
- # @param [boolean] required If true (default) will raise `SettingsPathNotFound` if not found; otherwise returns `nil` if not found.
55
- #
56
- # @return setting value
57
- def [](*path, dynamic_context: {}, required: true)
58
- targeted_value(*path, dynamic_context: dynamic_context, required: required)
59
- end
60
-
61
- # starts listening for changes
62
- # Note: This method creates a new thread that will be monitoring for changes
63
- # do to the nature of how the Listen gem works, there is no record of
64
- # existing threads, calling this mutliple times will result in spinning off
65
- # multiple listen threads and will have unknow effects
66
- def start
67
- path = File.dirname(@file_path)
68
-
69
- # to eliminate any race condition:
70
- # 1. set up file watcher
71
- # 2. start it (this should trigger if any changes have been made since (1))
72
- # 3. load the file
73
-
74
- @listener = file_change_notifier.to(path) do |modified, added, _removed|
75
- if modified.include?(@file_path) || added.include?(@file_path)
76
- @logger.info("ProcessSettings::Monitor file #{@file_path} changed. Reloading.")
77
- load_untargeted_settings
78
-
79
- load_statically_targeted_settings
80
- end
81
- end
82
-
83
- unless ENV['DISABLE_LISTEN_CHANGE_MONITORING']
84
- @listener.start
85
- end
86
-
87
- load_untargeted_settings
88
- load_statically_targeted_settings
89
- end
90
-
91
- # stops listening for changes
92
- def stop
93
- @listener&.stop
94
- end
95
-
96
- # Idempotently adds the given block to the when_updated collection
97
- # calls the block first unless initial_update: false is passed
98
- # returns a handle (the block itself) which can later be passed into cancel_when_updated
99
- def when_updated(initial_update: true, &block)
100
- if @when_updated_blocks.add?(block)
101
- if initial_update
102
- begin
103
- block.call(self)
104
- rescue => ex
105
- logger.error("ProcessSettings::Monitor#when_updated rescued exception during initialization:\n#{ex.class}: #{ex.message}")
106
- end
107
- end
108
- end
109
-
110
- block
111
- end
112
-
113
- # removes the given when_updated block identified by the handle returned from when_updated
114
- def cancel_when_updated(handle)
115
- @when_updated_blocks.delete_if { |callback| callback.eql?(handle) }
116
- end
117
-
118
- # Registers the given callback block to be called when settings change.
119
- # These are run using the shared thread that monitors for changes so be courteous and don't monopolize it!
120
- # @deprecated
121
- def on_change(&callback)
122
- @on_change_callbacks << callback
123
- end
124
- deprecate on_change: :when_updated, deprecator: OnChangeDeprecation
6
+ require_relative 'file_monitor'
125
7
 
126
- # Assigns a new static context. Recomputes statically_targeted_settings.
127
- # Keys must be strings or integers. No symbols.
128
- def static_context=(context)
129
- self.class.ensure_no_symbols(context)
130
-
131
- @static_context = context
132
-
133
- load_statically_targeted_settings(force_retarget: true)
134
- end
8
+ module ProcessSettings
9
+ # DEPRECATED
10
+ class Monitor < FileMonitor
11
+ class << self
12
+ attr_reader :logger, :file_path
13
+ attr_writer :instance
135
14
 
136
- # Returns the process settings value at the given `path` using the given `dynamic_context`.
137
- # (It is assumed that the static context was already set through static_context=.)
138
- # If nothing set at the given `path`:
139
- # if required, raises SettingsPathNotFound
140
- # else returns nil
141
- def targeted_value(*path, dynamic_context:, required: true)
142
- # Merging the static context in is necessary to make sure that the static context isn't shifting
143
- # this can be rather costly to do every time if the dynamic context is not changing
144
- # TODO: Warn in the case where dynamic context was attempting to change a static value
145
- # TODO: Cache the last used dynamic context as a potential optimization to avoid unnecessary deep merges
146
- # TECH-4402 was created to address these todos
147
- full_context = dynamic_context.deep_merge(static_context)
148
- result = statically_targeted_settings.reduce(:not_found) do |latest_result, target_and_settings|
149
- # find last value from matching targets
150
- if target_and_settings.target.target_key_matches?(full_context)
151
- if (value = target_and_settings.settings.json_doc.mine(*path, not_found_value: :not_found)) != :not_found
152
- latest_result = value
153
- end
154
- end
155
- latest_result
156
- end
15
+ def file_path=(new_file_path)
16
+ ActiveSupport::Deprecation.warn("ProcessSettings::Monitor.file_path= is deprecated and will be removed in v1.0.")
17
+ clear_instance
157
18
 
158
- if result == :not_found
159
- if required
160
- raise SettingsPathNotFound, "no settings found for path #{path.inspect}"
161
- else
162
- nil
163
- end
164
- else
165
- result
19
+ @file_path = new_file_path
166
20
  end
167
- end
168
-
169
- private
170
-
171
- # Loads the most recent settings from disk
172
- def load_untargeted_settings
173
- new_untargeted_settings = load_file(file_path)
174
- old_version = @untargeted_settings&.version
175
- new_version = new_untargeted_settings.version
176
- @untargeted_settings = new_untargeted_settings
177
- logger.info("ProcessSettings::Monitor#load_untargeted_settings loaded version #{new_version}#{" to replace version #{old_version}" if old_version}")
178
- end
179
21
 
180
- # Loads the latest untargeted settings from disk. Returns the current process settings as a TargetAndProcessSettings given
181
- # by applying the static context to the current untargeted settings from disk.
182
- # If these have changed, borrows this thread to call notify_on_change and call_when_updated_blocks.
183
- def load_statically_targeted_settings(force_retarget: false)
184
- if force_retarget || @last_untargetted_settings != @untargeted_settings
185
- @last_untargetted_settings = @untargeted_settings
186
- @statically_targeted_settings = @untargeted_settings.with_static_context(@static_context)
187
- if @last_statically_targetted_settings != @statically_targeted_settings
188
- @last_statically_targetted_settings = @statically_targeted_settings
189
-
190
- notify_on_change
191
- call_when_updated_blocks
192
- end
22
+ def new_from_settings
23
+ file_path or raise ArgumentError, "#{self}::file_path must be set before calling instance method"
24
+ logger or raise ArgumentError, "#{self}::logger must be set before calling instance method"
25
+ new(file_path, logger: logger)
193
26
  end
194
- end
195
-
196
- class << self
197
- attr_accessor :file_path
198
- attr_reader :logger
199
- attr_writer :instance
200
27
 
201
28
  def clear_instance
202
29
  @instance = nil
30
+ @default_instance = nil
203
31
  end
204
32
 
205
33
  def instance
206
- @instance ||= begin
207
- file_path or raise ArgumentError, "#{self}::file_path must be set before calling instance method"
208
- logger or raise ArgumentError, "#{self}::logger must be set before calling instance method"
209
- new(file_path, logger: logger)
210
- end
34
+ ActiveSupport::Deprecation.warn("ProcessSettings::Monitor.instance is deprecated and will be removed in v1.0. Use ProcessSettings.instance instead.")
35
+ @instance ||= default_instance
36
+ end
37
+
38
+ def default_instance
39
+ @default_instance ||= new_from_settings
211
40
  end
212
41
 
213
42
  def logger=(new_logger)
43
+ ActiveSupport::Deprecation.warn("ProcessSettings::Monitor.logger is deprecated and will be removed in v1.0.")
214
44
  @logger = new_logger
215
45
  Listen.logger ||= new_logger
216
46
  end
217
47
 
218
- def ensure_no_symbols(value)
219
- case value
220
- when Symbol
221
- raise ArgumentError, "symbol value #{value.inspect} found--should be String"
222
- when Hash
223
- value.each do |k, v|
224
- k.is_a?(Symbol) and raise ArgumentError, "symbol key #{k.inspect} found--should be String"
225
- ensure_no_symbols(v)
226
- end
227
- when Array
228
- value.each { |v| ensure_no_symbols(v) }
229
- end
230
- end
231
- end
232
-
233
- # Calls all registered on_change callbacks. Rescues any exceptions they may raise.
234
- # Note: this method can be re-entrant to the class; the on_change callbacks may call right back into these methods.
235
- # Therefore it's critical to finish all transitions and release any resources before calling this method.
236
- def notify_on_change
237
- @on_change_callbacks.each do |callback|
238
- begin
239
- callback.call(self)
240
- rescue => ex
241
- logger.error("ProcessSettings::Monitor#notify_on_change rescued exception:\n#{ex.class}: #{ex.message}")
242
- end
243
- end
244
- end
245
-
246
- def call_when_updated_blocks
247
- @when_updated_blocks.each do |block|
248
- begin
249
- block.call(self)
250
- rescue => ex
251
- logger.error("ProcessSettings::Monitor#call_when_updated_blocks rescued exception:\n#{ex.class}: #{ex.message}")
252
- end
253
- end
48
+ deprecate :logger, :logger=, :file_path, :file_path=, deprecator: ActiveSupport::Deprecation.new('1.0', 'ProcessSettings')
254
49
  end
255
50
 
256
- def load_file(file_path)
257
- TargetedSettings.from_file(file_path)
258
- end
259
-
260
- def file_change_notifier
261
- Listen
262
- end
51
+ deprecate :initialize, deprecator: ActiveSupport::Deprecation.new('1.0', 'ProcessSettings')
263
52
  end
264
53
  end
@@ -11,6 +11,8 @@ module ProcessSettings
11
11
  def initialize(json_doc)
12
12
  json_doc.is_a?(Hash) or raise ArgumentError, "ProcessSettings must be a Hash; got #{json_doc.inspect}"
13
13
 
14
+ AbstractMonitor.ensure_no_symbols(json_doc)
15
+
14
16
  @json_doc = HashWithHashPath[json_doc]
15
17
  end
16
18
 
@@ -4,6 +4,8 @@ module ProcessSettings
4
4
  class Target
5
5
  include Comparable
6
6
 
7
+ TRUE_JSON_DOC = {}.freeze
8
+
7
9
  attr_reader :json_doc
8
10
 
9
11
  def initialize(json_doc)
@@ -11,7 +13,7 @@ module ProcessSettings
11
13
  end
12
14
 
13
15
  def target_key_matches?(context_hash)
14
- @json_doc == {} || self.class.target_key_matches?(@json_doc, context_hash)
16
+ @json_doc == TRUE_JSON_DOC || self.class.target_key_matches?(@json_doc, context_hash)
15
17
  end
16
18
 
17
19
  def with_static_context(static_context_hash)
@@ -93,5 +95,11 @@ module ProcessSettings
93
95
  end
94
96
  end
95
97
  end
98
+
99
+ class << self
100
+ def true_target
101
+ @true_target || new(TRUE_JSON_DOC)
102
+ end
103
+ end
96
104
  end
97
105
  end
@@ -11,10 +11,10 @@ module ProcessSettings
11
11
  def initialize(filename, target, settings)
12
12
  @filename = filename
13
13
 
14
- target.is_a?(Target) or raise ArgumentError, "target must be a ProcessTarget; got #{target.inspect}"
14
+ target.is_a?(Target) or raise ArgumentError, "target must be a Target; got #{target.inspect}"
15
15
  @target = target
16
16
 
17
- settings.is_a?(Settings) or raise ArgumentError, "settings must be a ProcessSettings; got #{settings.inspect}"
17
+ settings.is_a?(Settings) or raise ArgumentError, "settings must be a Settings; got #{settings.inspect}"
18
18
  @settings = settings
19
19
  end
20
20
 
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'process_settings/monitor'
4
+ require 'process_settings/settings'
5
+ require 'process_settings/target_and_settings'
6
+
7
+ require 'process_settings/testing/monitor'
8
+
9
+ module ProcessSettings
10
+ module Testing
11
+ module Helpers
12
+
13
+ # Adds the given settings_hash as an override at the end of the process_settings array, with default targeting (true).
14
+ # Therefore this will override these settings while leaving others alone.
15
+ #
16
+ # @param [Hash] settings_hash
17
+ #
18
+ # @return none
19
+ def stub_process_settings(settings_hash)
20
+ new_target_and_settings = ProcessSettings::TargetAndSettings.new(
21
+ '<test_override>',
22
+ Target::true_target,
23
+ ProcessSettings::Settings.new(settings_hash.deep_stringify_keys)
24
+ )
25
+
26
+ new_process_settings = [
27
+ *initial_instance.statically_targeted_settings,
28
+ new_target_and_settings
29
+ ]
30
+
31
+ ProcessSettings.instance = ProcessSettings::Testing::Monitor.new(
32
+ new_process_settings,
33
+ logger: initial_instance.logger
34
+ )
35
+ end
36
+
37
+ private
38
+
39
+ def initial_instance
40
+ @initial_instance ||= ProcessSettings.instance
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext'
4
+ require 'process_settings/abstract_monitor'
5
+
6
+ module ProcessSettings
7
+ module Testing
8
+
9
+ # A special instance of the monitor specifically used for testing that
10
+ # allows the providing of a settings array from memory to initialize
11
+ # the ProcessSetting Monitor for testing
12
+ #
13
+ # @param Array settings_array
14
+ # @param Logger logger
15
+ class Monitor < ::ProcessSettings::AbstractMonitor
16
+ def initialize(settings_array, logger:)
17
+ super(logger: logger)
18
+ @statically_targeted_settings = settings_array
19
+ end
20
+
21
+ private
22
+
23
+ def load_statically_targetted_settings(force_retarget: false)
24
+ @statically_targeted_settings
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support/core_ext'
4
+
3
5
  require_relative '../monitor'
4
6
  require_relative '../hash_with_hash_path'
5
7
 
@@ -8,6 +10,7 @@ module ProcessSettings
8
10
  # This class implements the Monitor#targeted_value interface but is stubbed to use a simple hash in tests
9
11
  class MonitorStub
10
12
  def initialize(settings_hash)
13
+ ActiveSupport::Deprecation.warn("ProcessSettings::Testing::MonitorStub is deprecated and will be removed in future versions. Use ProcessSettings::Testing::Monitor instead.", caller)
11
14
  @settings_hash = HashWithHashPath[settings_hash]
12
15
  end
13
16
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ProcessSettings
4
- VERSION = '0.8.2'
4
+ VERSION = '0.9.0'
5
5
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support'
4
+ require 'active_support/deprecation'
5
+
3
6
  module ProcessSettings
4
7
  end
5
8
 
@@ -7,8 +10,28 @@ require 'process_settings/monitor'
7
10
 
8
11
  module ProcessSettings
9
12
  class << self
10
- # []
13
+ # Setter method for assigning the monitor instance for ProcessSettings to use
14
+ #
15
+ # @example
16
+ #
17
+ # ProcessSettings.instance = ProcessSettings::FileMonitor.new(...)
11
18
  #
19
+ # @param [ProcessSettings::AbstractMonitor] monitor The monitor to assign for use by ProcessSettings
20
+ def instance=(monitor)
21
+ if monitor && !monitor.is_a?(ProcessSettings::AbstractMonitor)
22
+ raise ArgumentError, "Invalid monitor of type #{monitor.class.name} provided. Must be of type ProcessSettings::AbstractMonitor"
23
+ end
24
+
25
+ @instance = monitor
26
+ end
27
+
28
+ # Getter method for retrieving the current monitor instance being used by ProcessSettings
29
+ #
30
+ # @return [ProcessSettings::AbstractMonitor]
31
+ def instance
32
+ @instance ||= lazy_create_instance
33
+ end
34
+
12
35
  # This is the main entry point for looking up settings in the process.
13
36
  #
14
37
  # @example
@@ -31,7 +54,14 @@ module ProcessSettings
31
54
  #
32
55
  # @return setting value
33
56
  def [](*path, dynamic_context: {}, required: true)
34
- Monitor.instance[*path, dynamic_context: dynamic_context, required: required]
57
+ instance[*path, dynamic_context: dynamic_context, required: required]
58
+ end
59
+
60
+ private
61
+
62
+ def lazy_create_instance
63
+ ActiveSupport::Deprecation.warn("lazy creation of Monitor instance is deprecated and will be removed from ProcessSettings 1.0")
64
+ Monitor.instance
35
65
  end
36
66
  end
37
67
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process_settings
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoca
@@ -16,14 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '4.2'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: '0'
29
+ version: '4.2'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '7'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: json
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -70,6 +76,8 @@ files:
70
76
  - bin/process_settings_for_services
71
77
  - bin/process_settings_version
72
78
  - lib/process_settings.rb
79
+ - lib/process_settings/abstract_monitor.rb
80
+ - lib/process_settings/file_monitor.rb
73
81
  - lib/process_settings/hash_path.rb
74
82
  - lib/process_settings/hash_with_hash_path.rb
75
83
  - lib/process_settings/monitor.rb
@@ -78,6 +86,8 @@ files:
78
86
  - lib/process_settings/target.rb
79
87
  - lib/process_settings/target_and_settings.rb
80
88
  - lib/process_settings/targeted_settings.rb
89
+ - lib/process_settings/testing/helpers.rb
90
+ - lib/process_settings/testing/monitor.rb
81
91
  - lib/process_settings/testing/monitor_stub.rb
82
92
  - lib/process_settings/util.rb
83
93
  - lib/process_settings/version.rb