super_settings 1.0.1 → 2.0.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -2
  3. data/README.md +121 -16
  4. data/VERSION +1 -1
  5. data/app/helpers/super_settings/settings_helper.rb +13 -3
  6. data/app/views/layouts/super_settings/settings.html.erb +1 -1
  7. data/config/routes.rb +1 -1
  8. data/db/migrate/20210414004553_create_super_settings.rb +1 -7
  9. data/lib/super_settings/application/api.js +4 -1
  10. data/lib/super_settings/application/helper.rb +56 -17
  11. data/lib/super_settings/application/images/arrow-down-short.svg +3 -0
  12. data/lib/super_settings/application/images/arrow-up-short.svg +3 -0
  13. data/lib/super_settings/application/images/info-circle.svg +4 -0
  14. data/lib/super_settings/application/images/pencil-square.svg +4 -0
  15. data/lib/super_settings/application/images/plus.svg +3 -1
  16. data/lib/super_settings/application/images/trash3.svg +3 -0
  17. data/lib/super_settings/application/images/x-circle.svg +4 -0
  18. data/lib/super_settings/application/index.html.erb +54 -37
  19. data/lib/super_settings/application/layout.html.erb +5 -2
  20. data/lib/super_settings/application/layout_styles.css +7 -151
  21. data/lib/super_settings/application/layout_vars.css.erb +21 -0
  22. data/lib/super_settings/application/scripts.js +100 -21
  23. data/lib/super_settings/application/style_vars.css.erb +62 -0
  24. data/lib/super_settings/application/styles.css +183 -14
  25. data/lib/super_settings/application.rb +18 -11
  26. data/lib/super_settings/attributes.rb +1 -8
  27. data/lib/super_settings/configuration.rb +9 -0
  28. data/lib/super_settings/context/current.rb +33 -0
  29. data/lib/super_settings/context.rb +3 -0
  30. data/lib/super_settings/controller_actions.rb +2 -2
  31. data/lib/super_settings/engine.rb +1 -3
  32. data/lib/super_settings/history_item.rb +1 -1
  33. data/lib/super_settings/http_client.rb +165 -0
  34. data/lib/super_settings/local_cache.rb +0 -15
  35. data/lib/super_settings/rack_application.rb +3 -3
  36. data/lib/super_settings/rest_api.rb +5 -4
  37. data/lib/super_settings/setting.rb +14 -3
  38. data/lib/super_settings/storage/active_record_storage/models.rb +28 -0
  39. data/lib/super_settings/storage/active_record_storage.rb +10 -20
  40. data/lib/super_settings/storage/history_attributes.rb +31 -0
  41. data/lib/super_settings/storage/http_storage.rb +60 -184
  42. data/lib/super_settings/storage/json_storage.rb +201 -0
  43. data/lib/super_settings/storage/mongodb_storage.rb +238 -0
  44. data/lib/super_settings/storage/redis_storage.rb +50 -111
  45. data/lib/super_settings/storage/s3_storage.rb +165 -0
  46. data/lib/super_settings/storage/storage_attributes.rb +64 -0
  47. data/lib/super_settings/storage/test_storage.rb +3 -5
  48. data/lib/super_settings/storage/transaction.rb +67 -0
  49. data/lib/super_settings/storage.rb +17 -8
  50. data/lib/super_settings/time_precision.rb +36 -0
  51. data/lib/super_settings.rb +48 -13
  52. data/super_settings.gemspec +11 -2
  53. metadata +30 -12
  54. data/lib/super_settings/application/images/edit.svg +0 -1
  55. data/lib/super_settings/application/images/info.svg +0 -1
  56. data/lib/super_settings/application/images/slash.svg +0 -1
  57. data/lib/super_settings/application/images/trash.svg +0 -1
  58. /data/{MIT-LICENSE → MIT-LICENSE.txt} +0 -0
