unified_settings 0.1.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 +7 -0
- data/.rubocop.yml +53 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +129 -0
- data/LICENSE.txt +21 -0
- data/README.md +249 -0
- data/Rakefile +16 -0
- data/lib/unified_settings/coercer.rb +105 -0
- data/lib/unified_settings/handlers/base.rb +57 -0
- data/lib/unified_settings/handlers/config_gem.rb +51 -0
- data/lib/unified_settings/handlers/constants.rb +74 -0
- data/lib/unified_settings/handlers/credentials.rb +42 -0
- data/lib/unified_settings/handlers/env.rb +47 -0
- data/lib/unified_settings/settings.rb +129 -0
- data/lib/unified_settings/unified_settings.rb +49 -0
- data/lib/unified_settings/version.rb +5 -0
- data/lib/unified_settings.rb +19 -0
- data/unified_settings.gemspec +46 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cc523c80caf34cfc9c862df454f01a434518c149e94ef342d354c7f5af4e40fe
|
4
|
+
data.tar.gz: 42e5d82f9d6b6cb4ee87b49c0ef97c987176bbb688a4f8b2492f6f4f353ff509
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d7ead0fae11ea90725007c1b0c8d80c309ad12fe2e25b86181355502efca95631992ffbbee21c8416228b65937238bae242c76ddb170771f5169ca229d3567ec
|
7
|
+
data.tar.gz: 96a5d214ba297bee6f0cbc0e8309a34ab30a85ad4d87c91d8941ab06b17954341d6b4bbb0416850f327f642922b32bc3b352908520d778fc9296a571dc78a7d7
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require:
|
2
|
+
- 'rubocop-performance'
|
3
|
+
- 'rubocop-rails'
|
4
|
+
- 'rubocop-minitest'
|
5
|
+
- 'rubocop-rake'
|
6
|
+
|
7
|
+
AllCops:
|
8
|
+
NewCops: enable
|
9
|
+
# Don't run rubocop on these files/directories
|
10
|
+
Exclude:
|
11
|
+
- '**/templates/**/*'
|
12
|
+
- '**/vendor/**/*'
|
13
|
+
- 'lib/templates/**/*'
|
14
|
+
- 'db/**/*'
|
15
|
+
- 'config/**/*'
|
16
|
+
- 'vendor/**/*'
|
17
|
+
- 'bin/**/*'
|
18
|
+
|
19
|
+
Layout/LineLength:
|
20
|
+
Max: 80
|
21
|
+
Exclude:
|
22
|
+
- 'unified_settings.gemspec'
|
23
|
+
|
24
|
+
Metrics/AbcSize:
|
25
|
+
Max: 30
|
26
|
+
Exclude:
|
27
|
+
- 'test/**/*'
|
28
|
+
|
29
|
+
Metrics/BlockLength:
|
30
|
+
Max: 40
|
31
|
+
Exclude:
|
32
|
+
- 'unified_settings.gemspec'
|
33
|
+
- 'test/**/*'
|
34
|
+
|
35
|
+
Metrics/ClassLength:
|
36
|
+
Max: 150
|
37
|
+
Exclude:
|
38
|
+
- 'test/**/*'
|
39
|
+
|
40
|
+
Metrics/MethodLength:
|
41
|
+
Max: 35
|
42
|
+
Exclude:
|
43
|
+
- 'test/**/*'
|
44
|
+
|
45
|
+
Metrics/ModuleLength:
|
46
|
+
Max: 100
|
47
|
+
|
48
|
+
Minitest/MultipleAssertions:
|
49
|
+
Max: 10
|
50
|
+
|
51
|
+
Rails/RefuteMethods:
|
52
|
+
Exclude:
|
53
|
+
- 'test/**/*'
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
unified_settings
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-3.2.2
|
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in unified_settings.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem 'bundler', '~> 2.3'
|
9
|
+
gem 'bundler-audit', '>= 0'
|
10
|
+
gem 'config', '>= 3.0'
|
11
|
+
gem 'minitest', '~> 5.0'
|
12
|
+
gem 'rake', '~> 13.0'
|
13
|
+
gem 'rubocop', '~> 1.21'
|
14
|
+
gem 'rubocop-minitest', '>= 0'
|
15
|
+
gem 'rubocop-performance', '>= 0'
|
16
|
+
gem 'rubocop-rails', '>= 0'
|
17
|
+
gem 'rubocop-rake', '>= 0'
|
18
|
+
gem 'ruby_audit', '>= 0'
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
unified_settings (0.1.0)
|
5
|
+
activerecord (> 4.2.0)
|
6
|
+
activesupport (> 4.2.0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activemodel (7.0.6)
|
12
|
+
activesupport (= 7.0.6)
|
13
|
+
activerecord (7.0.6)
|
14
|
+
activemodel (= 7.0.6)
|
15
|
+
activesupport (= 7.0.6)
|
16
|
+
activesupport (7.0.6)
|
17
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
|
+
i18n (>= 1.6, < 2)
|
19
|
+
minitest (>= 5.1)
|
20
|
+
tzinfo (~> 2.0)
|
21
|
+
ast (2.4.2)
|
22
|
+
bundler-audit (0.9.1)
|
23
|
+
bundler (>= 1.2.0, < 3)
|
24
|
+
thor (~> 1.0)
|
25
|
+
concurrent-ruby (1.2.2)
|
26
|
+
config (4.2.1)
|
27
|
+
deep_merge (~> 1.2, >= 1.2.1)
|
28
|
+
dry-validation (~> 1.0, >= 1.0.0)
|
29
|
+
deep_merge (1.2.2)
|
30
|
+
dry-configurable (1.0.1)
|
31
|
+
dry-core (~> 1.0, < 2)
|
32
|
+
zeitwerk (~> 2.6)
|
33
|
+
dry-core (1.0.0)
|
34
|
+
concurrent-ruby (~> 1.0)
|
35
|
+
zeitwerk (~> 2.6)
|
36
|
+
dry-inflector (1.0.0)
|
37
|
+
dry-initializer (3.1.1)
|
38
|
+
dry-logic (1.5.0)
|
39
|
+
concurrent-ruby (~> 1.0)
|
40
|
+
dry-core (~> 1.0, < 2)
|
41
|
+
zeitwerk (~> 2.6)
|
42
|
+
dry-schema (1.13.2)
|
43
|
+
concurrent-ruby (~> 1.0)
|
44
|
+
dry-configurable (~> 1.0, >= 1.0.1)
|
45
|
+
dry-core (~> 1.0, < 2)
|
46
|
+
dry-initializer (~> 3.0)
|
47
|
+
dry-logic (>= 1.4, < 2)
|
48
|
+
dry-types (>= 1.7, < 2)
|
49
|
+
zeitwerk (~> 2.6)
|
50
|
+
dry-types (1.7.1)
|
51
|
+
concurrent-ruby (~> 1.0)
|
52
|
+
dry-core (~> 1.0)
|
53
|
+
dry-inflector (~> 1.0)
|
54
|
+
dry-logic (~> 1.4)
|
55
|
+
zeitwerk (~> 2.6)
|
56
|
+
dry-validation (1.10.0)
|
57
|
+
concurrent-ruby (~> 1.0)
|
58
|
+
dry-core (~> 1.0, < 2)
|
59
|
+
dry-initializer (~> 3.0)
|
60
|
+
dry-schema (>= 1.12, < 2)
|
61
|
+
zeitwerk (~> 2.6)
|
62
|
+
i18n (1.14.1)
|
63
|
+
concurrent-ruby (~> 1.0)
|
64
|
+
json (2.6.3)
|
65
|
+
language_server-protocol (3.17.0.3)
|
66
|
+
minitest (5.18.1)
|
67
|
+
parallel (1.23.0)
|
68
|
+
parser (3.2.2.3)
|
69
|
+
ast (~> 2.4.1)
|
70
|
+
racc
|
71
|
+
racc (1.7.1)
|
72
|
+
rack (2.2.7)
|
73
|
+
rainbow (3.1.1)
|
74
|
+
rake (13.0.6)
|
75
|
+
regexp_parser (2.8.1)
|
76
|
+
rexml (3.2.5)
|
77
|
+
rubocop (1.54.2)
|
78
|
+
json (~> 2.3)
|
79
|
+
language_server-protocol (>= 3.17.0)
|
80
|
+
parallel (~> 1.10)
|
81
|
+
parser (>= 3.2.2.3)
|
82
|
+
rainbow (>= 2.2.2, < 4.0)
|
83
|
+
regexp_parser (>= 1.8, < 3.0)
|
84
|
+
rexml (>= 3.2.5, < 4.0)
|
85
|
+
rubocop-ast (>= 1.28.0, < 2.0)
|
86
|
+
ruby-progressbar (~> 1.7)
|
87
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
88
|
+
rubocop-ast (1.29.0)
|
89
|
+
parser (>= 3.2.1.0)
|
90
|
+
rubocop-minitest (0.31.0)
|
91
|
+
rubocop (>= 1.39, < 2.0)
|
92
|
+
rubocop-performance (1.18.0)
|
93
|
+
rubocop (>= 1.7.0, < 2.0)
|
94
|
+
rubocop-ast (>= 0.4.0)
|
95
|
+
rubocop-rails (2.20.2)
|
96
|
+
activesupport (>= 4.2.0)
|
97
|
+
rack (>= 1.1)
|
98
|
+
rubocop (>= 1.33.0, < 2.0)
|
99
|
+
rubocop-rake (0.6.0)
|
100
|
+
rubocop (~> 1.0)
|
101
|
+
ruby-progressbar (1.13.0)
|
102
|
+
ruby_audit (2.2.0)
|
103
|
+
bundler-audit (~> 0.9.0)
|
104
|
+
thor (1.2.2)
|
105
|
+
tzinfo (2.0.6)
|
106
|
+
concurrent-ruby (~> 1.0)
|
107
|
+
unicode-display_width (2.4.2)
|
108
|
+
zeitwerk (2.6.8)
|
109
|
+
|
110
|
+
PLATFORMS
|
111
|
+
x86_64-darwin-22
|
112
|
+
x86_64-linux
|
113
|
+
|
114
|
+
DEPENDENCIES
|
115
|
+
bundler (~> 2.3)
|
116
|
+
bundler-audit
|
117
|
+
config (>= 3.0)
|
118
|
+
minitest (~> 5.0)
|
119
|
+
rake (~> 13.0)
|
120
|
+
rubocop (~> 1.21)
|
121
|
+
rubocop-minitest
|
122
|
+
rubocop-performance
|
123
|
+
rubocop-rails
|
124
|
+
rubocop-rake
|
125
|
+
ruby_audit
|
126
|
+
unified_settings!
|
127
|
+
|
128
|
+
BUNDLED WITH
|
129
|
+
2.4.10
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 TODO: Write your name
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
# UnifiedSettings
|
2
|
+
|
3
|
+
[](https://dl.circleci.com/status-badge/redirect/gh/prschmid/unified_settings/tree/main)
|
4
|
+
[](https://badge.fury.io/rb/unified_settings)
|
5
|
+
|
6
|
+
A simple and unified way to get any setting in your code regardless of where it is defined.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Install the gem and add to the application's Gemfile by executing:
|
11
|
+
|
12
|
+
$ bundle add unified_settings
|
13
|
+
|
14
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
15
|
+
|
16
|
+
$ gem install unified_settings
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
### Basic Usage
|
21
|
+
|
22
|
+
To get the value of `some_setting`, simply do:
|
23
|
+
|
24
|
+
UnifiedSettings.get('some_setting')
|
25
|
+
|
26
|
+
Or to check if the key has been defined:
|
27
|
+
|
28
|
+
UnifiedSettings.defined?('some_setting')
|
29
|
+
|
30
|
+
This will search the following locations in the following order:
|
31
|
+
1. ENV
|
32
|
+
2. Rails Credentials (if using Rails)
|
33
|
+
4. constants
|
34
|
+
|
35
|
+
When using `UnifiedSettings.get('some_setting')`, the *first* setting that matches the provided key will be returned. As such, even if the same key is defined in ENV, Credentials and as a constant, it will return the value defined in ENV as that is what will take precedence.
|
36
|
+
|
37
|
+
If one wants to change the search order, or limit what is searched, one should provided the handlers to search explicity. For example:
|
38
|
+
|
39
|
+
UnifiedSettings.get(
|
40
|
+
'some_setting',
|
41
|
+
handlers: [
|
42
|
+
UnifiedSettings::Handlers::Credentials,
|
43
|
+
UnifiedSettings::Handlers::Env
|
44
|
+
]
|
45
|
+
)
|
46
|
+
|
47
|
+
This will first search the Credentials, then ENV, and completely ignore any values that might be defined in Settings or as a constant. If there are any other places that need to be searched, a new custom handler can be created and then provided. To do this, just create a class that inherits from `UnifiedSettings::Handlers::Base` and add it to the list of handlers. For details on how to configure `UnifiedSettings` to use differnet handlers by default, see the Configuration section below.
|
48
|
+
|
49
|
+
Note, by default the search is also done in a "case insensitive" manner. This means, for each setting source, it will first try to match the key as provied (e.g. `Some_Setting`). If nothing is found it will then attempt an upper case version of the key (`SOME_SETTING`) and a lower case version (`some_setting`). If this is not desired, one can do
|
50
|
+
|
51
|
+
UnifiedSettings.get('some_setting', case_sensitive: true)
|
52
|
+
|
53
|
+
### Predefined Handlers
|
54
|
+
|
55
|
+
Build in are 4 pre-defined handlers that look for setting keys in predfined locattions
|
56
|
+
|
57
|
+
* `UnifiedSettings::Handlers::ConfigGem`: Look for settings via the interface provied by [Config](https://github.com/rubyconfig/config)
|
58
|
+
* `UnifiedSettings::Handlers::Constants`: Look for a setting defined by a constant
|
59
|
+
* `UnifiedSettings::Handlers::Credentials`: Look for a setting in a Rails Credentials file
|
60
|
+
* `UnifiedSettings::Handlers::Env`: Look for a setting defined in `ENV`
|
61
|
+
|
62
|
+
### Coercing strings to objects
|
63
|
+
|
64
|
+
In many instances it is only possible to define strings as the value of a setting. For example, when setting an `ENV` var `SUPER_IMPORTANT_IDS` with the value of `1,2,3,4,5`, what the user really wants is a list of numbers, and not a comma separated string. As such, `UnifiedSettings` will automatically try to coerce things that look like arrays into arrays. Furthermore, it will convert things that look like floats to floats, ints to ints, booleans to booleans, etc. This way one does not have to worry about converting the values of settings to be easily used within the application. For example, is `some_setting` had the value of `' string, tRue, false,1, 2.2, NiL '`, the following be returned `['string', true, false, 1, 2.2, nil]`.
|
65
|
+
|
66
|
+
There are times when coercion is not desired (e.g. for things like long passcodes that may look like arrays since they may contain commas/numbers, etc.). For situations like this, simply disable the coercion.
|
67
|
+
|
68
|
+
UnifiedSettings.get(`some_setting`, coerce: false)
|
69
|
+
### Handling Missing Keys
|
70
|
+
|
71
|
+
#### Setting a Default Value
|
72
|
+
|
73
|
+
In many cases there might be a default value that should be provided if a key is missing. This can be supplied as follows:
|
74
|
+
|
75
|
+
UnifiedSettings.get('some_setting', default: 'some_value')
|
76
|
+
|
77
|
+
#### Logging/Raising Error
|
78
|
+
|
79
|
+
By default, when there is a missing key, an message will be logged with the severity of `error`. Depending on the situation one may want a different behavior. As such, different error handlers can be passed to meet those needs. The following handlers are predefined: `:log_debug`, `:log_info`, `:log_warn`, `:log_error`, `:log_fatal`, `:raise`. For example:
|
80
|
+
|
81
|
+
UnifiedSettings.get('some_setting', on_missing_key: :raise)
|
82
|
+
|
83
|
+
If these do not suffice, one can pass an anonymous function that takes `key` as a parameter. For example:
|
84
|
+
|
85
|
+
UnifiedSettings.get(
|
86
|
+
'some_setting',
|
87
|
+
on_missing_key: ->(key) { puts "Something is wrong with #{key}" }
|
88
|
+
)
|
89
|
+
|
90
|
+
Furthermore, one can pass a list of hanlders, so that multiple things can happen. For example
|
91
|
+
|
92
|
+
UnifiedSettings.get(
|
93
|
+
'some_setting',
|
94
|
+
on_missing_key: [
|
95
|
+
:log_fatal,
|
96
|
+
->(key) { puts "Something is wrong with #{key}" },
|
97
|
+
:raise
|
98
|
+
]
|
99
|
+
)
|
100
|
+
|
101
|
+
This will run the handlers in the order that they were defined.
|
102
|
+
|
103
|
+
### Nested Settings
|
104
|
+
|
105
|
+
In many cases it is advantageous to have settings nested when defining them in, for example, the Rails Credentials file. For example, if we had the following defined in the Credentials file:
|
106
|
+
|
107
|
+
aws:
|
108
|
+
client_id: SOME_ID
|
109
|
+
client_secret: SOME_SECRET
|
110
|
+
|
111
|
+
Then the corresponding setting keys would be:
|
112
|
+
|
113
|
+
aws.client_id
|
114
|
+
aws.client_secret
|
115
|
+
|
116
|
+
As you can see, for nested settings, the convention to be used is to separate the elements using a '.'
|
117
|
+
|
118
|
+
The separator for ENV variables follow a slightly different as there can be issues when using a `.` in ENV variable names. As such the convention used in `UnifiedSettings` is modeled after the [Config gem](https://github.com/rubyconfig/config). When defining a value via an environment variable, the separator is a double underscore (`__`). Continuing with the above example, the ENV keys would be
|
119
|
+
|
120
|
+
AWS__CLIENT_ID
|
121
|
+
AWS__CLIENT_SECRET
|
122
|
+
|
123
|
+
This means, if you do
|
124
|
+
|
125
|
+
UnifiedSettings.get('aws.client_id')
|
126
|
+
|
127
|
+
it will look for an ENV var of the form `AWS__CLIENT_ID`.
|
128
|
+
|
129
|
+
## Configuration
|
130
|
+
|
131
|
+
Most of the settings that can be set at the individual call level can also be globally configured.
|
132
|
+
|
133
|
+
IMPORTANT FOR RAILS: If one is planning on using `UnifiedSettings` during the initialization for other Rails gems, then this configuration MUST be done before we run `application.initialize!`.
|
134
|
+
|
135
|
+
### Configuring the Handlers
|
136
|
+
|
137
|
+
By default, the search order for a settings is:
|
138
|
+
1. ENV
|
139
|
+
2. Rails Credentials (if using Rails)
|
140
|
+
4. constants
|
141
|
+
|
142
|
+
For example, if one is also using the [Config gem](https://github.com/rubyconfig/config), and would also like to search for settings there, one can add that handler.
|
143
|
+
|
144
|
+
UnifiedSettings.configure do |config|
|
145
|
+
config.handlers = [
|
146
|
+
UnifiedSettings::Handlers::Env,
|
147
|
+
UnifiedSettings::Handlers::Credentials,
|
148
|
+
UnifiedSettings::Handlers::ConfigGem,
|
149
|
+
UnifiedSettings::Handlers::Constants
|
150
|
+
]
|
151
|
+
end
|
152
|
+
|
153
|
+
If one needs to supply some extra parameters when initializing the handler, for example if a non-default constant is used for the `Config` gem, one needs only to pass a hash as follows:
|
154
|
+
|
155
|
+
UnifiedSettings.configure do |config|
|
156
|
+
config.handlers = [
|
157
|
+
UnifiedSettings::Handlers::Env,
|
158
|
+
UnifiedSettings::Handlers::Credentials,
|
159
|
+
{
|
160
|
+
handler: UnifiedSettings::Handlers::ConfigGem,
|
161
|
+
params: {
|
162
|
+
const_name: 'ConfigSettings'
|
163
|
+
}
|
164
|
+
},
|
165
|
+
UnifiedSettings::Handlers::Constants
|
166
|
+
]
|
167
|
+
end
|
168
|
+
|
169
|
+
### All Other Options
|
170
|
+
|
171
|
+
Instead of going through all the various options, here is a fully worked out example with inline comments.
|
172
|
+
|
173
|
+
```
|
174
|
+
UnifiedSettings.configure do |config|
|
175
|
+
config.handlers = [
|
176
|
+
UnifiedSettings::Handlers::Env,
|
177
|
+
UnifiedSettings::Handlers::Credentials,
|
178
|
+
{
|
179
|
+
handler: UnifiedSettings::Handlers::ConfigGem,
|
180
|
+
params: {
|
181
|
+
const_name: 'Settings'
|
182
|
+
}
|
183
|
+
},
|
184
|
+
UnifiedSettings::Handlers::Constants
|
185
|
+
]
|
186
|
+
|
187
|
+
# Whether or not keys should be case sensitive. For example, let's assume the
|
188
|
+
# key foo.bar.baz is defined in one of the locations the handlers are
|
189
|
+
# configured to search. If we use case_sensitive = false then any of the
|
190
|
+
# following examples will match
|
191
|
+
# UnifiedSettings.get('FOO.BAR.BAZ')
|
192
|
+
# UnifiedSettings.get('foo.bar.bar')
|
193
|
+
# UnifiedSettings.get('Foo.BaR.baZ')
|
194
|
+
# If we set set case_sensitive = true, then only if the case exactly matches
|
195
|
+
# the key is a result returned.
|
196
|
+
# Default is: config.case_sensitive = false
|
197
|
+
config.case_sensitive = false
|
198
|
+
|
199
|
+
# This can be any of the following pre-defined handlers:
|
200
|
+
# :log_debug, :log_info, :log_warn, :log_error, :log_fatal, :raise
|
201
|
+
# or you can pass an anonymous function that takes `key` as a parameter. E.g.
|
202
|
+
# config.on_missing_key = ->(key) { puts "Something is wrong with #{key}" }
|
203
|
+
# If you need multiple things to happen, simply use an Array here. E.g.
|
204
|
+
# config.on_missing_key = [
|
205
|
+
# :log_fatal,
|
206
|
+
# ->(key) { puts "Something is wrong with #{key}" },
|
207
|
+
# :raise
|
208
|
+
# ]
|
209
|
+
# This will run the handlers in the order that they were defined.
|
210
|
+
#
|
211
|
+
# Default is: config.on_missing_key = :log_error
|
212
|
+
config.on_missing_key = :log_error
|
213
|
+
|
214
|
+
# The value that should be returned if no key was found. This can be a set
|
215
|
+
# to a particular value (e.g. `nil`, or `{}`), or can be an anonymous function
|
216
|
+
# that takes `key` as a parameter. E.g.
|
217
|
+
# config.default_value = ->(key) { key.starts_with?('a') ? 'AA' : 'ZZ'}
|
218
|
+
#
|
219
|
+
# Default is: config.default_value = nil
|
220
|
+
config.default_value = nil
|
221
|
+
|
222
|
+
# The types that should be coerced from strings to their Ruby type.
|
223
|
+
# (e.g. "true" to the boolean true or "1.2" to the float 1.2).
|
224
|
+
# Default is: config.coercions = %i[nil boolean integer float]
|
225
|
+
config.coercions = %i[nil boolean integer float]
|
226
|
+
|
227
|
+
# Whether or not to coerce strings that look like arrays to arrays.
|
228
|
+
# E.g. "a, B, true,1 " would be coerced become to ["a", "B", true, 1]
|
229
|
+
# Defualt is:
|
230
|
+
# config.coerce_arrays = true
|
231
|
+
# config.coerce_array_separator = ','
|
232
|
+
config.coerce_arrays = true
|
233
|
+
config.coerce_array_separator = ','
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
## Development
|
238
|
+
|
239
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
240
|
+
|
241
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
242
|
+
|
243
|
+
## Contributing
|
244
|
+
|
245
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/prschmid/unified_settings.
|
246
|
+
|
247
|
+
## License
|
248
|
+
|
249
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << 'test'
|
8
|
+
t.libs << 'lib'
|
9
|
+
t.test_files = FileList['test/**/*_test.rb']
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'rubocop/rake_task'
|
13
|
+
|
14
|
+
RuboCop::RakeTask.new
|
15
|
+
|
16
|
+
task default: %i[test rubocop]
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UnifiedSettings
|
4
|
+
#
|
5
|
+
# This will take string values and coerce them to the approrpiate ruby
|
6
|
+
# objects (e.g. "true" to the boolean true or "1.2" to the float 1.2)
|
7
|
+
#
|
8
|
+
class Coercer
|
9
|
+
def initialize(
|
10
|
+
coercions: %i[nil boolean integer float],
|
11
|
+
coerce_arrays: true,
|
12
|
+
array_separator: ','
|
13
|
+
)
|
14
|
+
@coercions = coercions
|
15
|
+
@coerce_arrays = coerce_arrays
|
16
|
+
@array_separator = array_separator
|
17
|
+
end
|
18
|
+
|
19
|
+
def coerce(value)
|
20
|
+
return value unless value
|
21
|
+
|
22
|
+
# If it's already been cast to something other than a string, just
|
23
|
+
# return what it is.
|
24
|
+
return value unless value.is_a?(String)
|
25
|
+
|
26
|
+
stripped_value = value.strip
|
27
|
+
|
28
|
+
coerced_value, is_array = coerce_to_array(stripped_value)
|
29
|
+
if is_array
|
30
|
+
coerced_value.map do |array_value|
|
31
|
+
array_value = array_value.strip
|
32
|
+
coerced_value, did_coerce = coerce_value(array_value)
|
33
|
+
did_coerce ? coerced_value : array_value
|
34
|
+
end
|
35
|
+
else
|
36
|
+
coerced_value, did_coerce = coerce_value(stripped_value)
|
37
|
+
did_coerce ? coerced_value : stripped_value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def coerce_value(value)
|
44
|
+
return [value, false] unless value
|
45
|
+
|
46
|
+
coerced_value, did_coerce = coerce_to_nil(value)
|
47
|
+
return [coerced_value, did_coerce] if did_coerce
|
48
|
+
|
49
|
+
coerced_value, did_coerce = coerce_to_boolean(value)
|
50
|
+
return [coerced_value, did_coerce] if did_coerce
|
51
|
+
|
52
|
+
coerced_value, did_coerce = coerce_to_integer(value)
|
53
|
+
return [coerced_value, did_coerce] if did_coerce
|
54
|
+
|
55
|
+
coerced_value, did_coerce = coerce_to_float(value)
|
56
|
+
return [coerced_value, did_coerce] if did_coerce
|
57
|
+
|
58
|
+
[value, false]
|
59
|
+
end
|
60
|
+
|
61
|
+
def coerce_to_array(value)
|
62
|
+
return [value, false] unless @coerce_arrays
|
63
|
+
return [value, false] unless value.include?(@array_separator)
|
64
|
+
|
65
|
+
[value.split(@array_separator), true]
|
66
|
+
end
|
67
|
+
|
68
|
+
def coerce_to_nil(value)
|
69
|
+
return [value, false] unless @coercions.include?(:nil)
|
70
|
+
return [nil, true] if value.casecmp('nil').zero?
|
71
|
+
|
72
|
+
[value, false]
|
73
|
+
end
|
74
|
+
|
75
|
+
def coerce_to_boolean(value)
|
76
|
+
return [value, false] unless @coercions.include?(:boolean)
|
77
|
+
return [true, true] if value.casecmp('true').zero?
|
78
|
+
return [false, true] if value.casecmp('false').zero?
|
79
|
+
|
80
|
+
[value, false]
|
81
|
+
end
|
82
|
+
|
83
|
+
def coerce_to_integer(value)
|
84
|
+
return [value, false] unless @coercions.include?(:integer)
|
85
|
+
|
86
|
+
begin
|
87
|
+
return [Integer(value), true]
|
88
|
+
rescue ArgumentError # rubocop:disable Lint/SuppressedException
|
89
|
+
end
|
90
|
+
|
91
|
+
[value, false]
|
92
|
+
end
|
93
|
+
|
94
|
+
def coerce_to_float(value)
|
95
|
+
return [value, false] unless @coercions.include?(:float)
|
96
|
+
|
97
|
+
begin
|
98
|
+
return [Float(value), true]
|
99
|
+
rescue ArgumentError # rubocop:disable Lint/SuppressedException
|
100
|
+
end
|
101
|
+
|
102
|
+
[value, false]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UnifiedSettings
|
4
|
+
module Handlers
|
5
|
+
# Base handler for a setting source
|
6
|
+
#
|
7
|
+
# All handers should inherit from this base class and implement the method
|
8
|
+
# def get(key, case_sensitive: nil)
|
9
|
+
class Base
|
10
|
+
KEY_NESTING_SEPARATOR = '.'
|
11
|
+
|
12
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
13
|
+
def get(key, case_sensitive: nil)
|
14
|
+
raise 'Needs to be implemented by subclasss'
|
15
|
+
end
|
16
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def split(val, separator)
|
21
|
+
case val
|
22
|
+
when String
|
23
|
+
val.split(separator)
|
24
|
+
when Symbol
|
25
|
+
val.to_s.split(separator)
|
26
|
+
when Array
|
27
|
+
val
|
28
|
+
else
|
29
|
+
raise 'key must either be a string or an array'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def case_sensitive?(case_sensitive)
|
34
|
+
if case_sensitive.nil?
|
35
|
+
UnifiedSettings.config.case_sensitive
|
36
|
+
else
|
37
|
+
case_sensitive
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_symbol_array(val, separator: KEY_NESTING_SEPARATOR)
|
42
|
+
split(val, separator).map(&:to_sym)
|
43
|
+
end
|
44
|
+
|
45
|
+
def nested_key_exists?(hash, keys)
|
46
|
+
current_level = hash
|
47
|
+
keys.each do |key|
|
48
|
+
return false if current_level.nil?
|
49
|
+
return true if current_level.key?(key)
|
50
|
+
|
51
|
+
current_level = current_level[key]
|
52
|
+
end
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module UnifiedSettings
|
6
|
+
module Handlers
|
7
|
+
# Setting handler for Config gem
|
8
|
+
class ConfigGem < Base
|
9
|
+
# By default the gem makes a `Settings` object available
|
10
|
+
DEFAULT_CONST_NAME = 'Settings'
|
11
|
+
|
12
|
+
def initialize(const_name: nil)
|
13
|
+
super()
|
14
|
+
@const_name = const_name || DEFAULT_CONST_NAME
|
15
|
+
end
|
16
|
+
|
17
|
+
def defined?(key, case_sensitive: nil)
|
18
|
+
key_arr = to_symbol_array(key)
|
19
|
+
case_sensitive = case_sensitive?(case_sensitive)
|
20
|
+
|
21
|
+
return true if nested_key_exists?(setting_obj, key_arr)
|
22
|
+
return false if case_sensitive
|
23
|
+
|
24
|
+
return true if nested_key_exists?(setting_obj, key_arr.map(&:upcase))
|
25
|
+
return true if nested_key_exists?(setting_obj, key_arr.map(&:downcase))
|
26
|
+
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(key, case_sensitive: nil)
|
31
|
+
key_arr = to_symbol_array(key)
|
32
|
+
case_sensitive = case_sensitive?(case_sensitive)
|
33
|
+
|
34
|
+
val = setting_obj.dig(*key_arr)
|
35
|
+
return val if val
|
36
|
+
return nil if case_sensitive
|
37
|
+
|
38
|
+
val = setting_obj.dig(*key_arr.map(&:downcase))
|
39
|
+
return val if val
|
40
|
+
|
41
|
+
setting_obj.dig(*key_arr.map(&:upcase))
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def setting_obj
|
47
|
+
Object.const_get("::#{@const_name}")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module UnifiedSettings
|
6
|
+
module Handlers
|
7
|
+
# Setting handler for Ruby constants
|
8
|
+
class Constants < Base
|
9
|
+
CONSTANT_KEY_NESTING_SEPARATOR = '::'
|
10
|
+
|
11
|
+
def defined?(key, case_sensitive: nil)
|
12
|
+
klass, variable_names = key_to_class_and_variable(
|
13
|
+
key, case_sensitive:
|
14
|
+
)
|
15
|
+
|
16
|
+
variable_names.each do |name|
|
17
|
+
if klass
|
18
|
+
return true if klass.const_get(name)
|
19
|
+
elsif Object.const_get(name)
|
20
|
+
return true
|
21
|
+
end
|
22
|
+
rescue NameError
|
23
|
+
# Ignore if the constant is not defined
|
24
|
+
end
|
25
|
+
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(key, case_sensitive: nil)
|
30
|
+
klass, variable_names = key_to_class_and_variable(
|
31
|
+
key, case_sensitive:
|
32
|
+
)
|
33
|
+
|
34
|
+
variable_names.each do |name|
|
35
|
+
return klass.const_get(name) if klass
|
36
|
+
|
37
|
+
return Object.const_get(name)
|
38
|
+
rescue NameError
|
39
|
+
# Ignore if the constant is not defined
|
40
|
+
end
|
41
|
+
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def key_to_class_and_variable(key, case_sensitive: nil)
|
48
|
+
key_arr = to_symbol_array(key,
|
49
|
+
separator: CONSTANT_KEY_NESTING_SEPARATOR)
|
50
|
+
case_sensitive = case_sensitive?(case_sensitive)
|
51
|
+
|
52
|
+
klass, variable =
|
53
|
+
if key_arr.length > 1
|
54
|
+
[
|
55
|
+
key_arr[0..-2].join(CONSTANT_KEY_NESTING_SEPARATOR)
|
56
|
+
.safe_constantize,
|
57
|
+
key_arr[-1]
|
58
|
+
]
|
59
|
+
else
|
60
|
+
[nil, key_arr[0]]
|
61
|
+
end
|
62
|
+
|
63
|
+
variable_names = if case_sensitive
|
64
|
+
[variable]
|
65
|
+
else
|
66
|
+
[variable, variable.upcase, variable.downcase]
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
[klass, variable_names]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module UnifiedSettings
|
6
|
+
module Handlers
|
7
|
+
# Setting handler for Rails.application.credentials
|
8
|
+
class Credentials < Base
|
9
|
+
def defined?(key, case_sensitive: nil)
|
10
|
+
key_arr = to_symbol_array(key)
|
11
|
+
case_sensitive = case_sensitive?(case_sensitive)
|
12
|
+
|
13
|
+
return true if nested_key_exists?(Rails.application.credentials,
|
14
|
+
key_arr)
|
15
|
+
return false if case_sensitive
|
16
|
+
|
17
|
+
return true if nested_key_exists?(
|
18
|
+
Rails.application.credentials, key_arr.map(&:upcase)
|
19
|
+
)
|
20
|
+
return true if nested_key_exists?(
|
21
|
+
Rails.application.credentials, key_arr.map(&:downcase)
|
22
|
+
)
|
23
|
+
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(key, case_sensitive: nil)
|
28
|
+
key_arr = to_symbol_array(key)
|
29
|
+
case_sensitive = case_sensitive?(case_sensitive)
|
30
|
+
|
31
|
+
val = Rails.application.credentials.dig(*key_arr)
|
32
|
+
return val if val
|
33
|
+
return nil if case_sensitive
|
34
|
+
|
35
|
+
val = Rails.application.credentials.dig(*key_arr.map(&:downcase))
|
36
|
+
return val if val
|
37
|
+
|
38
|
+
Rails.application.credentials.dig(*key_arr.map(&:upcase))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module UnifiedSettings
|
6
|
+
module Handlers
|
7
|
+
# Settings handler for ENV variables
|
8
|
+
class Env < Base
|
9
|
+
ENV_KEY_NESTING_SEPARATOR = '__'
|
10
|
+
|
11
|
+
def defined?(key, case_sensitive: nil)
|
12
|
+
key_arr = to_symbol_array(key)
|
13
|
+
case_sensitive = case_sensitive?(case_sensitive)
|
14
|
+
|
15
|
+
return true if ENV.key?(key_arr.join(ENV_KEY_NESTING_SEPARATOR))
|
16
|
+
return false if case_sensitive
|
17
|
+
|
18
|
+
return true if ENV.key?(
|
19
|
+
key_arr.map(&:upcase).join(ENV_KEY_NESTING_SEPARATOR)
|
20
|
+
)
|
21
|
+
return true if ENV.key?(
|
22
|
+
key_arr.map(&:downcase).join(ENV_KEY_NESTING_SEPARATOR)
|
23
|
+
)
|
24
|
+
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def get(key, case_sensitive: nil)
|
29
|
+
key_arr = to_symbol_array(key)
|
30
|
+
case_sensitive = case_sensitive?(case_sensitive)
|
31
|
+
|
32
|
+
val = ENV.fetch(key_arr.join(ENV_KEY_NESTING_SEPARATOR), nil)
|
33
|
+
return val if val
|
34
|
+
return nil if case_sensitive
|
35
|
+
|
36
|
+
val = ENV.fetch(
|
37
|
+
key_arr.map(&:upcase).join(ENV_KEY_NESTING_SEPARATOR), nil
|
38
|
+
)
|
39
|
+
return val if val
|
40
|
+
|
41
|
+
ENV.fetch(
|
42
|
+
key_arr.map(&:downcase).join(ENV_KEY_NESTING_SEPARATOR), nil
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UnifiedSettings
|
4
|
+
# Settings
|
5
|
+
#
|
6
|
+
# Main interface for getting any of the settings
|
7
|
+
class Settings
|
8
|
+
attr_accessor :handlers
|
9
|
+
|
10
|
+
def initialize(handlers: nil)
|
11
|
+
handlers_config = handlers || UnifiedSettings.config.handlers
|
12
|
+
@handlers = handlers_config.map { |config| initialize_handler(config) }
|
13
|
+
@coercer = Coercer.new(
|
14
|
+
coercions: UnifiedSettings.config.coercions,
|
15
|
+
coerce_arrays: UnifiedSettings.config.coerce_arrays,
|
16
|
+
array_separator: UnifiedSettings.config.coerce_array_separator
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def defined?(key, case_sensitive: nil)
|
21
|
+
return false unless key
|
22
|
+
|
23
|
+
@handlers.each do |handler|
|
24
|
+
return true if handler.defined?(key, case_sensitive:)
|
25
|
+
end
|
26
|
+
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(
|
31
|
+
key, default: NO_DEFAULT, case_sensitive: nil, coerce: true,
|
32
|
+
on_missing_key: nil
|
33
|
+
)
|
34
|
+
return nil unless key
|
35
|
+
|
36
|
+
@handlers.each do |handler|
|
37
|
+
val = handler.get(key, case_sensitive:)
|
38
|
+
unless val.nil?
|
39
|
+
return coerce ? @coercer.coerce(val) : val
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
handle_missing_key(key, default:, on_missing_key:)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def initialize_handler(config)
|
49
|
+
handler, params = if config.respond_to?(:keys)
|
50
|
+
[config[:handler], config[:params]]
|
51
|
+
else
|
52
|
+
[config, nil]
|
53
|
+
end
|
54
|
+
|
55
|
+
case handler
|
56
|
+
when String
|
57
|
+
klass = handler.safe_constantize
|
58
|
+
params.blank? ? klass.new : klass.new(**params)
|
59
|
+
when Class
|
60
|
+
params.blank? ? handler.new : handler.new(**params)
|
61
|
+
when SettingHandler
|
62
|
+
handler
|
63
|
+
else
|
64
|
+
raise 'UnifiedSettings: Unsupported handler. Handlers must be an ' \
|
65
|
+
'array of strings, classes, or instances of ' \
|
66
|
+
'UnifiedSettings::SettingHandler'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def handle_missing_key(key, default: NO_DEFAULT, on_missing_key: nil)
|
71
|
+
handle_on_missing_key(key, on_missing_key:)
|
72
|
+
|
73
|
+
val = if default == NO_DEFAULT
|
74
|
+
UnifiedSettings.config.default_value
|
75
|
+
else
|
76
|
+
default
|
77
|
+
end
|
78
|
+
return val.call(key) if val.respond_to?(:call)
|
79
|
+
|
80
|
+
val
|
81
|
+
end
|
82
|
+
|
83
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
84
|
+
def handle_on_missing_key(key, on_missing_key: nil)
|
85
|
+
actions = on_missing_key || UnifiedSettings.config.on_missing_key
|
86
|
+
actions = [actions] unless actions.is_a?(Array)
|
87
|
+
|
88
|
+
actions.each do |action|
|
89
|
+
if action.respond_to?(:call)
|
90
|
+
action.call(key)
|
91
|
+
next
|
92
|
+
end
|
93
|
+
|
94
|
+
case action
|
95
|
+
when :raise
|
96
|
+
on_missing_key_raise(key)
|
97
|
+
when :log_debug
|
98
|
+
on_missing_key_log(Logger::DEBUG, key)
|
99
|
+
when :log_info
|
100
|
+
on_missing_key_log(Logger::INFO, key)
|
101
|
+
when :log_warn
|
102
|
+
on_missing_key_log(Logger::WARN, key)
|
103
|
+
when :log_error
|
104
|
+
on_missing_key_log(Logger::ERROR, key)
|
105
|
+
when :log_fatal
|
106
|
+
on_missing_key_log(Logger::FATAL, key)
|
107
|
+
else
|
108
|
+
raise "UnifiedSettings: Unknown on_missing_key handler: '#{action}'"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
113
|
+
|
114
|
+
def on_missing_key_raise(key)
|
115
|
+
raise error_message(key)
|
116
|
+
end
|
117
|
+
|
118
|
+
def on_missing_key_log(level, key)
|
119
|
+
::Rails.logger.add(level) { error_message(key) }
|
120
|
+
rescue NoMethodError
|
121
|
+
logger = Logger.new($stdout, Logger::INFO)
|
122
|
+
logger.add(level) { error_message(key) }
|
123
|
+
end
|
124
|
+
|
125
|
+
def error_message(key)
|
126
|
+
"UnifiedSettings: No matches found for '#{key}'"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
# Unified way to get settings
|
6
|
+
module UnifiedSettings
|
7
|
+
include ActiveSupport::Configurable
|
8
|
+
|
9
|
+
NO_DEFAULT = :no_default
|
10
|
+
|
11
|
+
def self.configure
|
12
|
+
# Set the defaults
|
13
|
+
config.handlers = [
|
14
|
+
Handlers::Env, Handlers::Credentials, Handlers::Constants
|
15
|
+
]
|
16
|
+
config.default_value = nil
|
17
|
+
config.case_sensitive = false
|
18
|
+
config.on_missing_key = [:log_error]
|
19
|
+
config.coercions = %i[nil boolean integer float]
|
20
|
+
config.coerce_arrays = true
|
21
|
+
config.coerce_array_separator = ','
|
22
|
+
|
23
|
+
super
|
24
|
+
|
25
|
+
# Create an instance of the settings that can be used
|
26
|
+
@settings = Settings.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.defined?(key, case_sensitive: nil, handlers: nil)
|
30
|
+
settings = handlers.nil? ? @settings : Settings.new(handlers:)
|
31
|
+
settings.defined?(key, case_sensitive:)
|
32
|
+
end
|
33
|
+
|
34
|
+
# rubocop:disable Metrics/ParameterLists
|
35
|
+
def self.get(
|
36
|
+
key, default: NO_DEFAULT, case_sensitive: nil, handlers: nil, coerce: true,
|
37
|
+
on_missing_key: nil
|
38
|
+
)
|
39
|
+
settings = handlers.nil? ? @settings : Settings.new(handlers:)
|
40
|
+
settings.get(
|
41
|
+
key,
|
42
|
+
case_sensitive:,
|
43
|
+
coerce:,
|
44
|
+
on_missing_key:,
|
45
|
+
default:
|
46
|
+
)
|
47
|
+
end
|
48
|
+
# rubocop:enable Metrics/ParameterLists
|
49
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'unified_settings/version'
|
4
|
+
|
5
|
+
directories = [
|
6
|
+
File.join(File.dirname(__FILE__), 'unified_settings'),
|
7
|
+
File.join(File.dirname(__FILE__), 'unified_settings', 'handlers')
|
8
|
+
]
|
9
|
+
|
10
|
+
directories.each do |directory|
|
11
|
+
Dir[File.join(directory, '*.rb')].each do |file|
|
12
|
+
require file
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
ActiveRecord::Base.instance_eval { include UnifiedSettings }
|
17
|
+
if defined?(Rails) && Rails.version.to_i < 4
|
18
|
+
raise 'This version of unified_settings requires Rails 4 or higher'
|
19
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'unified_settings/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'unified_settings'
|
9
|
+
spec.version = UnifiedSettings::VERSION
|
10
|
+
spec.authors = ['Patrick R. Schmid']
|
11
|
+
spec.email = ['prschmid@gmail.com']
|
12
|
+
|
13
|
+
spec.summary = 'A unified way to get settings from different sources.'
|
14
|
+
spec.description = 'A unified way to get settings from different sources.'
|
15
|
+
spec.homepage = 'https://github.com/prschmid/unified_settings'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the
|
19
|
+
# 'allowed_push_host' to allow pushing to a single host or delete this
|
20
|
+
# section to allow pushing to any host.
|
21
|
+
if spec.respond_to?(:metadata)
|
22
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
23
|
+
spec.metadata['source_code_uri'] = 'https://github.com/prschmid/unified_settings'
|
24
|
+
spec.metadata['changelog_uri'] = 'https://github.com/prschmid/unified_settings'
|
25
|
+
else
|
26
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
27
|
+
'public gem pushes.'
|
28
|
+
end
|
29
|
+
|
30
|
+
# Specify which files should be added to the gem when it is released.
|
31
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
32
|
+
spec.files = Dir.chdir(__dir__) do
|
33
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
34
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
35
|
+
end
|
36
|
+
end
|
37
|
+
spec.bindir = 'exe'
|
38
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
39
|
+
spec.require_paths = ['lib']
|
40
|
+
|
41
|
+
spec.required_ruby_version = '>= 3.2'
|
42
|
+
|
43
|
+
spec.add_runtime_dependency('activerecord', '> 4.2.0')
|
44
|
+
spec.add_runtime_dependency('activesupport', '> 4.2.0')
|
45
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: unified_settings
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Patrick R. Schmid
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-07-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 4.2.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 4.2.0
|
41
|
+
description: A unified way to get settings from different sources.
|
42
|
+
email:
|
43
|
+
- prschmid@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".rubocop.yml"
|
49
|
+
- ".ruby-gemset"
|
50
|
+
- ".ruby-version"
|
51
|
+
- Gemfile
|
52
|
+
- Gemfile.lock
|
53
|
+
- LICENSE.txt
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- lib/unified_settings.rb
|
57
|
+
- lib/unified_settings/coercer.rb
|
58
|
+
- lib/unified_settings/handlers/base.rb
|
59
|
+
- lib/unified_settings/handlers/config_gem.rb
|
60
|
+
- lib/unified_settings/handlers/constants.rb
|
61
|
+
- lib/unified_settings/handlers/credentials.rb
|
62
|
+
- lib/unified_settings/handlers/env.rb
|
63
|
+
- lib/unified_settings/settings.rb
|
64
|
+
- lib/unified_settings/unified_settings.rb
|
65
|
+
- lib/unified_settings/version.rb
|
66
|
+
- unified_settings.gemspec
|
67
|
+
homepage: https://github.com/prschmid/unified_settings
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
metadata:
|
71
|
+
homepage_uri: https://github.com/prschmid/unified_settings
|
72
|
+
source_code_uri: https://github.com/prschmid/unified_settings
|
73
|
+
changelog_uri: https://github.com/prschmid/unified_settings
|
74
|
+
rubygems_mfa_required: 'true'
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '3.2'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubygems_version: 3.4.10
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: A unified way to get settings from different sources.
|
94
|
+
test_files: []
|