Skip to content

2.0.0 🐙

Compare
Choose a tag to compare
@palkan palkan released this 14 Apr 13:13
· 216 commits to master since this release

New major release including significant internals refactoring and multiple new features.
See the complete documentation in the repo Readme.

Highlights

Loaders API (📖 Docs)

Now it's possible to extend and modify data sources for configs.
By default, YAML files, ENV, secrets.yml and credentials (NEW) for Rails are supported.

Local configuration files (📖 Docs)

Now users can store their personal configurations in local files:

  • config/<config_name>.local.yml
  • config/credentials/local.yml.enc (for Rails 6).

Rails integration (📖 Docs)

Using Anyway Config to manage application-specific configuration became much easier:

  • Generators are added for setting up anyway_config and creating new configs.
  • Autoloading configs work during the application initialization (so you can use config classes in Rails configuration files and initializers).
  • Added credentials support.

Validation and callbacks (📖 Docs)

You can mark some attributes as required, which would made config load to fail if they're missing of blank:

attr_config :user, :password

required :user, :password

You can also add custom hooks to be run every time the configuration is loaded (i.e., during the initialization or when #reload is called):

on_load do
  raise_validation_error("Unknown log level: #{log_level}") unless LOG_LEVELS.include?(log_level)
end

Source tracing (📖 Docs)

It's now possible to get the information on where the config data came from:

conf = ExampleConfig.new
conf.to_source_trace

# returns the following hash
{
  "host" => {value: "test.host", source: {type: :yml, path: "config/example.yml"}},
  "user" => {
    "name" => {value: "john", source: {type: :env, key: "EXAMPLE_USER__NAME"}},
    "password" => {value: "root", source: {type: :credentials, store: "config/credentials/production.enc.yml"}}
  },
  "port" => {value: 9292, source: {type: :defaults}}
}

Breaking

  • Ruby >= 2.5.0 is required.

  • Changed the way of providing explicit values:

# BEFORE
Config.new(overrides: data)

# AFTER
Config.new(data)
  • The accessors generated by attr_config are no longer attr_accessor-s.

You cannot rely on instance variables anymore. Instead, you can use super when overriding accessors or values[name]. For example:

class MyConfig < Anyway::Config
  attr_config :host, :port, :url, :meta

  # override writer to handle type coercion
  def meta=(val)
    super JSON.parse(val)
  end

  # or override reader to handle missing values
  def url
    super || (self.url = "#{host}:#{port}")
  end

  # untill v2.1, it will still be possible to read instance variables,
  # i.e. the following code would also work
  def url
    @url ||= "#{host}:#{port}"
  end
end

We recommend to add a feature check and support both v1.x and v2.0 in gems for the time being:

# Check for the class method added in 2.0, e.g., `.on_load`
if respond_to?(:on_load)
  def url
    super || (self.url = "#{host}:#{port}")
  end
else
  def url
    @url ||= "#{host}:#{port}"
  end
end

We still set instance variables in writers (for backward compatibility) but that is to be removed in 2.1

  • (only If you upgrading from v2.0.0.pre) Changed the way Rails application configs autoloading works.

In Rails 6, autoloading before initialization is deprecated. We can still make it work by using our own autoloading mechanism (custom Zeitwerk loader).

This forces us to use a custom directory (not app/) for configs required at the boot time.

By default, we put static configs into config/configs but you can still use app/configs for dynamic (runtime) configs.

If you used app/configs and relied on configs during initialization, you can set static configs path to app/configs:

config.anyway_config.autoload_static_config_path = "app/configs"

You can do this by running the generator:

rails g anyway:install --configs-path=app/configs

Features

  • Added with_env helper to test code in the context of the specified environment variables.

Included automatically in RSpec for examples with the type: :config meta or with the spec/configs path.

  • Added Config#dig method.

  • Add Config#deconstruct_keys.

Now you can use configs in pattern matching:

case AWSConfig.new
in bucket:, region: "eu-west-1"
  setup_eu_storage(bucket)
in bucket:, region: "us-east-1"
  setup_us_storage(bucket)
end
  • Add predicate methods for attributes with boolean defaults. ([@palkan][])

For example:

class MyConfig < Anyway::Config
  attr_config :key, :secret, debug: false
end

MyConfig.new.debug? #=> false
MyConfig.new(debug: true).debug? #=> true

Changes

  • Config attribute names are now validated.

Using reserved names (Anyway::Config method names) is not allowed.
Only alphanumeric names (matching /^[a-z_]([\w]+)?$/) are allowed.

  • Updated config name inference logic.

Config name is automatically inferred only if the class name has a form of <Module>::Config (SomeModule::Config => "some_module") or the class name has a form of <Something>Config (SomeConfig => "some").

  • Added ability to specify types for OptionParser options.
describe_options(
  concurrency: {
    desc: "number of threads to use",
    type: String
  }
)