@@ -0,0 +1,62 @@
1
+ .super-settings {
2
+ --primary-color-h: 216;
3
+ --primary-color-s: 98%;
4
+ --primary-color-l: 52%;
5
+ --primary-color-hsl: var(--primary-color-h), var(--primary-color-s), var(--primary-color-l);
6
+ --primary-color: hsl(var(--primary-color-hsl));
7
+ --primary-contrast-color: #fff;
8
+ --primary-hover-color: hsl(var(--primary-color-h), var(--primary-color-s), calc(var(--primary-color-l) - 10%));
9
+ --primary-border-color: hsl(var(--primary-color-h), var(--primary-color-s), calc(var(--primary-color-l) + 10%));
10
+ }
11
+
12
+ <% unless color_scheme == :dark %>
13
+ .super-settings {
14
+ --edit-bg-color: #f2fdf2;
15
+ --deleted-row-color: darkred;
16
+ --deleted-row-bg-color: #ffd1d8;
17
+ --history-key-color: royalblue;
18
+ --table-header-bg-color: #fff;
19
+ --table-border-color: #dee2e6;
20
+ --alt-row-color: rgba(0, 0, 0, .05);
21
+ --form-control-color: #495057;
22
+ --form-control-bg-color: #fff;
23
+ --form-control-border-color: #ced4da;
24
+ --form-control-placeholder-color: #bbb;
25
+ --modal-bg-color: #fff;
26
+ --modal-transparency: 0.4;
27
+ --modal-control-color: #000;
28
+ --success-color: green;
29
+ --danger-color: firebrick;
30
+ --muted-color: #666;
31
+ --unselected-color: #666;
32
+ }
33
+ <% end %>
34
+
35
+ <% if color_scheme == :system %>
36
+ @media (prefers-color-scheme: dark) {
37
+ <% end %>
38
+ <% if color_scheme == :system || color_scheme == :dark %>
39
+ .super-settings {
40
+ --edit-bg-color: #8dc875;
41
+ --deleted-row-color: #e0b1b8;
42
+ --deleted-row-bg-color: #7a3636;
43
+ --history-key-color: #a7d6f4;
44
+ --table-header-bg-color: #333;
45
+ --table-border-color: #555;
46
+ --alt-row-color: rgba(0, 0, 0, .30);
47
+ --form-control-color: #eee;
48
+ --form-control-bg-color: #666;
49
+ --form-control-border-color: #555;
50
+ --form-control-placeholder-color: #aaa;
51
+ --modal-bg-color: #333;
52
+ --modal-transparency: 0.75;
53
+ --modal-control-color: #fff;
54
+ --success-color: #00ff00;
55
+ --danger-color: #ff0000;
56
+ --muted-color: #999;
57
+ --unselected-color: #ccc;
58
+ }
59
+ <% end %>
60
+ <% if color_scheme == :system %>
61
+ }
62
+ <% end %>
@@ -1,3 +1,10 @@
1
+ .super-settings-container {
2
+ padding-left: 15px;
3
+ padding-right: 15px;
4
+ margin-left: auto;
5
+ margin-right: auto;
6
+ }
7
+
1
8
  #settings-table td p {
2
9
  margin-top: 0;
3
10
  margin-bottom: 0.5rem;
@@ -11,13 +18,9 @@
11
18
  width: 100%;
12
19
  }
13
20
 
14
- .super-settings-edit-row {
15
- background-color: #f2fdf2 !important;
16
- }
17
-
18
21
  #settings-table tr[data-deleted] td {
19
- background-color: #ffd1d8 !important;
20
- color: darkred;
22
+ background-color: var(--deleted-row-bg-color) !important;
23
+ color: var(--deleted-row-color);
21
24
  text-decoration: line-through;
22
25
  }
23
26
 
@@ -25,16 +28,49 @@
25
28
  display: none;
26
29
  }
27
30
 
