configurable_from_env 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ [![Gem Version](https://img.shields.io/gem/v/configurable_from_env)](https://rubygems.org/gems/configurable_from_env)
4
+ [![Gem Downloads](https://img.shields.io/gem/dt/configurable_from_env)](https://www.ruby-toolbox.com/projects/configurable_from_env)
5
+ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/mattbrictson/configurable_from_env/ci.yml)](https://github.com/mattbrictson/configurable_from_env/actions/workflows/ci.yml)
6
+ [![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/mattbrictson/configurable_from_env)](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,3 @@
1
+ module ConfigurableFromEnv
2
+ VERSION = "0.1.0".freeze
3
+ 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: []