anyway_config 2.0.5 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
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).