31
+ .super-settings-icon {
32
+ display: inline-block;
33
+ }
34
+
35
+ .super-settings-icon svg {
36
+ width: 100%;
37
+ height: 100%;
38
+ vertical-align: inherit;
39
+ }
40
+
41
+ .super-settings-btn-no-chrome {
42
+ display: inline-block;
43
+ vertical-align: middle;
44
+ background-color: transparent;
45
+ border: 0;
46
+ padding: 0;
47
+ cursor: pointer;
48
+ }
49
+
50
+
51
+
52
+ .super-settings-sort-control {
53
+ color: var(--unselected-color);
54
+ }
55
+
56
+ .super-settings-sort-control svg {
57
+ vertical-align: middle;
58
+ }
59
+
60
+ .super-settings-edit-row {
61
+ background-color: var(--edit-bg-color) !important;
62
+ }
63
+
28
64
  .super-settings-key {
29
65
  overflow-wrap: break-word;
30
66
  max-width: 30rem;
31
- min-width: 15rem;
67
+ min-width: 8rem;
32
68
  }
33
69
 
34
70
  .super-settings-value {
35
71
  overflow-wrap: break-word;
36
72
  max-width: 30rem;
37
- min-width: 15rem;
73
+ min-width: 8rem;
38
74
  }
39
75
 
