configurable_from_env 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/LICENSE.txt +21 -0
- data/README.md +109 -0
- data/lib/configurable_from_env/environment_value.rb +64 -0
- data/lib/configurable_from_env/version.rb +3 -0
- data/lib/configurable_from_env.rb +31 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c87398f6f8863cf380719eb8e54029579cbc2941476a97b522b45a256ab996da
|
4
|
+
data.tar.gz: 8cbf8cf83eb5ce937b22b874fecd12e0ea8ed8161944df184cf79b0ec4d6e595
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2dfeb692f554bd8db7c5ac4d2c3c605f0eec120a82ea9565468b4a1b5c0b508c1c5bc498488719cdd5980f08e26d200a11f71c0abf6e6fe41577b6f21f09156e
|
7
|
+
data.tar.gz: ee98578923a3262c77e75eece7a8f7bee05f268738a2b4f81d488507f9ed2ca9c188c10856ec2c20b04a1a660b1df68b36313f63191110eb665aabef08d9a384
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Matt Brictson
|
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,109 @@
|
|
1
|
+
# configurable_from_env
|
2
|
+
|
3
|
+
[](https://rubygems.org/gems/configurable_from_env)
|
4
|
+
[](https://www.ruby-toolbox.com/projects/configurable_from_env)
|
5
|
+
[](https://github.com/mattbrictson/configurable_from_env/actions/workflows/ci.yml)
|
6
|
+
[](https://codeclimate.com/github/mattbrictson/configurable_from_env)
|
7
|
+
|
8
|
+
The `configurable_from_env` gem allows you to define accessors that automatically populate via environment variables. It works by extending Active Support's [`config_accessor`](https://api.rubyonrails.org/classes/ActiveSupport/Configurable/ClassMethods.html#method-i-config_accessor) with a new `:from_env` option.
|
9
|
+
|
10
|
+
> [!NOTE]
|
11
|
+
> This project is experimental. Please open an issue or send me an email and let me know what you think!
|
12
|
+
|
13
|
+
---
|
14
|
+
|
15
|
+
- [Quick start](#quick-start)
|
16
|
+
- [Motivation](#motivation)
|
17
|
+
- [Support](#support)
|
18
|
+
- [License](#license)
|
19
|
+
- [Code of conduct](#code-of-conduct)
|
20
|
+
- [Contribution guide](#contribution-guide)
|
21
|
+
|
22
|
+
## Quick start
|
23
|
+
|
24
|
+
Add the gem to your Gemfile and run `bundle install`:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
gem "configurable_from_env"
|
28
|
+
```
|
29
|
+
|
30
|
+
Include the `ConfigurableFromEnv` mixin and then use `config_accessor` to define configurable attributes that are automatically populated from the environment.
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class MyHttpClient
|
34
|
+
include ConfigurableFromEnv
|
35
|
+
|
36
|
+
# Define an api_key accessor that is automatically populated from ENV["MY_API_KEY"]
|
37
|
+
config_accessor :api_key, from_env: "MY_API_KEY"
|
38
|
+
|
39
|
+
# Validate and convert ENV value into a desired data type
|
40
|
+
config_accessor :timeout, from_env: { key: "MY_TIMEOUT", type: :integer }
|
41
|
+
|
42
|
+
# Fall back to a default value if the environment variable is absent
|
43
|
+
config_accessor :verify_tls, from_env: { key: "MY_VERIFY_TLS", type: :boolean }, default: true
|
44
|
+
|
45
|
+
def fetch_data
|
46
|
+
# Config attributes are exposed as instance methods for easy access
|
47
|
+
conn = Faraday.new(
|
48
|
+
headers: { "X-API-Key" => api_key },
|
49
|
+
request: { timeout: timeout },
|
50
|
+
ssl: { verify: verify_tls }
|
51
|
+
)
|
52
|
+
conn.get("https://my.api/data")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
## Motivation
|
58
|
+
|
59
|
+
Rails lacks a simple way to declare configuration dependencies on `ENV` values. Generally, the framework guides you toward one of two common approaches:
|
60
|
+
|
61
|
+
**Use `ENV.fetch` directly when you need a value.** This is the most direct technique, but it means that `ENV` dependencies are scattered throughout implementation code. Testing can become more difficult due to this implicit global variable dependency, and you may need a special library to mock `ENV` access.
|
62
|
+
|
63
|
+
**Define a configuration object, and use an initializer to copy values from `ENV` into the config.** Third-party gems often use this approach. Consider Devise, which uses `config/initializers/devise.rb`.
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
Devise.setup do |config|
|
67
|
+
config.secret_key = ENV.fetch("DEVISE_SECRET_KEY")
|
68
|
+
```
|
69
|
+
|
70
|
+
This is very flexible, and works well for libraries that need to be portable across many different app environments. However for application-level code it can repetitive, as each configuration attribute has to be declared multiple times, often in 3 separate files:
|
71
|
+
|
72
|
+
1. Define a configuration class that declares the attribute.
|
73
|
+
2. Create an initializer that sets the attribute.
|
74
|
+
3. Reference the configuration when using the attribute.
|
75
|
+
|
76
|
+
**Regardless of where you put the `ENV` access, validating and converting values can be tedious.** Environment values are always strings, but often we need them to configure settings that are booleans or integers.
|
77
|
+
|
78
|
+
### A different approach
|
79
|
+
|
80
|
+
`configurable_from_env` is an extremely lightweight solution (~80 LOC) to the shortcomings listed above. Consider this example:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class MyHttpClient
|
84
|
+
include ConfigurableFromEnv
|
85
|
+
config_accessor :timeout, from_env: { key: "MY_TIMEOUT", type: :integer }, default: 30
|
86
|
+
```
|
87
|
+
|
88
|
+
The benefits are:
|
89
|
+
|
90
|
+
- Configurable attributes, their default values, how they map from environment variables, and their data types are all declared in one place, as opposed to scattered across initializers, classes, and/or YAML files.
|
91
|
+
- The configuration is colocated with the code where it used (i.e. the `MyHttpClient` class, in the example).
|
92
|
+
- Because these are simple accessors, values can be easily injected in unit tests without needing to mock `ENV`.
|
93
|
+
- Specifying a `:type` takes care of validation and conversion of environment values with an intuitive and concise syntax.
|
94
|
+
|
95
|
+
## Support
|
96
|
+
|
97
|
+
If you want to report a bug, or have ideas, feedback or questions about the gem, [let me know via GitHub issues](https://github.com/mattbrictson/configurable_from_env/issues/new) and I will do my best to provide a helpful answer. Happy hacking!
|
98
|
+
|
99
|
+
## License
|
100
|
+
|
101
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
102
|
+
|
103
|
+
## Code of conduct
|
104
|
+
|
105
|
+
Everyone interacting in this project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
|
106
|
+
|
107
|
+
## Contribution guide
|
108
|
+
|
109
|
+
Pull requests are welcome!
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module ConfigurableFromEnv
|
2
|
+
class EnvironmentValue
|
3
|
+
TYPES = %i[boolean integer string].freeze
|
4
|
+
|
5
|
+
BOOLEAN_VALUES = {}.merge(
|
6
|
+
%w[1 true yes t y enable enabled on].to_h { [_1, true] },
|
7
|
+
%w[0 false no f n disable disabled off].to_h { [_1, false] },
|
8
|
+
{ "" => false }
|
9
|
+
).freeze
|
10
|
+
|
11
|
+
def self.from(definition)
|
12
|
+
return nil if definition.nil?
|
13
|
+
|
14
|
+
definition = { key: definition } unless definition.is_a?(Hash)
|
15
|
+
definition.assert_valid_keys(:key, :type)
|
16
|
+
new(**definition)
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :key, :type
|
20
|
+
|
21
|
+
def initialize(key:, type: :string, env: ENV)
|
22
|
+
unless TYPES.include?(type)
|
23
|
+
raise ArgumentError, "Invalid type: #{type.inspect} (must be one of #{TYPES.map(&:inspect).join(', ')})"
|
24
|
+
end
|
25
|
+
|
26
|
+
@key = key
|
27
|
+
@type = type
|
28
|
+
@env = env
|
29
|
+
end
|
30
|
+
|
31
|
+
def read(required: true)
|
32
|
+
if env.key?(key)
|
33
|
+
value = convert(env[key])
|
34
|
+
block_given? ? yield(value) : value
|
35
|
+
elsif required
|
36
|
+
raise ArgumentError, "Missing required environment variable: #{key}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :env
|
43
|
+
|
44
|
+
def convert(value)
|
45
|
+
send(:"convert_to_#{type}", value)
|
46
|
+
rescue ArgumentError
|
47
|
+
raise ArgumentError, "Environment variable #{key} has an invalid #{type} value: #{value.inspect}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def convert_to_boolean(value)
|
51
|
+
BOOLEAN_VALUES.fetch(value&.downcase&.strip) do
|
52
|
+
raise ArgumentError, "Boolean value must be one of #{BOOLEAN_VALUES.keys.join(', ')}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def convert_to_integer(value)
|
57
|
+
Integer(value)
|
58
|
+
end
|
59
|
+
|
60
|
+
def convert_to_string(value)
|
61
|
+
value.to_s
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "active_support"
|
2
|
+
require "active_support/concern"
|
3
|
+
require "active_support/configurable"
|
4
|
+
require "active_support/core_ext/enumerable"
|
5
|
+
require "active_support/core_ext/hash/keys"
|
6
|
+
|
7
|
+
module ConfigurableFromEnv
|
8
|
+
autoload :EnvironmentValue, "configurable_from_env/environment_value"
|
9
|
+
autoload :VERSION, "configurable_from_env/version"
|
10
|
+
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
include ActiveSupport::Configurable
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def config_accessor(*attributes, from_env: nil, **options, &block)
|
16
|
+
if from_env && attributes.many?
|
17
|
+
raise ArgumentError, "Only one accessor at a time can be created using the :from_env option"
|
18
|
+
end
|
19
|
+
|
20
|
+
env_value = EnvironmentValue.from(from_env)
|
21
|
+
accessor = super(*attributes, **options, &block)
|
22
|
+
default_provided = options.key?(:default) || block_given?
|
23
|
+
|
24
|
+
env_value&.read(required: !default_provided) do |value|
|
25
|
+
public_send(:"#{attributes.first}=", value)
|
26
|
+
end
|
27
|
+
|
28
|
+
accessor
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: configurable_from_env
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Brictson
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-11-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.2'
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
- opensource@mattbrictson.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- LICENSE.txt
|
35
|
+
- README.md
|
36
|
+
- lib/configurable_from_env.rb
|
37
|
+
- lib/configurable_from_env/environment_value.rb
|
38
|
+
- lib/configurable_from_env/version.rb
|
39
|
+
homepage: https://github.com/mattbrictson/configurable_from_env
|
40
|
+
licenses:
|
41
|
+
- MIT
|
42
|
+
metadata:
|
43
|
+
bug_tracker_uri: https://github.com/mattbrictson/configurable_from_env/issues
|
44
|
+
changelog_uri: https://github.com/mattbrictson/configurable_from_env/releases
|
45
|
+
source_code_uri: https://github.com/mattbrictson/configurable_from_env
|
46
|
+
homepage_uri: https://github.com/mattbrictson/configurable_from_env
|
47
|
+
rubygems_mfa_required: 'true'
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '3.1'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubygems_version: 3.5.23
|
64
|
+
signing_key:
|
65
|
+
specification_version: 4
|
66
|
+
summary: Define accessors that are automatically populated via ENV
|
67
|
+
test_files: []
|