Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow env variable prefix and setting namespace to be configured in settings provider #258

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion docsite/source/settings.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Application.register_provider(:settings, from: :dry_system) do
require "your/types/module"
end

configure do |config|
config.prefix = 'SOME_PREFIX_'
end

settings do
setting :database_url, constructor: Types::String.constrained(filled: true)

Expand All @@ -29,7 +33,7 @@ Application.register_provider(:settings, from: :dry_system) do
end
```

Your provider will then map `ENV` variables to a struct object giving access to your settings as their own methods, which you can use throughout your application:
An optional prefix can be specified with the `config.prefix` setting inside a `configure` block. Your provider will then map `ENV` variables with the given prefix to a struct object giving access to your settings as their own methods, which you can use throughout your application:

```ruby
Application[:settings].database_url # => "postgres://..."
Expand Down Expand Up @@ -65,3 +69,56 @@ Or as an injected dependency in your classes:
end
end
```

## Multiple Settings Providers

In some situations you may wish to have multiple settings providers registered to different namespaces, e.g `config.database` and `config.api`. This can be achieved using the `register_as` configuration option:

```ruby
# system/providers/database_settings.rb:

require "dry/system/provider_sources"

Application.register_provider(:database_settings, from: :dry_system, source: :settings) do
before :prepare do
require "your/types/module"
end

configure do |config|
config.register_as = 'config.database'
end

settings do
setting :url, constructor: Types::String.constrained(filled: true)
end
end
```

```ruby
# system/providers/api_settings.rb:

require "dry/system/provider_sources"

Application.register_provider(:api_settings, from: :dry_system, source: :settings) do
before :prepare do
require "your/types/module"
end

configure do |config|
config.register_as = 'config.api'
end

settings do
setting :base_url, constructor: Types::String.constrained(filled: true)
end
end
```

The individual settings namespaces can then be accessed from the container seperately:

```ruby
Application.start(:database_settings)
Application.start(:api_settings)
Application['config.database'].url # => "postgres://..."
Application['config.api'].base_url # => "https://..."
```
6 changes: 5 additions & 1 deletion lib/dry/system/provider_sources/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ module ProviderSources
module Settings
class Source < Dry::System::Provider::Source
setting :store
setting :register_as, default: :settings
setting :prefix, default: ""

def prepare
require "dry/system/provider_sources/settings/config"
end

def start
register(:settings, settings.load(root: target.root, env: target.config.env))
register(config.register_as,
settings.load(root: target.root, env: target.config.env,
prefix: config.prefix))
end

def settings(&block)
Expand Down
4 changes: 2 additions & 2 deletions lib/dry/system/provider_sources/settings/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ def setting_errors(errors)
# @api private
class Config
# @api private
def self.load(root:, env:, loader: Loader)
def self.load(root:, env:, prefix: "", loader: Loader)
loader = loader.new(root: root, env: env)

new.tap do |settings_obj|
errors = {}

settings.to_a.each do |setting|
value = loader[setting.name.to_s.upcase]
value = loader[prefix + setting.name.to_s.upcase]

begin
if value
Expand Down
97 changes: 97 additions & 0 deletions spec/integration/settings_component_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,101 @@
end
end
end

context "With a custom prefix" do
subject(:system) do
Class.new(Dry::System::Container) do
setting :env

configure do |config|
config.root = SPEC_ROOT.join("fixtures").join("settings_test")
config.env = :test
end

register_provider(:settings, from: :dry_system) do
configure do |config|
config.prefix = "CUSTOM_PREFIX_"
end

before(:prepare) do
target_container.require_from_root "types"
end

settings do
setting :string_value, constructor: SettingsTest::Types::String
end
end
end
end

before do
ENV["CUSTOM_PREFIX_STRING_VALUE"] = "foo"
end

after do
ENV.delete("CUSTOM_PREFIX_STRING_VALUE")
end

it "sets up system settings component via ENV and .env" do
expect(settings.string_value).to eql("foo")
end
end

context "With multiple settings providers" do
subject(:system) do
Class.new(Dry::System::Container) do
setting :env

configure do |config|
config.root = SPEC_ROOT.join("fixtures").join("settings_test")
config.env = :test
end

register_provider(:settings_provider1, from: :dry_system, source: :settings) do
configure do |config|
config.register_as = "database_settings"
end

before(:prepare) do
target_container.require_from_root "types"
end

settings do
setting :example_port, constructor: SettingsTest::Types::Coercible::Integer
end
end

register_provider(:settings_provider2, from: :dry_system, source: :settings) do
configure do |config|
config.register_as = "api_settings"
end

before(:prepare) do
target_container.require_from_root "types"
end

settings do
setting :example_token, constructor: SettingsTest::Types::String
end
end
end
end

before do
ENV["EXAMPLE_PORT"] = "19"
ENV["EXAMPLE_TOKEN"] = "abc123"
system.start(:settings_provider1)
system.start(:settings_provider2)
end

after do
ENV.delete("EXAMPLE_PORT")
ENV.delete("EXAMPLE_TOKENT")
end

it "sets up system settings component via ENV and .env" do
expect(system["database_settings"].example_port).to eql(19)
expect(system["api_settings"].example_token).to eql("abc123")
end
end
end