40
76
  .super-settings-value-type {
@@ -59,7 +95,34 @@
59
95
 
60
96
  .super-settings-history-key {
61
97
  font-weight: normal;
62
- color: royalblue;
98
+ color: var(--history-key-color);
99
+ }
100
+
101
+ .super-settings-table {
102
+ width: 100%;
103
+ max-width: 100%;
104
+ margin-bottom: 1rem;
105
+ border-collapse: collapse;
106
+ }
107
+
108
+ .super-settings-table thead th {
109
+ vertical-align: bottom;
110
+ border-bottom: 2px solid var(--table-border-color);
111
+ white-space: nowrap;
112
+ }
113
+
114
+ .super-settings-table td, .super-settings-table th {
115
+ padding: 0.75rem;
116
+ vertical-align: top;
117
+ border-top: 1px solid var(--table-border-color);
118
+ }
119
+
120
+ .super-settings-table-striped tbody tr:nth-of-type(odd) {
121
+ background-color: var(--alt-row-color);
122
+ }
123
+
124
+ .super-settings-align-center {
125
+ text-align: center;
63
126
  }
64
127
 
65
128
  .super-settings-modal {
@@ -72,12 +135,12 @@
72
135
  width: 100%;
73
136
  height: 100%;
74
137
  overflow: auto;
75
- background-color: rgba(0,0,0,0.4);
138
+ background-color: rgba(0, 0, 0, var(--modal-transparency));
76
139
  }
77
140
 
78
141
  .super-settings-modal-dialog {
79
142
  margin: auto;
80
- background-color: #fff;
143
+ background-color: var(--modal-bg-color);
81
144
  position: relative;
82
145
  outline: 0;
83
146
  padding: 2em;
@@ -99,7 +162,7 @@
99
162
  right: 0;
100
163
  border: 0;
101
164
  padding: 1ex;
102
- background-color: inherit;
165
+ color: var(--modal-control-color);
103
166
  font-size: 1.5rem;
104
167
  font-weight: 500;
105
168
  }
@@ -122,6 +185,112 @@
122
185
  .super-settings-sticky-top {
123
186
  position: sticky;
124
187
  top: 0;
125
- padding: 1rem 0;
126
- background-color: white;
188
+ padding: 1rem;
189
+ background-color: var(--table-header-bg-color);
190
+ }
191
+
192
+ .super-settings-form-control {
193
+ font-family: inherit;
194
+ margin: 0;
195
+ overflow: visible;
196
+ display: block;
197
+ width: 100%;
198
+ padding: .375rem .75rem;
199
+ font-size: 1rem;
200
+ line-height: 1.5;
201
+ color: var(--form-control-color);
202
+ background-color: var(--form-control-bg-color);
203
+ background-clip: padding-box;
204
+ border: 1px solid var(--form-control-border-color);
205
+ border-radius: .25rem;
206
+ transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
207
+ }
208
+
209
+ .super-settings-form-control::placeholder {
210
+ color: var(--form-control-placeholder-color);
211
+ }
212
+
213
+ select.super-settings-form-control:not([size]):not([multiple]) {
214
+ height: calc(2.25rem + 2px);
215
+ }
216
+
217
+ .super-settings-form-check {
218
+ margin-top: .5rem;
219
+ display: inline-block;
220
+ }
221
+
222
+ .super-settings-form-check input[type=checkbox] {
223
+ vertical-align: middle;
224
+ }
225
+
226
+ .super-settings-form-inline {
227
+ display: inline-block;
228
+ }
229
+
230
+ .super-settings-form-inline .super-settings-form-control {
231
+ display: inline-block;
232
+ width: auto;
233
+ vertical-align: middle;
234
+ }
235
+
236
+ .super-settings-form-control textarea {
237
+ font-family: inherit;
238
+ margin: 0;
239
+ overflow: auto;
240
+ resize: vertical;
241
+ }
242
+
243
+ .super-settings-text-success {
244
+ color: var(--success-color) !important;
245
+ }
246
+
247
+ .super-settings-text-danger {
248
+ color: var(--danger-color) !important;
249
+ }
250
+
251
+ .super-settings-text-muted {
252
+ color: var(--muted-color) !important;
253
+ }
254
+
255
+ .super-settings-btn {
256
+ display: inline-block;
257
+ vertical-align: middle;
258
+ padding: 0.375rem 0.75rem;
259
+ border-radius: 0.375rem;
260
+ border: 1px solid #dcdcdc;
261
+ background-color:#f9f9f9;
262
+ text-decoration: none;
263
+ text-align: center;
264
+ font-family: Arial, sans-serif;
265
+ font-size: 1rem;
266
+ font-weight: 400;
267
+ line-height: 1.5;
268
+ user-select: none;
269
+ cursor: pointer;
270
+ transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out, box-shadow .15s ease-in-out;
271
+ }
272
+
273
+ .super-settings-btn:hover:not(:disabled) {
274
+ background-color:#e9e9e9;
275
+ }
276
+ .super-settings-btn:active {
277
+ position:relative;
278
+ top:1px;
279
+ }
280
+ .super-settings-btn:disabled {
281
+ opacity: 0.8;
282
+ cursor: not-allowed;
283
+ }
284
+
285
+ .super-settings-btn-primary {
286
+ background-color: var(--primary-color);
287
+ border-color: var(--primary-border-color);
288
+ color: var(--primary-contrast-color);
289
+ }
290
+ .super-settings-btn-primary:hover:not(:disabled) {
291
+ background-color: var(--primary-hover-color);
292
+ }
293
+
294
+ .super-settings-btn-primary:disabled {
295
+ opacity: 0.5;
127
296
  }
@@ -10,31 +10,38 @@ module SuperSettings
10
10
  # @param layout [String, Symbol] path to an ERB template to use as the layout around the application UI. You can
11
11
  # pass the symbol +:default+ to use the default layout that ships with the gem.
12
12
  # @param add_to_head [String] HTML code to add to the <head> element on the page.
13
- def initialize(layout = nil, add_to_head = nil)
13
+ # @param api_base_url [String] the base URL for the REST API.
14
+ # @param color_scheme [Symbol] whether to use dark mode for the application UI. If +nil+, the user's system
15
+ # preference will be used.
16
+ def initialize(layout: nil, add_to_head: nil, api_base_url: nil, color_scheme: nil)
14
17
  if layout
15
18
  layout = File.expand_path(File.join("application", "layout.html.erb"), __dir__) if layout == :default
16
- @layout = ERB.new(File.read(layout))
19
+ @layout = ERB.new(File.read(layout)) if layout
17
20
  @add_to_head = add_to_head
21
+ else
22
+ @layout = nil
23
+ @add_to_head = nil
18
24
  end
25
+
26
+ @api_base_url = api_base_url
27
+ @color_scheme = color_scheme&.to_sym
19
28
  end
20
29
 
21
- # Render the specified ERB file in the lib/application directory distributed with the gem.
30
+ # Render the web UI application HTML.
22
31
  #
23
32
  # @return [void]
24
- def render(erb_file)
25
- template = ERB.new(File.read(File.expand_path(File.join("application", erb_file), __dir__)))
33
+ def render
34
+ template = ERB.new(File.read(File.expand_path(File.join("application", "index.html.erb"), __dir__)))
26
35
  html = template.result(binding)
27
- if @layout
28
- render_layout { html }
29
- else
30
- html
31
- end
36
+ html = render_layout { html } if @layout
37
+ html = html.html_safe if html.respond_to?(:html_safe)
38
+ html
32
39
  end
33
40
 
34
41
  private
35
42
 
36
43
  def render_layout
37
- @layout.result(binding)
44
+ @layout&.result(binding)
38
45
  end
39
46
  end
40
47
  end
@@ -4,20 +4,13 @@ module SuperSettings
4
4
  # Interface to expose mass setting attributes on an object. Setting attributes with a
5
5
  # hash will simply call the attribute writers for each key in the hash.
6
6
  module Attributes
7
- class UnknownAttributeError < StandardError
8
- end
9
-
10
7
  def initialize(attributes = nil)
11
8
  self.attributes = attributes if attributes
12
9
  end
13
10
 
14
11
  def attributes=(values)
15
12
  values.each do |name, value|
16
- if respond_to?("#{name}=", true)
17
- send("#{name}=", value)
18
- else
19
- raise UnknownAttributeError.new("unknown attribute #{name.to_s.inspect} for #{self.class}")
20
- end
13
+ send(:"#{name}=", value) if respond_to?(:"#{name}=", true)
21
14
  end
22
15
  end
23
16
  end
@@ -25,6 +25,7 @@ module SuperSettings
25
25
  def initialize
26
26
  @superclass = nil
27
27
  @web_ui_enabled = true
28
+ @color_scheme = false
28
29
  @changed_by_block = nil
29
30
  end
30
31
 
@@ -63,6 +64,14 @@ module SuperSettings
63
64
  !!@web_ui_enabled
64
65
  end
65
66
 
67
+ # Set dark mode for the web UI. Possible values are :light, :dark, or :system.
68
+ # The default value is :light.
69
+ attr_writer :color_scheme
70
+
71
+ def color_scheme
72
+ (@color_scheme ||= :light).to_sym
73
+ end
74
+
66
75
  # Enhance the controller. You can define methods or call controller class methods like
67
76
  # +before_action+, etc. in the block. These will be applied to the engine controller.
68
77
  # This is essentially the same a monkeypatching the controller class.
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SuperSettings
4
+ module Context
5
+ class Current
6
+ def initialize
7
+ @context = {}
8
+ @seed = nil
9
+ end
10
+
11
+ def include?(key)
12
+ @context.include?(key)
13
+ end
14
+
15
+ def [](key)
16
+ @context[key]
17
+ end
18
+
19
+ def []=(key, value)
20
+ @context[key] = value
21
+ end
22
+
23
+ def delete(key)
24
+ @context.delete(key)
25
+ end
26
+
27
+ def rand(max = nil)
28
+ @seed ||= Random.new_seed
29
+ Random.new(@seed).rand(max || 1.0)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -2,5 +2,8 @@
2
2
 
3
3
  module SuperSettings
4
4
  module Context
5
+ autoload :Current, File.join(__dir__, "context/current")
6
+ autoload :RackMiddleware, File.join(__dir__, "context/rack_middleware")
7
+ autoload :SidekiqMiddleware, File.join(__dir__, "context/sidekiq_middleware")
5
8
  end
6
9
  end
@@ -19,7 +19,7 @@ module SuperSettings
19
19
 
20
20
  # Render the HTML application for managing settings.
21
21
  def root
22
- html = SuperSettings::Application.new.render("index.html.erb")
22
+ html = SuperSettings::Application.new.render
23
23
  render html: html.html_safe, layout: true
24
24
  end
25
25
 
@@ -40,7 +40,7 @@ module SuperSettings
40
40
 
41
41
  # API endpoint for updating settings. See SuperSettings::RestAPI for details.
42
42
  def update
43
- changed_by = Configuration.instance.controller.changed_by(self)
43
+ changed_by = SuperSettings.configuration.controller.changed_by(self)
44
44
  result = SuperSettings::RestAPI.update(params[:settings], changed_by)
45
45
  if result[:success]
46
46
  render json: result
@@ -17,8 +17,6 @@ module SuperSettings
17
17
  end
18
18
 
19
19
  if defined?(Sidekiq.server?) && Sidekiq.server?
20
- require_relative "context/sidekiq_middleware"
21
-
22
20
  Sidekiq.configure_server do |sidekiq_config|
23
21
  sidekiq_config.server_middleware do |chain|
24
22
  chain.prepend(SuperSettings::Context::SidekiqMiddleware)
@@ -29,7 +27,7 @@ module SuperSettings
29
27
 
30
28
  config.after_initialize do
31
29
  # Call the deferred initialization block.
32
- configuration = Configuration.instance
30
+ configuration = SuperSettings.configuration
33
31
  configuration.call
34
32
 
35
33
  SuperSettings.refresh_interval = configuration.refresh_interval unless configuration.refresh_interval.nil?
@@ -27,7 +27,7 @@ module SuperSettings
27
27
  def changed_by_display
28
28
  return changed_by if changed_by.nil?
29
29
 
30
- display_proc = Configuration.instance.model.changed_by_display
30
+ display_proc = SuperSettings.configuration.model.changed_by_display
31
31
  if display_proc && !changed_by.nil?
32
32
  display_proc.call(changed_by) || changed_by
33
33
  else
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+
6
+ module SuperSettings
7
+ # This is a simple HTTP client that is used to communicate with the REST API. It
8
+ # will keep the connection alive and reuse it on subsequent requests.
9
+ class HttpClient
10
+ DEFAULT_HEADERS = {"Accept" => "application/json"}.freeze
11
+ DEFAULT_TIMEOUT = 5.0
12
+ KEEP_ALIVE_TIMEOUT = 60
13
+
14
+ class Error < StandardError
15
+ end
16
+
17
+ class NotFoundError < Error
18
+ end
19
+
20
+ class InvalidRecordError < Error
21
+ attr_reader :errors
22
+
23
+ def initialize(message, errors:)
24
+ super(message)
25
+ @errors = errors
26
+ end
27
+ end
28
+
29
+ def initialize(base_url, headers: nil, params: nil, timeout: nil, user: nil, password: nil)
30
+ base_url = "#{base_url}/" unless base_url.end_with?("/")
31
+ @base_uri = URI(base_url)
32
+ @base_uri.query = query_string(params) if params
33
+ @headers = headers ? DEFAULT_HEADERS.merge(headers) : DEFAULT_HEADERS
34
+ @timeout = timeout || DEFAULT_TIMEOUT
35
+ @user = user
36
+ @password = password
37
+ @mutex = Mutex.new
38
+ @connections = []
39
+ end
40
+
41
+ def get(path, params = nil)
42
+ request = Net::HTTP::Get.new(request_uri(path, params))
43
+ send_request(request)
44
+ end
45
+
46
+ def post(path, params = nil)
47
+ request = Net::HTTP::Post.new(request_uri(path))
48
+ request.body = JSON.dump(params) if params
49
+ send_request(request)
50
+ end
51
+
52
+ private
53
+
54
+ def send_request(request)
55
+ set_headers(request)
56
+ response_payload = nil
57
+ attempts = 0
58
+
59
+ with_connection do |http|
60
+ http.start unless http.started?
61
+ response = http.request(request)
62
+
63
+ begin
64
+ response.value # raises exception unless response is a success
65
+ response_payload = JSON.parse(response.body)
66
+ rescue Net::ProtocolError
67
+ if [404, 410].include?(response.code.to_i)
68
+ raise NotFoundError.new("#{response.code} #{response.message}")
69
+ elsif response.code.to_i == 422
70
+ raise InvalidRecordError.new("#{response.code} #{response.message}", errors: JSON.parse(response.body)["errors"])
71
+ else
72
+ raise Error.new("#{response.code} #{response.message}")
73
+ end
74
+ rescue JSON::JSONError => e
75
+ raise Error.new(e.message)
76
+ end
77
+ rescue IOError, Errno::ECONNRESET => connection_error
78
+ attempts += 1
79
+ retry if attempts <= 1
80
+ raise connection_error
81
+ end
82
+
83
+ response_payload
84
+ end
85
+
86
+ def with_connection(&block)
87
+ http = pop_connection
88
+ begin
89
+ response = yield(http)
90
+ return_connection(http)
91
+ response
92
+ rescue => e
93
+ begin
94
+ http.finish if http.started?
95
+ rescue IOError
96
+ end
97
+ raise e
98
+ end
99
+ end
100
+
101
+ def pop_connection
102
+ http = nil
103
+ @mutex.synchronize do
104
+ http = @connections.pop
105
+ end
106
+ http = nil unless http&.started?
107
+ http ||= new_connection
108
+ http
109
+ end
110
+
111
+ def return_connection(http)
112
+ @mutex.synchronize do
113
+ if @connections.empty?
114
+ @connections.push(http)
115
+ http = nil
116
+ end
117
+ end
118
+
119
+ if http
120
+ begin
121
+ http.finish if http.started?
122
+ rescue IOError
123
+ end
124
+ end
125
+ end
126
+
127
+ def new_connection
128
+ http = Net::HTTP.new(@base_uri.host, @base_uri.port || @base_uri.inferred_port)
129
+ http.use_ssl = @base_uri.scheme == "https"
130
+ http.open_timeout = @timeout
131
+ http.read_timeout = @timeout
132
+ http.write_timeout = @timeout
133
+ http.keep_alive_timeout = KEEP_ALIVE_TIMEOUT
134
+ http
135
+ end
136
+
137
+ def set_headers(request)
138
+ @headers.each do |name, value|
139
+ name = name.to_s
140
+ values = Array(value)
141
+ request[name] = values[0].to_s
142
+ values[1, values.length].each do |val|
143
+ request.add_field(name, val.to_s)
144
+ end
145
+ end
146
+ end
147
+
148
+ def request_uri(path, params = nil)
149
+ uri = URI.join(@base_uri, path.delete_prefix("/"))
150
+ if (params && !params.empty?) || (@base_uri.query && !@base_uri.query.empty?)
151
+ uri.query = [uri.query, query_string(params)].join("&")
152
+ end
153
+ uri
154
+ end
155
+
156
+ def query_string(params)
157
+ q = []
158
+ q << @base_uri.query unless @base_uri.query.to_s.empty?
159
+ params&.each do |name, value|
160
+ q << "#{URI.encode_www_form_component(name.to_s)}=#{URI.encode_www_form_component(value.to_s)}"
161
+ end
162
+ q.join("&")
163
+ end
164
+ end
165
+ end
@@ -240,21 +240,6 @@ module SuperSettings
240
240
  end
241
241
  end
242
242
 
243
- # Recusive method for creating a nested hash from delimited keys.
244
- def set_nested_hash_value(hash, key, value, current_depth, delimiter:, max_depth:)
245
- key, sub_key = ((max_depth && current_depth < max_depth) ? [key, nil] : key.split(delimiter, 2))
246
- if sub_key
247
- sub_hash = hash[key]
248
- unless sub_hash.is_a?(Hash)
249
- sub_hash = {}
250
- hash[key] = sub_hash
251
- end
252
- set_nested_hash_value(sub_hash, sub_key, value, current_depth + 1, delimiter: delimiter, max_depth: max_depth)
253
- else
254
- hash[key] = value
255
- end
256
- end
257
-
258
243
  # Recursively freeze a hash.
259
244
  def deep_freeze_hash(hash)
260
245
  hash.each_value do |value|