An open API service indexing awesome lists of open source software.

https://github.com/nxt-insurance/nxt_registry

A simple registry to implement the container pattern
https://github.com/nxt-insurance/nxt_registry

container registry ruby ruby-on-rails

Last synced: about 1 month ago
JSON representation

A simple registry to implement the container pattern

Awesome Lists containing this project

README

        

[![CircleCI](https://circleci.com/gh/nxt-insurance/nxt_registry.svg?style=svg)](https://circleci.com/gh/nxt-insurance/nxt_registry)

# NxtRegistry

`NxtRegistry` is a simple container that allows you to register and resolve values in nested structures.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'nxt_registry'
```

And then execute:

$ bundle

Or install it yourself as:

$ gem install nxt_registry

## Usage

### Simple use case

## Instance Level

If you simply need a single global instance of a registry include `NxtRegistry::Singleton`:

```ruby
class Example
include NxtRegistry::Singleton

registry do
register(:ruby, 'Stone')
register(:python, 'Snake')
register(:javascript, 'undefined')
end
end

Example.resolve(:ruby) # => 'Stone'
```

Alternatively you can simply create instances of `NxtRegistry::Registry`:

```ruby
registry = NxtRegistry::Registry.new do
register(:andy, 'Andy')
register(:anthony, 'Anthony')
register(:aki, 'Aki')
end

registry.resolve(:aki) # => 'Aki'

```

## Class Level

You can also add registries on the class level simply by extending your class with `NxtRegistry`

```ruby
class OtherExample
extend NxtRegistry

registry(:errors) do
register(KeyError, ->(error) { puts 'KeyError handler' } )
register(ArgumentError, ->(error) { puts 'ArgumentError handler' } )
end

registry(:country_codes) do
register(:germany, :de)
register(:england, :uk)
register(:france, :fr)
end
end

OtherExample.registry(:errors).resolve(KeyError)
# KeyError handler
# => nil
OtherExample.registry(:country_codes).resolve(:germany)
# => :de
```

## Register Patterns

You can also register values with patterns as keys. Non pattern keys are always evaluated first and then patterns
will be tried to match by definition sequence.

```ruby
class Example
extend NxtRegistry

registry :status_codes do
register(/\A4\d{2}\z/, 'Client errors')
register(/\A5.*\z/, 'Server errors')
register('422', 'Unprocessable Entity')
register(:'503', 'Internal Server Error')
end
end

Example.registry(:status_codes).resolve('503') # => "Internal Server Error"
Example.registry(:status_codes).resolve(503) # => "Internal Server Error"
Example.registry(:status_codes).resolve(422) # => "Unprocessable Entity"
Example.registry(:status_codes).resolve(404) # => "Client Errors"
```

### Readers

Access your defined registries with the `registry(:country_code)` method.

### Nesting registries

You can also simply nest registries like so:

```ruby
class Nested
extend NxtRegistry

registry :developers do
register(:frontend) do
register(:igor, 'Igor')
register(:ben, 'Ben')
end

register(:backend) do
register(:rapha, 'Rapha')
register(:aki, 'Aki')
end
end
end

Nested.registry(:developers).resolve(:frontend, :igor)
# => 'Igor'
```

#### Inherit options in nested registries

```ruby
class Nested
extend NxtRegistry

registry :developers, default: 'options can be inherited' do
register(:frontend, inherit_options: true) do
register(:igor, 'Igor')
register(:ben, 'Ben')
end
end
end

Nested.registry(:developers).resolve(:frontend, :blank)
# => 'options can be inherited'
```

### Defining specific nesting levels of a registry

Another feature of `NxtRegistry` is that you can define the nesting levels for a registry. Levels allow you to dynamically
register values within the defined levels. This means that on any level the registry will resolve to another registry and
you can register values into a deeply nested structure.

```ruby
class Layer
extend NxtRegistry

registry :from do
level :to do
level :via
end
end
end

# On every upper level every resolve returns a registry
Layer.registry(:from) # => Registry[from]
Layer.registry(:from).resolve(:munich) # => Registry[to] -> {}
Layer.registry(:from).resolve(:amsterdam) # => Registry[to] -> {}
Layer.registry(:from).resolve(:any_key) # => Registry[to] -> {}
Layer.registry(:from).resolve(:munich, :amsterdam) # => Registry[via] -> {}

# Register a value on the bottom level
Layer.registry(:from).resolve(:munich, :amsterdam).register(:train, -> { 'train' })
# Resolve the complete path
Layer.registry(:from).resolve(:munich, :amsterdam, :train) # => 'train'
```

For registries with multiple levels the normal syntax for registering and resolving becomes quite weird and unreadable. This is why
every registry can be accessed through it's name or a custom accessor. The above example then can be simplified as follows.

```ruby
class Layer
extend NxtRegistry

registry :path, accessor: :from do # registry named path, can be accessed with .from(...)
level :to do
level :via
end
end
end

# Register a value
Layer.registry(:path).from(:munich).to(:amsterdam).via(:train, -> { 'train' })
# Resolve the complete path
Layer.registry(:path).from(:munich).to(:amsterdam).via(:train) # => 'train'
```

*Note that this feature is also available for registries with a single level only.*

### Restrict keys to a certain set

Use `allowed_keys` to restrict which keys can be registered on a specific level.

```ruby
registry :example, allowed_keys: %w[one two three]
```

### Require a certain set of keys to be registered

Use `required_keys` to enforce a certain set of keys to be registered on a specific level. This is especially helpful
if you use registries in multiple places and you want to ensure they all register the same set of keys.

```ruby
registry :example, required_keys: %w[one two three]
```

### Default values

Use `default` to register a default value that will be resolved in case an attribute was not registered.

```ruby
registry :example, default: ->(value) { 'default' }
```

### Blocks

When you register a block value that can be called, it will automatically be called when you resolve the value.
If that's not what you want, you can configure your registry (on each level) not to call blocks directly by defining `call false`

```ruby
registry :example, call: false do
register(:one, ->(value) { 'Not called when resolved' } )
end
```

### Memoize

Values are memoized per default. Switch it off with `memoize: false`

```ruby
registry :example, memoize: false do
register(:one, -> { Time.current } )
end

registry.resolve(:one)
# => 2020-01-02 23:56:15 +0100
registry.resolve(:one)
# => 2020-01-02 23:56:17 +0100
registry.resolve(:one)
# => 2020-01-02 23:56:18 +0100
```

**IMPORTANT**: whenever you want your value to be evaluated anew every time it is resolved, you should always wrap it in a lambda.

For example, if you're resolving an ENV variable you should do it this way:

```ruby
registry :example do
register(:env_variable, -> { ENV['FEATURE_FLAG'] })
end
```

In this case config can be reloaded on the fly, and tests can also overwrite feature flags, for example.

### Resolve callbacks

You can hook into the before and after resolver callbacks in case you need to lay hands on your values
before and / or after resolving. A callback can be anything that implements `:call` to which the value is passed.

```ruby
registry :example do
key_resolver ->(key) { key.strip }
resolver ->(value) { value.upcase }

register(:input, 'output')
end

registry.resolve(' input ')
# => 'OUTPUT'
```

### Transform keys

`NxtRegistry` uses a plain ruby hash to store values internally. Per default all keys used are transformed with `&:to_s`.
Thus you can use symbols or strings to register and resolve values. If it's not what you want, switch it off with
`transform_keys false` or define your own key transformer by assigning a block to transform_keys:
`transform_keys ->(key) { key.upcase }`

```ruby
registry :example do
transform_keys ->(key) { key.to_s.downcase }
register(:bombshell, 'hanna')
end

registry.resolve('BOMBSHELL')
# => 'hanna'
```

### Customize registry errors

You can also customize what kind of errors are being raised in case a of a key was not registered or was already registered.
by providing blocks or a handler responding to :call for `on_key_already_registered` and `on_key_already_registered`

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/nxt_registry.

## License

The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).