anyway_config 2.0.5 → 2.2.1

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -181
  3. data/README.md +238 -13
  4. data/lib/.rbnext/1995.next/anyway/config.rb +438 -0
  5. data/lib/.rbnext/1995.next/anyway/dynamic_config.rb +31 -0
  6. data/lib/.rbnext/1995.next/anyway/env.rb +56 -0
  7. data/lib/.rbnext/1995.next/anyway/loaders/base.rb +21 -0
  8. data/lib/.rbnext/1995.next/anyway/tracing.rb +181 -0
  9. data/lib/.rbnext/2.7/anyway/auto_cast.rb +39 -19
  10. data/lib/.rbnext/2.7/anyway/config.rb +61 -16
  11. data/lib/.rbnext/2.7/anyway/rails/loaders/yaml.rb +30 -0
  12. data/lib/.rbnext/2.7/anyway/rbs.rb +92 -0
  13. data/lib/.rbnext/2.7/anyway/settings.rb +79 -0
  14. data/lib/.rbnext/2.7/anyway/tracing.rb +6 -6
  15. data/lib/.rbnext/2.7/anyway/type_casting.rb +143 -0
  16. data/lib/.rbnext/3.0/anyway/auto_cast.rb +53 -0
  17. data/lib/.rbnext/{2.8 → 3.0}/anyway/config.rb +61 -16
  18. data/lib/.rbnext/{2.8 → 3.0}/anyway/loaders/base.rb +0 -0
  19. data/lib/.rbnext/{2.8 → 3.0}/anyway/loaders.rb +0 -0
  20. data/lib/.rbnext/{2.8 → 3.0}/anyway/tracing.rb +6 -6
  21. data/lib/anyway/auto_cast.rb +39 -19
  22. data/lib/anyway/config.rb +75 -30
  23. data/lib/anyway/dynamic_config.rb +6 -2
  24. data/lib/anyway/env.rb +1 -1
  25. data/lib/anyway/ext/deep_dup.rb +12 -0
  26. data/lib/anyway/ext/hash.rb +10 -12
  27. data/lib/anyway/loaders/base.rb +1 -1
  28. data/lib/anyway/loaders/env.rb +3 -1
  29. data/lib/anyway/loaders/yaml.rb +9 -5
  30. data/lib/anyway/option_parser_builder.rb +1 -3
  31. data/lib/anyway/optparse_config.rb +5 -7
  32. data/lib/anyway/rails/loaders/credentials.rb +4 -4
  33. data/lib/anyway/rails/loaders/secrets.rb +6 -8
  34. data/lib/anyway/rails/loaders/yaml.rb +11 -0
  35. data/lib/anyway/rails/settings.rb +9 -2
  36. data/lib/anyway/rbs.rb +92 -0
  37. data/lib/anyway/settings.rb +52 -2
  38. data/lib/anyway/tracing.rb +9 -9
  39. data/lib/anyway/type_casting.rb +134 -0
  40. data/lib/anyway/utils/deep_merge.rb +21 -0
  41. data/lib/anyway/version.rb +1 -1
  42. data/lib/anyway_config.rb +4 -0
  43. data/sig/anyway_config.rbs +129 -0
  44. metadata +42 -15
  45. data/lib/.rbnext/2.7/anyway/option_parser_builder.rb +0 -31
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](http://cultofmartians.com)
1
+ [![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](https://cultofmartians.com/tasks/anyway-config-options-parse.html#task)
2
2
  [![Gem Version](https://badge.fury.io/rb/anyway_config.svg)](https://rubygems.org/gems/anyway_config) [![Build](https://github.com/palkan/anyway_config/workflows/Build/badge.svg)](https://github.com/palkan/anyway_config/actions)
3
3
  [![JRuby Build](https://github.com/palkan/anyway_config/workflows/JRuby%20Build/badge.svg)](https://github.com/palkan/anyway_config/actions)
4
4
 
@@ -42,12 +42,14 @@ For version 1.x see the [1-4-stable branch](https://github.com/palkan/anyway_con
42
42
  - [Generators](#generators)
43
43
  - [Using with Ruby applications](#using-with-ruby)
44
44
  - [Environment variables](#environment-variables)
45
+ - [Type coercion](#type-coercion)
45
46
  - [Local configuration](#local-files)
46
47
  - [Data loaders](#data-loaders)
47
48
  - [Source tracing](#tracing)
48
49
  - [Pattern matching](#pattern-matching)
49
50
  - [Test helpers](#test-helpers)
50
51
  - [OptionParser integration](#optionparser-integration)
52
+ - [RBS support](#rbs-support)
51
53
 
52
54
  ## Main concepts
53
55
 
@@ -94,7 +96,7 @@ Or adding to your project:
94
96
 
95
97
  ```ruby
96
98
  # Gemfile
97
- gem "anyway_config", "~> 2.0.0"
99
+ gem "anyway_config", "~> 2.0"
98
100
  ```
99
101
 
100
102
  ### Supported Ruby versions
@@ -269,6 +271,7 @@ This feature is similar to `Rails.application.config_for` but more powerful:
269
271
  | Load data from environment | ❌ | ✅ |
270
272
  | Load data from [custom sources](#data-loaders) | ❌ | ✅ |
271
273
  | Local config files | ❌ | ✅ |
274
+ | Type coercion | ❌ | ✅ |
272
275
  | [Source tracing](#tracing) | ❌ | ✅ |
273
276
  | Return Hash with indifferent access | ❌ | ✅ |
274
277
  | Support ERB\* within `config/app.yml` | ✅ | ✅ |
@@ -329,7 +332,9 @@ and then use [Rails generators](#generators) to make your application Anyway Con
329
332
 
330
333
  Your config is filled up with values from the following sources (ordered by priority from low to high):
331
334
 
332
- - `RAILS_ROOT/config/my_cool_gem.yml` (for the current `RAILS_ENV`, supports `ERB`):
335
+ 1) **YAML configuration files**: `RAILS_ROOT/config/my_cool_gem.yml`.
336
+
337
+ Rails environment is used as the namespace (required); supports `ERB`:
333
338
 
334
339
  ```yml
335
340
  test:
@@ -341,9 +346,51 @@ development:
341
346
  port: 3000
342
347
  ```
343
348
 
344
- **NOTE:** you can override the default YML lookup path by setting `MYCOOLGEM_CONF` env variable.
349
+ ### Multi-env configuration
350
+
351
+ _⚡️ This feature will be turned on by default in the future releases. You can turn it on now via `config.anyway_config.future.use :unwrap_known_environments`._
345
352
 
346
- - `Rails.application.secrets.my_cool_gem` (if `secrets.yml` present):
353
+ If the YML does not have keys that are one of the "known" Rails environments (development, production, test)—the same configuration will be available in all environments, similar to non-Rails behavior:
354
+
355
+ ```yml
356
+ host: localhost
357
+ port: 3002
358
+ # These values will be active in all environments
359
+ ```
360
+
361
+ To extend the list of known environments, use the setting in the relevant part of your Rails code:
362
+
363
+ ```ruby
364
+ Rails.application.config.anyway_config.known_environments << "staging"
365
+ ```
366
+
367
+ If your YML defines at least a single "environmental" top-level, you _have_ to separate all your settings per-environment. You can't mix and match:
368
+
369
+ ```yml
370
+ staging:
371
+ host: localhost # This value will be loaded when Rails.env.staging? is true
372
+
373
+ port: 3002 # This value will not be loaded at all
374
+ ```
375
+
376
+ You can specify the lookup path for YAML files in one of the following ways:
377
+
378
+ - By setting `config.anyway_config.default_config_path` to a target directory path:
379
+
380
+ ```ruby
381
+ config.anyway_config.default_config_path = "/etc/configs"
382
+ config.anyway_config.default_config_path = Rails.root.join("etc", "configs")
383
+ ```
384
+
385
+ - By setting `config.anyway_config.default_config_path` to a Proc, which accepts a config name and returns the path:
386
+
387
+ ```ruby
388
+ config.anyway_config.default_config_path = ->(name) { Rails.root.join("data", "configs", "#{name}.yml") }
389
+ ```
390
+
391
+ - By overriding a specific config YML file path via the `<NAME>_CONF` env variable, e.g., `MYCOOLGEM_CONF=path/to/cool.yml`
392
+
393
+ 2) **Rails secrets**: `Rails.application.secrets.my_cool_gem` (if `secrets.yml` present).
347
394
 
348
395
  ```yml
349
396
  # config/secrets.yml
@@ -352,7 +399,7 @@ development:
352
399
  port: 4444
353
400
  ```
354
401
 
355
- - `Rails.application.credentials.my_cool_gem` (if supported):
402
+ 3) **Rails credentials**: `Rails.application.credentials.my_cool_gem` (if supported):
356
403
 
357
404
  ```yml
358
405
  my_cool_gem:
@@ -361,7 +408,7 @@ my_cool_gem:
361
408
 
362
409
  **NOTE:** You can backport Rails 6 per-environment credentials to Rails 5.2 app using [this patch](https://gist.github.com/palkan/e27e4885535ff25753aefce45378e0cb).
363
410
 
364
- - `ENV['MYCOOLGEM_*']`.
411
+ 4) **Environment variables**: `ENV['MYCOOLGEM_*']`.
365
412
 
366
413
  See [environment variables](#environment-variables).
367
414
 
@@ -407,6 +454,22 @@ config.anyway_config.autoload_static_config_path = "path/to/configs"
407
454
  **NOTE:** Configs loaded from the `autoload_static_config_path` are **not reloaded in development**. We call them _static_. So, it makes sense to keep only configs necessary for initialization in this folder. Other configs, _dynamic_, could be stored in `app/configs`.
408
455
  Or you can store everything in `app/configs` by setting `config.anyway_config.autoload_static_config_path = "app/configs"`.
409
456
 
457
+ **NOTE 2**: Since _static_ configs are loaded before initializers, it's not possible to use custom inflection Rules (usually defined in `config/initializers/inflections.rb`) to resolve constant names from files. If you rely on custom inflection rules (see, for example, [#81](https://github.com/palkan/anyway_config/issues/81)), we recommend configuration Rails inflector before initialization as well:
458
+
459
+ ```ruby
460
+ # config/application.rb
461
+
462
+ # ...
463
+
464
+ require_relative "initializers/inflections"
465
+
466
+ module SomeApp
467
+ class Application < Rails::Application
468
+ # ...
469
+ end
470
+ end
471
+ ```
472
+
410
473
  ### Generators
411
474
 
412
475
  Anyway Config provides Rails generators to create new config classes:
@@ -444,7 +507,7 @@ Alternatively, you can call `rails g anyway:app_config name param1 param2 ...`.
444
507
 
445
508
  The default data loading mechanism for non-Rails applications is the following (ordered by priority from low to high):
446
509
 
447
- - `./config/<config-name>.yml` (`ERB` is supported if `erb` is loaded)
510
+ 1) **YAML configuration files**: `./config/<config-name>.yml`.
448
511
 
449
512
  In pure Ruby apps, we do not know about _environments_ (`test`, `development`, `production`, etc.); thus, we assume that the YAML contains values for a single environment:
450
513
 
@@ -453,9 +516,25 @@ host: localhost
453
516
  port: 3000
454
517
  ```
455
518
 
456
- **NOTE:** you can override the default YML lookup path by setting `MYCOOLGEM_CONF` env variable.
519
+ `ERB` is supported if `erb` is loaded (thus, you need to call `require "erb"` somewhere before loading configuration).
520
+
521
+ You can specify the lookup path for YAML files in one of the following ways:
522
+
523
+ - By setting `Anyway::Settings.default_config_path` to a target directory path:
524
+
525
+ ```ruby
526
+ Anyway::Settings.default_config_path = "/etc/configs"
527
+ ```
528
+
529
+ - By setting `Anyway::Settings.default_config_path` to a Proc, which accepts a config name and returns the path:
530
+
531
+ ```ruby
532
+ Anyway::Settings.default_config_path = ->(name) { Rails.root.join("data", "configs", "#{name}.yml") }
533
+ ```
457
534
 
458
- - `ENV['MYCOOLGEM_*']`.
535
+ - By overriding a specific config YML file path via the `<NAME>_CONF` env variable, e.g., `MYCOOLGEM_CONF=path/to/cool.yml`
536
+
537
+ 2) **Environment variables**: `ENV['MYCOOLGEM_*']`.
459
538
 
460
539
  See [environment variables](#environment-variables).
461
540
 
@@ -465,13 +544,15 @@ Environmental variables for your config should start with your config name, uppe
465
544
 
466
545
  For example, if your config name is "mycoolgem", then the env var "MYCOOLGEM_PASSWORD" is used as `config.password`.
467
546
 
468
- Environment variables are automatically type cast:
547
+ By default, environment variables are automatically type cast\*:
469
548
 
470
549
  - `"True"`, `"t"` and `"yes"` to `true`;
471
550
  - `"False"`, `"f"` and `"no"` to `false`;
472
551
  - `"nil"` and `"null"` to `nil` (do you really need it?);
473
552
  - `"123"` to 123 and `"3.14"` to 3.14.
474
553
 
554
+ \* See below for coercion customization.
555
+
475
556
  *Anyway Config* supports nested (_hashed_) env variables—just separate keys with double-underscore.
476
557
 
477
558
  For example, "MYCOOLGEM_OPTIONS__VERBOSE" is parsed as `config.options["verbose"]`.
@@ -489,6 +570,94 @@ If you want to provide a text-like env variable which contains commas then wrap
489
570
  MYCOOLGEM = "Nif-Nif, Naf-Naf and Nouf-Nouf"
490
571
  ```
491
572
 
573
+ ## Type coercion
574
+
575
+ > 🆕 v2.2.0
576
+
577
+ You can define custom type coercion rules to convert string data to config values. To do that, use `.coerce_types` method:
578
+
579
+ ```ruby
580
+ class CoolConfig < Anyway::Config
581
+ config_name :cool
582
+ attr_config port: 8080,
583
+ host: "localhost",
584
+ user: {name: "admin", password: "admin"}
585
+
586
+ coerce_types port: :string, user: {dob: :date}
587
+ end
588
+
589
+ ENV["COOL_USER__DOB"] = "1989-07-01"
590
+
591
+ config = CoolConfig.new
592
+ config.port == "8080" # Even though we defined the default value as int, it's converted into a string
593
+ config.user["dob"] == Date.new(1989, 7, 1) #=> true
594
+ ```
595
+
596
+ Type coercion is especially useful to deal with array values:
597
+
598
+ ```ruby
599
+ # To define an array type, provide a hash with two keys:
600
+ # - type — elements type
601
+ # - array: true — mark the parameter as array
602
+ coerce_types list: {type: :string, array: true}
603
+ ```
604
+
605
+ You can use `type: nil` in case you don't want to coerce values, just convert a value into an array:
606
+
607
+ ```ruby
608
+ # From AnyCable config (sentinels could be represented via strings or hashes)
609
+ coerce_types redis_sentinels: {type: nil, array: true}
610
+ ```
611
+
612
+ It's also could be useful to explicitly define non-array types (to avoid confusion):
613
+
614
+ ```ruby
615
+ coerce_types non_list: :string
616
+ ```
617
+
618
+ Finally, it's possible to disable auto-casting for a particular config completely:
619
+
620
+ ```ruby
621
+ class CoolConfig < Anyway::Config
622
+ attr_config port: 8080,
623
+ host: "localhost",
624
+ user: {name: "admin", password: "admin"}
625
+
626
+ disable_auto_cast!
627
+ end
628
+
629
+ ENV["COOL_PORT"] = "443"
630
+
631
+ CoolConfig.new.port == "443" #=> true
632
+ ```
633
+
634
+ **IMPORTANT**: Values provided explicitly (via attribute writers) are not coerced. Coercion is only happening during the load phase.
635
+
636
+ The following types are supported out-of-the-box: `:string`, `:integer`, `:float`, `:date`, `:datetime`, `:uri`, `:boolean`.
637
+
638
+ You can use custom deserializers by passing a callable object instead of a type name:
639
+
640
+ ```ruby
641
+ COLOR_TO_HEX = lambda do |raw|
642
+ case raw
643
+ when "red"
644
+ "#ff0000"
645
+ when "green"
646
+ "#00ff00"
647
+ when "blue"
648
+ "#0000ff"
649
+ end
650
+ end
651
+
652
+ class CoolConfig < Anyway::Config
653
+ attr_config :color
654
+
655
+ coerce_types color: COLOR_TO_HEX
656
+ end
657
+
658
+ CoolConfig.new({color: "red"}).color #=> "#ff0000"
659
+ ```
660
+
492
661
  ## Local files
493
662
 
494
663
  It's useful to have a personal, user-specific configuration in development, which extends the project-wide one.
@@ -550,7 +719,7 @@ In order to support [source tracing](#tracing), you need to wrap the resulting H
550
719
 
551
720
  ```ruby
552
721
  def call(name:, **_opts)
553
- trace!(source: :chamber) do
722
+ trace!(:chamber) do
554
723
  Chamber.env.to_h[name] || {}
555
724
  end
556
725
  end
@@ -666,7 +835,7 @@ If you want to delete the env var, pass `nil` as the value.
666
835
 
667
836
  This helper is automatically included to RSpec if `RAILS_ENV` or `RACK_ENV` env variable is equal to "test". It's only available for the example with the tag `type: :config` or with the path `spec/configs/...`.
668
837
 
669
- You can add it manually by requiring `"anyway/testing/helpers"` and including the `Anyway::Test::Helpers` module (into RSpec configuration or Minitest test class).
838
+ You can add it manually by requiring `"anyway/testing/helpers"` and including the `Anyway::Testing::Helpers` module (into RSpec configuration or Minitest test class).
670
839
 
671
840
  ## OptionParser integration
672
841
 
@@ -730,6 +899,62 @@ describe_options(
730
899
  )
731
900
  ```
732
901
 
902
+ ## RBS support
903
+
904
+ Anyway Config comes with Ruby type signatures (RBS).
905
+
906
+ To use them with Steep, add `library "anyway_config"` to your Steepfile.
907
+
908
+ We also provide an API to generate a type signature for your config class:
909
+
910
+ ```ruby
911
+ class MyGem::Config < Anyway::Config
912
+ attr_config :host, port: 8080, tags: [], debug: false
913
+
914
+ coerce_types host: :string, port: :integer,
915
+ tags: {type: :string, array: true}
916
+
917
+ required :host
918
+ end
919
+ ```
920
+
921
+ Then calling `MyGem::Config.to_rbs` will return the following signature:
922
+
923
+ ```rbs
924
+ module MyGem
925
+ interface _Config
926
+ def host: () -> String
927
+ def host=: (String) -> void
928
+ def port: () -> String?
929
+ def port=: (String) -> void
930
+ def tags: () -> Array[String]?
931
+ def tags=: (Array[String]) -> void
932
+ def debug: () -> bool
933
+ def debug?: () -> bool
934
+ def debug=: (bool) -> void
935
+ end
936
+
937
+ class Config < Anyway::Config
938
+ include _Config
939
+ end
940
+ end
941
+ ```
942
+
943
+ ### Handling `on_load`
944
+
945
+ When we use `on_load` callback with a block, we switch the context (via `instance_eval`), and we need to provide type hints for the type checker. Here is an example:
946
+
947
+ ```ruby
948
+ class MyConfig < Anyway::Config
949
+ on_load do
950
+ # @type self : MyConfig
951
+ raise_validation_error("host is invalid") if host.start_with?("localhost")
952
+ end
953
+ end
954
+ ```
955
+
956
+ Yeah, a lot of annotations 😞 Welcome to the type-safe world!
957
+
733
958
  ## Contributing
734
959
 
735
960
  Bug reports and pull requests are welcome on GitHub at [https://github.com/palkan/anyway_config](https://github.com/palkan/anyway_config).